트랩은 검사가 쓰인 파일을 댄다

빌드 전체가 한 모듈이라 파일 이름도 하나였다. 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:
2026-08-17 12:56:34 +09:00
parent abdb049d05
commit 63ad48cd8a
9 changed files with 79 additions and 17 deletions
+20 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1
View File
@@ -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);
-1
View File
@@ -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
View File
@@ -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;
+1 -3
View File
@@ -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 },
+20
View File
@@ -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;
}
+7
View File
@@ -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];
}