트랩은 검사가 쓰인 파일을 댄다
빌드 전체가 한 모듈이라 파일 이름도 하나였다. std.list 안에서 터진 경계 검사가 프로그램의 파일 이름을 대고 있었으니, 줄 번호는 맞는데 파일이 틀려서 엉뚱한 줄을 가리켰다 -- 이름을 안 대는 것보다 나쁘다. 모듈이 파일 표를 들고 트랩은 그 인덱스를 든다. 생성기는 파일마다 FE_FILE_n 을 한 번씩 찍는다. before: index out of bounds at main.fe:2 after: index out of bounds at pick.fe:6 그리고 Parser.on 이 구조체 리터럴 안에서 다시 try 를 쓴다. 앞서 그것이 깨졌던 것은 try 때문이 아니라 Parser 가 1 바이트로 자리잡았기 때문이었다. 224/224, 31/31.
This commit is contained in:
+20
-4
@@ -5,7 +5,7 @@
|
||||
void fe_ir_module_init(FeIrModule *m)
|
||||
{
|
||||
fe_arena_init(&m->arena, 16384);
|
||||
m->unit_file = "";
|
||||
m->file_count = 0;
|
||||
m->entry_main = 0;
|
||||
m->funcs = 0;
|
||||
m->last_func = 0;
|
||||
@@ -13,6 +13,20 @@ void fe_ir_module_init(FeIrModule *m)
|
||||
m->last_global = 0;
|
||||
}
|
||||
|
||||
/* The index of this path in the module's file table, adding it if it is new.
|
||||
Traps carry the index rather than the string so the generator emits each
|
||||
name once. */
|
||||
unsigned fe_ir_file(FeIrModule *m, const char *path)
|
||||
{
|
||||
unsigned i;
|
||||
if (!path) path = "";
|
||||
for (i = 0; i < m->file_count; ++i)
|
||||
if (!strcmp(m->files[i], path)) return i;
|
||||
if (m->file_count >= FE_IR_FILE_MAX) return 0;
|
||||
m->files[m->file_count] = path;
|
||||
return m->file_count++;
|
||||
}
|
||||
|
||||
void fe_ir_module_destroy(FeIrModule *m)
|
||||
{
|
||||
fe_arena_destroy(&m->arena);
|
||||
@@ -301,13 +315,15 @@ void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value)
|
||||
b->has_ret_value = has_value;
|
||||
}
|
||||
|
||||
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line)
|
||||
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line,
|
||||
unsigned file)
|
||||
{
|
||||
if (b->terminated) return;
|
||||
b->terminated = 1;
|
||||
b->term = FE_IR_TRAP;
|
||||
b->trap = reason;
|
||||
b->trap_line = line;
|
||||
b->trap_file = file;
|
||||
}
|
||||
|
||||
const char *fe_ir_type_name(FeIrType t)
|
||||
@@ -432,8 +448,8 @@ void fe_ir_dump(const FeIrModule *m, FILE *out)
|
||||
const FeIrValue *v;
|
||||
const FeIrGlobal *g;
|
||||
unsigned i;
|
||||
if (m->unit_file && m->unit_file[0])
|
||||
fprintf(out, "; unit file %s\n", m->unit_file);
|
||||
for (i = 0; i < m->file_count; ++i)
|
||||
fprintf(out, "; file %u %s\n", i, m->files[i]);
|
||||
for (g = m->globals; g; g = g->next)
|
||||
fprintf(out, "global @%s : %s %lu\n", g->name,
|
||||
fe_ir_type_name(g->type), g->size);
|
||||
|
||||
+11
-2
@@ -4,6 +4,9 @@
|
||||
#include "arena.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/* How many source files one build can trap from. */
|
||||
#define FE_IR_FILE_MAX 64
|
||||
|
||||
/* The intermediate representation. `IR.md` is the description; this is the
|
||||
shape it takes in memory.
|
||||
|
||||
@@ -88,6 +91,7 @@ typedef struct FeIrBlock {
|
||||
int has_ret_value;
|
||||
FeIrTrap trap;
|
||||
unsigned long trap_line;
|
||||
unsigned trap_file; /* index into the module's file table */
|
||||
/* Set once a terminator is chosen. Lowering asks before appending a
|
||||
jump, so a `return` inside a branch is not overwritten by the jump
|
||||
to the join block. */
|
||||
@@ -142,7 +146,10 @@ typedef struct FeIrGlobal {
|
||||
|
||||
typedef struct FeIrModule {
|
||||
FeArena arena;
|
||||
const char *unit_file; /* the one file-name string a unit's traps share */
|
||||
/* Every unit in the build lands in one module, so a trap has to say which
|
||||
file it came from rather than share one name with the whole program. */
|
||||
const char *files[FE_IR_FILE_MAX];
|
||||
unsigned file_count;
|
||||
/* The entry unit's `main`, if it has one. The runtime's start stub
|
||||
calls a fixed name, so the generator emits a jump to this one. */
|
||||
const char *entry_main;
|
||||
@@ -152,6 +159,7 @@ typedef struct FeIrModule {
|
||||
FeIrGlobal *last_global;
|
||||
} FeIrModule;
|
||||
|
||||
unsigned fe_ir_file(FeIrModule *m, const char *path);
|
||||
void fe_ir_module_init(FeIrModule *m);
|
||||
void fe_ir_module_destroy(FeIrModule *m);
|
||||
|
||||
@@ -198,7 +206,8 @@ void fe_ir_copy(FeIrModule *m, FeIrBlock *b, FeIrPlace dst, FeIrPlace src,
|
||||
void fe_ir_jmp(FeIrBlock *b, unsigned target);
|
||||
void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f);
|
||||
void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value);
|
||||
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line);
|
||||
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line,
|
||||
unsigned file);
|
||||
|
||||
void fe_ir_dump(const FeIrModule *m, FILE *out);
|
||||
const char *fe_ir_type_name(FeIrType t);
|
||||
|
||||
+10
-3
@@ -199,6 +199,13 @@ FeIrBlock *new_block(Lower *L)
|
||||
return fe_ir_block(L->m, L->fn);
|
||||
}
|
||||
|
||||
/* Which file a trap raised right now came from. The whole build lowers into
|
||||
one module, so the unit being lowered is the only thing that knows. */
|
||||
unsigned trap_file(Lower *L)
|
||||
{
|
||||
return fe_ir_file(L->m, L->c->unit ? L->c->unit->path : "");
|
||||
}
|
||||
|
||||
/* A check that must hold. `ok` is a condition; when it is false the program
|
||||
stops where it is. `--no-checks` removes the comparison and the branch, not
|
||||
just the message, which is the whole point of the flag. */
|
||||
@@ -208,7 +215,7 @@ void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line)
|
||||
FeIrBlock *cont = new_block(L);
|
||||
fe_ir_br(L->b, ok, cont->id, bad->id);
|
||||
L->b = bad;
|
||||
fe_ir_trap(L->b, reason, line);
|
||||
fe_ir_trap(L->b, reason, line, trap_file(L));
|
||||
L->b = cont;
|
||||
}
|
||||
|
||||
@@ -403,13 +410,13 @@ int lower_builtin(Lower *L, FeNode *n, Slot *out)
|
||||
const char *name = n->text;
|
||||
if (!name || name[0] != '@') return 0;
|
||||
if (!strcmp(name, "@trap")) {
|
||||
fe_ir_trap(L->b, FE_TRAP_EXPLICIT, n->loc.line);
|
||||
fe_ir_trap(L->b, FE_TRAP_EXPLICIT, n->loc.line, trap_file(L));
|
||||
L->b = new_block(L);
|
||||
*out = slot_void();
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(name, "@unreachable")) {
|
||||
fe_ir_trap(L->b, FE_TRAP_UNREACHABLE, n->loc.line);
|
||||
fe_ir_trap(L->b, FE_TRAP_UNREACHABLE, n->loc.line, trap_file(L));
|
||||
L->b = new_block(L);
|
||||
*out = slot_void();
|
||||
return 1;
|
||||
|
||||
@@ -121,6 +121,7 @@ unsigned declare_var(Lower *L, const char *cname, const FeType *t,
|
||||
int release_flag(Lower *L, unsigned local, unsigned *flag);
|
||||
LowerVar *find_var(Lower *L, const char *cname);
|
||||
FeIrBlock *new_block(Lower *L);
|
||||
unsigned trap_file(Lower *L);
|
||||
void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line);
|
||||
FeIrType tag_type(const FeType *t);
|
||||
int uses_niche(const FeType *t);
|
||||
|
||||
@@ -631,7 +631,6 @@ int fe_lower_program(FeCheck *c, FeIrModule *out)
|
||||
c->ast = &unit->ast;
|
||||
c->unit = unit;
|
||||
c->types.unit_name = unit->name[0] ? unit->name : "unit";
|
||||
if (!out->unit_file || !out->unit_file[0]) out->unit_file = unit->path;
|
||||
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next)
|
||||
if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST)
|
||||
lower_global(&L, n);
|
||||
|
||||
+9
-4
@@ -548,7 +548,8 @@ static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out)
|
||||
break;
|
||||
case FE_IR_TRAP:
|
||||
fprintf(out, " push %lu\n", b->trap_line);
|
||||
fprintf(out, " push offset FE_UNIT_FILE\n");
|
||||
fprintf(out, " push offset FE_FILE_%u\n",
|
||||
b->trap_file);
|
||||
fprintf(out, " push %u\n", (unsigned)b->trap);
|
||||
fprintf(out, " call fe_trap\n");
|
||||
fprintf(out, " add esp, 12\n");
|
||||
@@ -587,6 +588,7 @@ void fe_x86_emit(const FeIrModule *m, FILE *out)
|
||||
const FeIrGlobal *g;
|
||||
int any_trap = 0;
|
||||
const FeIrBlock *b;
|
||||
unsigned i;
|
||||
|
||||
for (f = m->funcs; f && !any_trap; f = f->next)
|
||||
for (b = f->first; b; b = b->next)
|
||||
@@ -623,9 +625,12 @@ void fe_x86_emit(const FeIrModule *m, FILE *out)
|
||||
if (any_trap) fputs("extern fe_trap : near\n", out);
|
||||
|
||||
fputs("\n_DATA segment dword public 'DATA'\n", out);
|
||||
if (any_trap) {
|
||||
fputs("public FE_UNIT_FILE\nFE_UNIT_FILE label byte\n", out);
|
||||
emit_string(m->unit_file, out);
|
||||
/* One name per file a trap can come from. A build is many units in
|
||||
one module, and a trap that names the wrong file is worse than
|
||||
one that names none. */
|
||||
for (i = 0; i < m->file_count; ++i) {
|
||||
fprintf(out, "public FE_FILE_%u\nFE_FILE_%u label byte\n", i, i);
|
||||
emit_string(m->files[i], out);
|
||||
}
|
||||
for (g = m->globals; g; g = g->next) {
|
||||
unsigned long i;
|
||||
|
||||
@@ -23,10 +23,8 @@ pub struct Parser {
|
||||
pub errors: usize,
|
||||
|
||||
pub fn on(src: []u8) -> !Self {
|
||||
let nodes: list.List(ast.Node) =
|
||||
try list.List(ast.Node).with_capacity(16);
|
||||
var p: Self = Self{
|
||||
nodes: nodes,
|
||||
nodes: try list.List(ast.Node).with_capacity(16),
|
||||
at: 0,
|
||||
line: 1,
|
||||
cur: tok.Token{ kind: tok.Kind.End, from: 0, len: 0, line: 1 },
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// EXIT:3
|
||||
// OUTPUT:index out of bounds
|
||||
// OUTPUT:pick.fe:6
|
||||
// NOCHECKS:0
|
||||
unit main;
|
||||
|
||||
import pick;
|
||||
|
||||
// A build is many units in one module. A trap has to name the file the check
|
||||
// was written in, not the file the program was started from.
|
||||
|
||||
const S: str = "abc";
|
||||
|
||||
fn main() -> i32 {
|
||||
let c: u8 = pick.at(S, 9);
|
||||
// Without the checks the read runs off the end and `c` is whatever
|
||||
// was there, so nothing is decided by its value.
|
||||
if c == 0 { return 0; }
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
unit pick;
|
||||
|
||||
// The failing index is here, two units away from the program that runs it.
|
||||
|
||||
pub fn at(s: []u8, i: usize) -> u8 {
|
||||
return s[i];
|
||||
}
|
||||
Reference in New Issue
Block a user