From 351e5dbb231eb96f7e8899f14bc1649d1281f997 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 18:27:26 +0900 Subject: [PATCH] feat: replace M4 callback writers with safe handles --- fec/src/check.c | 21 ++++++--------------- fec/src/emit_c.c | 33 ++++++++++++--------------------- fec/std/fmt.fe | 8 +++++--- fec/std/io.fe | 11 ++++++----- fec/test-dos.bat | 2 ++ fec/tests/m4/bad-bufw.fe | 7 +++++++ fec/tests/m4/bad-writ.fe | 2 +- fec/tests/m4/format.fe | 14 +++++++------- fec/tests/m4/prop.fe | 2 +- fec/tests/m4/proptest.c | 20 ++++---------------- fec/tests/m4/try-fpr.fe | 6 +++--- 11 files changed, 54 insertions(+), 72 deletions(-) create mode 100644 fec/tests/m4/bad-bufw.fe diff --git a/fec/src/check.c b/fec/src/check.c index 4bda576..711159d 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -375,9 +375,8 @@ static void check_format_call(FeCheckerState *s, FeNode *n) if (offset) { if (!fmt_node) { err(s->c,n->loc,"@fprint requires a writer"); return; } t=check_expr(s,fmt_node); - if (!(t && t->kind==FE_TYPE_REF && t->ref_mut && - format_is_writer_type(t->elem))) - err(s->c,fmt_node->loc,"@fprint requires &mut io.Writer"); + if (!format_is_writer_type(t)) + err(s->c,fmt_node->loc,"@fprint requires io.Writer"); fmt_node=fmt_node->next; } if (strcmp(n->text,"@sprint")==0) { @@ -679,18 +678,9 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) if (n->a && n->a->kind==FE_N_MEMBER && n->a->a && n->a->a->kind==FE_N_IDENT && n->a->a->text && strcmp(n->a->a->text,"io")==0 && n->a->b && n->a->b->text && - (strcmp(n->a->b->text,"buf_writer")==0 || - strcmp(n->a->b->text,"null_writer")==0)) { + strcmp(n->a->b->text,"null_writer")==0) { FeNode *arg=n->children; - if (strcmp(n->a->b->text,"buf_writer")==0) { - if (!arg) err(c,n->loc,"io.buf_writer requires a buffer"); - else { - a=check_expr(s,arg); - if (!(a && a->kind==FE_TYPE_REF && a->ref_mut && - format_is_slice_u8(a->elem))) - err(c,arg->loc,"io.buf_writer requires &mut []u8 buffer"); - } - } else if (arg) err(c,n->loc,"io.null_writer takes no arguments"); + if (arg) err(c,n->loc,"io.null_writer takes no arguments"); n->sem_type=fe_type_intern(&c->types,"io.Writer"); return n->sem_type; } @@ -760,7 +750,8 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) if (n->kind == FE_N_MEMBER) { if (n->a && n->a->kind==FE_N_IDENT && n->a->text && strcmp(n->a->text,"io")==0 && n->b && n->b->text && - strcmp(n->b->text,"stdout")==0) { + (strcmp(n->b->text,"stdout")==0 || + strcmp(n->b->text,"stderr")==0)) { n->sem_type=fe_type_intern(&c->types,"io.Writer"); return n->sem_type; } diff --git a/fec/src/emit_c.c b/fec/src/emit_c.c index f445afc..0df0575 100644 --- a/fec/src/emit_c.c +++ b/fec/src/emit_c.c @@ -251,22 +251,20 @@ static void emit_type_helpers(FeEmitter *e) static void emit_m4_runtime(FeEmitter *e) { fputs("typedef struct { unsigned char *p; unsigned long n; } fe_m4_slice;\n",e->out); - fputs("typedef struct { void *ctx; unsigned short (*write_fn)(void *, const unsigned char *, unsigned long); } fe_writer;\n",e->out); + fputs("typedef struct { unsigned char tag; unsigned short handle; } fe_writer;\n",e->out); fputs("unsigned short fe_m4_error;\n",e->out); - fputs("unsigned short fe_m4_stdout_write(void *ctx, const unsigned char *p, unsigned long n) { (void)ctx; return fwrite(p,1,(size_t)n,stdout)==(size_t)n ? 0 : 1; }\n",e->out); - fputs("unsigned short fe_m4_null_write(void *ctx, const unsigned char *p, unsigned long n) { (void)ctx; (void)p; (void)n; return 0; }\n",e->out); - fputs("unsigned short fe_m4_buf_write(void *ctx, const unsigned char *p, unsigned long n) { fe_m4_slice *b=(fe_m4_slice*)ctx; unsigned long k=nn?n:b->n; if(k) memcpy(b->p,p,(size_t)k); b->p+=k; b->n-=k; return 0; }\n",e->out); - fputs("fe_writer fe_m4_stdout_writer(void) { fe_writer w; w.ctx=0; w.write_fn=fe_m4_stdout_write; return w; }\n",e->out); - fputs("fe_writer fe_m4_null_writer(void) { fe_writer w; w.ctx=0; w.write_fn=fe_m4_null_write; return w; }\n",e->out); - fputs("fe_writer fe_m4_buf_writer(fe_m4_slice *b) { fe_writer w; w.ctx=b; w.write_fn=fe_m4_buf_write; return w; }\n",e->out); + fputs("fe_writer fe_m4_writer(unsigned char tag, unsigned short handle) { fe_writer w; w.tag=tag; w.handle=handle; return w; }\n",e->out); + fputs("fe_writer fe_m4_stdout_writer(void) { return fe_m4_writer(0,1); }\n",e->out); + fputs("fe_writer fe_m4_stderr_writer(void) { return fe_m4_writer(1,2); }\n",e->out); + fputs("fe_writer fe_m4_null_writer(void) { return fe_m4_writer(3,0); }\n",e->out); fputs("/* bounded sprint stack; overflow traps instead of corrupting an outer call */\n#define FE_M4_SPRINT_DEPTH 8\n",e->out); fputs("typedef struct { fe_m4_slice b; unsigned long start_n; } fe_m4_sprint_frame;\n",e->out); fputs("static fe_m4_sprint_frame fe_m4_sprint_stack[FE_M4_SPRINT_DEPTH];\n",e->out); fputs("static unsigned fe_m4_sprint_depth;\n",e->out); fputs("void fe_m4_sprint_begin(fe_m4_slice *b) { if (fe_m4_sprint_depth>=FE_M4_SPRINT_DEPTH) abort(); fe_m4_sprint_stack[fe_m4_sprint_depth].b=*b; fe_m4_sprint_stack[fe_m4_sprint_depth].start_n=b->n; ++fe_m4_sprint_depth; }\n",e->out); - fputs("fe_writer fe_m4_sprint_writer(void) { return fe_m4_buf_writer(&fe_m4_sprint_stack[fe_m4_sprint_depth-1].b); }\n",e->out); + fputs("fe_writer fe_m4_sprint_writer(void) { return fe_m4_writer(4,(unsigned short)(fe_m4_sprint_depth-1)); }\n",e->out); fputs("unsigned long fe_m4_sprint_finish(void) { unsigned long result; if (!fe_m4_sprint_depth) abort(); --fe_m4_sprint_depth; result=fe_m4_sprint_stack[fe_m4_sprint_depth].start_n-fe_m4_sprint_stack[fe_m4_sprint_depth].b.n; return result; }\n",e->out); - fputs("unsigned short fe_m4_write_bytes(fe_writer w, const unsigned char *p, unsigned long n) { return w.write_fn ? w.write_fn(w.ctx,p,n) : 1; }\n",e->out); + fputs("unsigned short fe_m4_write_bytes(fe_writer w, const unsigned char *p, unsigned long n) { if(w.tag==0) return fwrite(p,1,(size_t)n,stdout)==(size_t)n?0:1; if(w.tag==1) return fwrite(p,1,(size_t)n,stderr)==(size_t)n?0:1; if(w.tag==3) return 0; if(w.tag==4 && w.handlen?n:b->n; if(k) memcpy(b->p,p,(size_t)k); b->p+=k; b->n-=k; return 0; } return 1; }\n",e->out); fputs("unsigned short fe_m4_write_cstr(fe_writer w, const char *p) { return fe_m4_write_bytes(w,(const unsigned char*)p,(unsigned long)strlen(p)); }\n",e->out); fputs("#define fe_m4_write_slice(w,s) fe_m4_write_bytes((w),(s).p,(s).n)\n",e->out); fputs("unsigned short fe_m4_write_int(fe_writer w, long v) { char b[40]; sprintf(b,\"%ld\",v); return fe_m4_write_cstr(w,b); }\n",e->out); @@ -456,13 +454,8 @@ static void emit_m4_piece(FILE *out, const char *fmt, unsigned long begin, static void emit_m4_writer(FeEmitter *e, FeNode *arg, int buffer) { - if (buffer) { - fputs("fe_m4_buf_writer((fe_m4_slice*)&",e->out); - if (arg && arg->kind==FE_N_UNARY && arg->text && - (strcmp(arg->text,"&")==0 || strcmp(arg->text,"&mut")==0)) emit_expr(e,arg->a); - else emit_expr(e,arg); - fputs(")",e->out); - } else if (arg && arg->kind==FE_N_UNARY && arg->text && + (void)buffer; + if (arg && arg->kind==FE_N_UNARY && arg->text && (strcmp(arg->text,"&")==0 || strcmp(arg->text,"&mut")==0)) { emit_expr(e,arg->a); } else if (arg && arg->kind==FE_N_CALL && arg->a && @@ -817,10 +810,6 @@ static void emit_expr(FeEmitter *e, FeNode *n) fputs("()",e->out); special=1; } - else if(n->a && n->a->kind==FE_N_MEMBER && n->a->a && - n->a->a->kind==FE_N_IDENT && n->a->a->text && - strcmp(n->a->a->text,"io")==0 && n->a->b && n->a->b->text && - strcmp(n->a->b->text,"buf_writer")==0 && n->children) { emit_m4_writer(e,n->children,1); special=1; } else if(n->a && n->a->kind==FE_N_MEMBER && n->a->a && n->a->a->kind==FE_N_IDENT && n->a->a->text && strcmp(n->a->a->text,"io")==0 && n->a->b && n->a->b->text && @@ -861,7 +850,9 @@ static void emit_expr(FeEmitter *e, FeNode *n) FeVariantType *v; if(n->a && n->a->kind==FE_N_IDENT && n->a->text && strcmp(n->a->text,"io")==0 && n->b && n->b->text && - strcmp(n->b->text,"stdout")==0) fputs("fe_m4_stdout_writer()",e->out); + (strcmp(n->b->text,"stdout")==0 || strcmp(n->b->text,"stderr")==0)) + fputs(strcmp(n->b->text,"stderr")==0 ? + "fe_m4_stderr_writer()" : "fe_m4_stdout_writer()",e->out); else if(n->a && n->a->sem_type && n->a->sem_type->kind==FE_TYPE_REF && n->b && n->b->text && strcmp(n->b->text,"^")==0) { fputs("(*",e->out); emit_expr(e,n->a); fputs(")",e->out); diff --git a/fec/std/fmt.fe b/fec/std/fmt.fe index 460cc0b..e63bc0b 100644 --- a/fec/std/fmt.fe +++ b/fec/std/fmt.fe @@ -1,4 +1,6 @@ unit fmt; -pub fn write_str(w: &mut io.Writer, s: str) -> !void; -pub fn write_int_i32(w: &mut io.Writer, v: i32) -> !void; -pub fn write_bool(w: &mut io.Writer, v: bool) -> !void; +pub fn fmt_int_i32(buf: []mut u8, v: i32) -> str; +pub fn fmt_hex_i32(buf: []mut u8, v: i32) -> str; +pub fn fmt_char(buf: []mut u8, v: char) -> str; +pub fn fmt_bool(buf: []mut u8, v: bool) -> str; +pub fn fmt_error(buf: []mut u8, v: core.Error) -> str; diff --git a/fec/std/io.fe b/fec/std/io.fe index 2f4f983..d7cc685 100644 --- a/fec/std/io.fe +++ b/fec/std/io.fe @@ -1,9 +1,10 @@ unit io; -pub struct Writer { - ctx: *void, - write_fn: fn(*void, []u8) -> !usize, -} +pub enum Writer { Stdout, Stderr, File(u16), Null } +pub enum Reader { Stdin, File(u16) } +pub fn write(w: Writer, bytes: []u8) -> !usize; +pub fn read(r: Reader, bytes: []mut u8) -> !usize; pub struct File { handle: u16, - pub fn close(self: &mut Self) { } + pub fn close(self: Self) -> !void; + pub fn drop(self: &mut Self) { } } diff --git a/fec/test-dos.bat b/fec/test-dos.bat index be85d12..60acb3e 100644 --- a/fec/test-dos.bat +++ b/fec/test-dos.bat @@ -237,6 +237,8 @@ fec.exe --target=bits32 --emit-c TESTS\M4\BAD-TRY.FE -o TESTS\M4\BAD-TRY.C > nul if not errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M4\BAD-WRIT.FE -o TESTS\M4\BAD-WRI.C > nul if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-BUFW.FE -o TESTS\M4\BAD-BUFW.C > nul +if not errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M4\BAD-MANY.FE -o TESTS\M4\BAD-MANY.C > nul if not errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M4\BAD-OPEN.FE -o TESTS\M4\BAD-OPEN.C > nul diff --git a/fec/tests/m4/bad-bufw.fe b/fec/tests/m4/bad-bufw.fe new file mode 100644 index 0000000..791bd7d --- /dev/null +++ b/fec/tests/m4/bad-bufw.fe @@ -0,0 +1,7 @@ +unit m4_bad_buffer_writer; + +fn main() -> void { + var raw: [4]u8 = [0, 0, 0, 0]; + var buf: []mut u8 = raw[..]; + let w = io.buf_writer(buf); +} diff --git a/fec/tests/m4/bad-writ.fe b/fec/tests/m4/bad-writ.fe index 8dac8c4..794ed64 100644 --- a/fec/tests/m4/bad-writ.fe +++ b/fec/tests/m4/bad-writ.fe @@ -2,6 +2,6 @@ unit m4_bad_writer; fn main() -> i32 { var x: i32 = 0; - @fprint(&mut x, "bad"); + @fprint(x, "bad"); return 0; } diff --git a/fec/tests/m4/format.fe b/fec/tests/m4/format.fe index e3582fd..2445625 100644 --- a/fec/tests/m4/format.fe +++ b/fec/tests/m4/format.fe @@ -8,15 +8,15 @@ fn main() -> i32 { var raw3: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; var raw4: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; var raw5: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; - var buf: []u8 = raw[..]; - var buf2: []u8 = raw2[..]; - var buf3: []u8 = raw3[..]; - var buf4: []u8 = raw4[..]; - var buf5: []u8 = raw5[..]; - let w: io.Writer = io.buf_writer(&mut buf); + var buf: []mut u8 = raw[..]; + var buf2: []mut u8 = raw2[..]; + var buf3: []mut u8 = raw3[..]; + var buf4: []mut u8 = raw4[..]; + var buf5: []mut u8 = raw5[..]; + let w: io.Writer = io.null_writer(); const LOCAL_FMT: str = "value={}\n"; @print(FMT, 7, 15, 'A', "yes", true); - @fprint(&mut w, LOCAL_FMT, 12); + @fprint(w, LOCAL_FMT, 12); let n: usize = @sprint(buf2, "A\x42\u0043defghi"); let n2: usize = @sprint(buf3, "xy"); let inner_n: usize = @sprint(buf4, "xy"); diff --git a/fec/tests/m4/prop.fe b/fec/tests/m4/prop.fe index 81a85f4..878a77a 100644 --- a/fec/tests/m4/prop.fe +++ b/fec/tests/m4/prop.fe @@ -1,5 +1,5 @@ unit m4_prop; -pub fn propagate(w: &mut io.Writer) -> !void { +pub fn propagate(w: io.Writer) -> !void { try @fprint(w, "a{}b", 1); } diff --git a/fec/tests/m4/proptest.c b/fec/tests/m4/proptest.c index 7261461..66bba1a 100644 --- a/fec/tests/m4/proptest.c +++ b/fec/tests/m4/proptest.c @@ -1,23 +1,11 @@ #include "prop.c" -static unsigned short calls; - -static unsigned short fail_write(void *ctx, const unsigned char *p, - unsigned long n) -{ - (void)ctx; - (void)p; - (void)n; - ++calls; - return calls == 1 ? 7 : 0; -} - int main(void) { fe_writer w; unsigned short result; - w.ctx=0; - w.write_fn=fail_write; - result=fe_m4_prop_propagate(&w); - return (result==7 && calls==1) ? 0 : 1; + w.tag=2; + w.handle=99; + result=fe_m4_prop_propagate(w); + return result==1 ? 0 : 1; } diff --git a/fec/tests/m4/try-fpr.fe b/fec/tests/m4/try-fpr.fe index e6c419c..94736cc 100644 --- a/fec/tests/m4/try-fpr.fe +++ b/fec/tests/m4/try-fpr.fe @@ -2,7 +2,7 @@ unit m4_try_fprint; fn main() -> !void { var raw: [4]u8 = [0, 0, 0, 0]; - var buf: []u8 = raw[..]; - let w: io.Writer = io.buf_writer(&mut buf); - try @fprint(&mut w, "ok"); + var buf: []mut u8 = raw[..]; + let w: io.Writer = io.null_writer(); + try @fprint(w, "ok"); }