트랩은 검사가 쓰인 파일을 댄다
빌드 전체가 한 모듈이라 파일 이름도 하나였다. 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)
|
void fe_ir_module_init(FeIrModule *m)
|
||||||
{
|
{
|
||||||
fe_arena_init(&m->arena, 16384);
|
fe_arena_init(&m->arena, 16384);
|
||||||
m->unit_file = "";
|
m->file_count = 0;
|
||||||
m->entry_main = 0;
|
m->entry_main = 0;
|
||||||
m->funcs = 0;
|
m->funcs = 0;
|
||||||
m->last_func = 0;
|
m->last_func = 0;
|
||||||
@@ -13,6 +13,20 @@ void fe_ir_module_init(FeIrModule *m)
|
|||||||
m->last_global = 0;
|
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)
|
void fe_ir_module_destroy(FeIrModule *m)
|
||||||
{
|
{
|
||||||
fe_arena_destroy(&m->arena);
|
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;
|
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;
|
if (b->terminated) return;
|
||||||
b->terminated = 1;
|
b->terminated = 1;
|
||||||
b->term = FE_IR_TRAP;
|
b->term = FE_IR_TRAP;
|
||||||
b->trap = reason;
|
b->trap = reason;
|
||||||
b->trap_line = line;
|
b->trap_line = line;
|
||||||
|
b->trap_file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *fe_ir_type_name(FeIrType t)
|
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 FeIrValue *v;
|
||||||
const FeIrGlobal *g;
|
const FeIrGlobal *g;
|
||||||
unsigned i;
|
unsigned i;
|
||||||
if (m->unit_file && m->unit_file[0])
|
for (i = 0; i < m->file_count; ++i)
|
||||||
fprintf(out, "; unit file %s\n", m->unit_file);
|
fprintf(out, "; file %u %s\n", i, m->files[i]);
|
||||||
for (g = m->globals; g; g = g->next)
|
for (g = m->globals; g; g = g->next)
|
||||||
fprintf(out, "global @%s : %s %lu\n", g->name,
|
fprintf(out, "global @%s : %s %lu\n", g->name,
|
||||||
fe_ir_type_name(g->type), g->size);
|
fe_ir_type_name(g->type), g->size);
|
||||||
|
|||||||
+11
-2
@@ -4,6 +4,9 @@
|
|||||||
#include "arena.h"
|
#include "arena.h"
|
||||||
#include <stdio.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
|
/* The intermediate representation. `IR.md` is the description; this is the
|
||||||
shape it takes in memory.
|
shape it takes in memory.
|
||||||
|
|
||||||
@@ -88,6 +91,7 @@ typedef struct FeIrBlock {
|
|||||||
int has_ret_value;
|
int has_ret_value;
|
||||||
FeIrTrap trap;
|
FeIrTrap trap;
|
||||||
unsigned long trap_line;
|
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
|
/* Set once a terminator is chosen. Lowering asks before appending a
|
||||||
jump, so a `return` inside a branch is not overwritten by the jump
|
jump, so a `return` inside a branch is not overwritten by the jump
|
||||||
to the join block. */
|
to the join block. */
|
||||||
@@ -142,7 +146,10 @@ typedef struct FeIrGlobal {
|
|||||||
|
|
||||||
typedef struct FeIrModule {
|
typedef struct FeIrModule {
|
||||||
FeArena arena;
|
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
|
/* 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. */
|
calls a fixed name, so the generator emits a jump to this one. */
|
||||||
const char *entry_main;
|
const char *entry_main;
|
||||||
@@ -152,6 +159,7 @@ typedef struct FeIrModule {
|
|||||||
FeIrGlobal *last_global;
|
FeIrGlobal *last_global;
|
||||||
} FeIrModule;
|
} FeIrModule;
|
||||||
|
|
||||||
|
unsigned fe_ir_file(FeIrModule *m, const char *path);
|
||||||
void fe_ir_module_init(FeIrModule *m);
|
void fe_ir_module_init(FeIrModule *m);
|
||||||
void fe_ir_module_destroy(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_jmp(FeIrBlock *b, unsigned target);
|
||||||
void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f);
|
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_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);
|
void fe_ir_dump(const FeIrModule *m, FILE *out);
|
||||||
const char *fe_ir_type_name(FeIrType t);
|
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);
|
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
|
/* 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
|
stops where it is. `--no-checks` removes the comparison and the branch, not
|
||||||
just the message, which is the whole point of the flag. */
|
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);
|
FeIrBlock *cont = new_block(L);
|
||||||
fe_ir_br(L->b, ok, cont->id, bad->id);
|
fe_ir_br(L->b, ok, cont->id, bad->id);
|
||||||
L->b = bad;
|
L->b = bad;
|
||||||
fe_ir_trap(L->b, reason, line);
|
fe_ir_trap(L->b, reason, line, trap_file(L));
|
||||||
L->b = cont;
|
L->b = cont;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,13 +410,13 @@ int lower_builtin(Lower *L, FeNode *n, Slot *out)
|
|||||||
const char *name = n->text;
|
const char *name = n->text;
|
||||||
if (!name || name[0] != '@') return 0;
|
if (!name || name[0] != '@') return 0;
|
||||||
if (!strcmp(name, "@trap")) {
|
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);
|
L->b = new_block(L);
|
||||||
*out = slot_void();
|
*out = slot_void();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (!strcmp(name, "@unreachable")) {
|
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);
|
L->b = new_block(L);
|
||||||
*out = slot_void();
|
*out = slot_void();
|
||||||
return 1;
|
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);
|
int release_flag(Lower *L, unsigned local, unsigned *flag);
|
||||||
LowerVar *find_var(Lower *L, const char *cname);
|
LowerVar *find_var(Lower *L, const char *cname);
|
||||||
FeIrBlock *new_block(Lower *L);
|
FeIrBlock *new_block(Lower *L);
|
||||||
|
unsigned trap_file(Lower *L);
|
||||||
void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line);
|
void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line);
|
||||||
FeIrType tag_type(const FeType *t);
|
FeIrType tag_type(const FeType *t);
|
||||||
int uses_niche(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->ast = &unit->ast;
|
||||||
c->unit = unit;
|
c->unit = unit;
|
||||||
c->types.unit_name = unit->name[0] ? unit->name : "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)
|
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)
|
if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST)
|
||||||
lower_global(&L, n);
|
lower_global(&L, n);
|
||||||
|
|||||||
+9
-4
@@ -548,7 +548,8 @@ static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out)
|
|||||||
break;
|
break;
|
||||||
case FE_IR_TRAP:
|
case FE_IR_TRAP:
|
||||||
fprintf(out, " push %lu\n", b->trap_line);
|
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, " push %u\n", (unsigned)b->trap);
|
||||||
fprintf(out, " call fe_trap\n");
|
fprintf(out, " call fe_trap\n");
|
||||||
fprintf(out, " add esp, 12\n");
|
fprintf(out, " add esp, 12\n");
|
||||||
@@ -587,6 +588,7 @@ void fe_x86_emit(const FeIrModule *m, FILE *out)
|
|||||||
const FeIrGlobal *g;
|
const FeIrGlobal *g;
|
||||||
int any_trap = 0;
|
int any_trap = 0;
|
||||||
const FeIrBlock *b;
|
const FeIrBlock *b;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
for (f = m->funcs; f && !any_trap; f = f->next)
|
for (f = m->funcs; f && !any_trap; f = f->next)
|
||||||
for (b = f->first; b; b = b->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);
|
if (any_trap) fputs("extern fe_trap : near\n", out);
|
||||||
|
|
||||||
fputs("\n_DATA segment dword public 'DATA'\n", out);
|
fputs("\n_DATA segment dword public 'DATA'\n", out);
|
||||||
if (any_trap) {
|
/* One name per file a trap can come from. A build is many units in
|
||||||
fputs("public FE_UNIT_FILE\nFE_UNIT_FILE label byte\n", out);
|
one module, and a trap that names the wrong file is worse than
|
||||||
emit_string(m->unit_file, out);
|
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) {
|
for (g = m->globals; g; g = g->next) {
|
||||||
unsigned long i;
|
unsigned long i;
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ pub struct Parser {
|
|||||||
pub errors: usize,
|
pub errors: usize,
|
||||||
|
|
||||||
pub fn on(src: []u8) -> !Self {
|
pub fn on(src: []u8) -> !Self {
|
||||||
let nodes: list.List(ast.Node) =
|
|
||||||
try list.List(ast.Node).with_capacity(16);
|
|
||||||
var p: Self = Self{
|
var p: Self = Self{
|
||||||
nodes: nodes,
|
nodes: try list.List(ast.Node).with_capacity(16),
|
||||||
at: 0,
|
at: 0,
|
||||||
line: 1,
|
line: 1,
|
||||||
cur: tok.Token{ kind: tok.Kind.End, from: 0, len: 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