feat: replace M4 callback writers with safe handles

This commit is contained in:
2026-08-16 18:27:26 +09:00
parent 42abc0d7e7
commit 351e5dbb23
11 changed files with 54 additions and 72 deletions
+6 -15
View File
@@ -375,9 +375,8 @@ static void check_format_call(FeCheckerState *s, FeNode *n)
if (offset) { if (offset) {
if (!fmt_node) { err(s->c,n->loc,"@fprint requires a writer"); return; } if (!fmt_node) { err(s->c,n->loc,"@fprint requires a writer"); return; }
t=check_expr(s,fmt_node); t=check_expr(s,fmt_node);
if (!(t && t->kind==FE_TYPE_REF && t->ref_mut && if (!format_is_writer_type(t))
format_is_writer_type(t->elem))) err(s->c,fmt_node->loc,"@fprint requires io.Writer");
err(s->c,fmt_node->loc,"@fprint requires &mut io.Writer");
fmt_node=fmt_node->next; fmt_node=fmt_node->next;
} }
if (strcmp(n->text,"@sprint")==0) { 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 && if (n->a && n->a->kind==FE_N_MEMBER && n->a->a &&
n->a->a->kind==FE_N_IDENT && n->a->a->text && 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->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; FeNode *arg=n->children;
if (strcmp(n->a->b->text,"buf_writer")==0) { if (arg) err(c,n->loc,"io.null_writer takes no arguments");
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");
n->sem_type=fe_type_intern(&c->types,"io.Writer"); n->sem_type=fe_type_intern(&c->types,"io.Writer");
return n->sem_type; return n->sem_type;
} }
@@ -760,7 +750,8 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n)
if (n->kind == FE_N_MEMBER) { if (n->kind == FE_N_MEMBER) {
if (n->a && n->a->kind==FE_N_IDENT && n->a->text && 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->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"); n->sem_type=fe_type_intern(&c->types,"io.Writer");
return n->sem_type; return n->sem_type;
} }
+11 -20
View File
@@ -251,22 +251,20 @@ static void emit_type_helpers(FeEmitter *e)
static void emit_m4_runtime(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 { 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_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("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("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("fe_writer fe_m4_stdout_writer(void) { return fe_m4_writer(0,1); }\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=n<b->n?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_stderr_writer(void) { return fe_m4_writer(1,2); }\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) { return fe_m4_writer(3,0); }\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("/* bounded sprint stack; overflow traps instead of corrupting an outer call */\n#define FE_M4_SPRINT_DEPTH 8\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("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 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("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("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 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.handle<FE_M4_SPRINT_DEPTH) { fe_m4_slice *b=&fe_m4_sprint_stack[w.handle].b; unsigned long k=n<b->n?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("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("#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); 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) static void emit_m4_writer(FeEmitter *e, FeNode *arg, int buffer)
{ {
if (buffer) { (void)buffer;
fputs("fe_m4_buf_writer((fe_m4_slice*)&",e->out);
if (arg && arg->kind==FE_N_UNARY && arg->text && 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 &&
(strcmp(arg->text,"&")==0 || strcmp(arg->text,"&mut")==0)) { (strcmp(arg->text,"&")==0 || strcmp(arg->text,"&mut")==0)) {
emit_expr(e,arg->a); emit_expr(e,arg->a);
} else if (arg && arg->kind==FE_N_CALL && 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); fputs("()",e->out);
special=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 &&
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 && else if(n->a && n->a->kind==FE_N_MEMBER && n->a->a &&
n->a->a->kind==FE_N_IDENT && n->a->a->text && 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->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; FeVariantType *v;
if(n->a && n->a->kind==FE_N_IDENT && n->a->text && 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->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 && 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) { n->b && n->b->text && strcmp(n->b->text,"^")==0) {
fputs("(*",e->out); emit_expr(e,n->a); fputs(")",e->out); fputs("(*",e->out); emit_expr(e,n->a); fputs(")",e->out);
+5 -3
View File
@@ -1,4 +1,6 @@
unit fmt; unit fmt;
pub fn write_str(w: &mut io.Writer, s: str) -> !void; pub fn fmt_int_i32(buf: []mut u8, v: i32) -> str;
pub fn write_int_i32(w: &mut io.Writer, v: i32) -> !void; pub fn fmt_hex_i32(buf: []mut u8, v: i32) -> str;
pub fn write_bool(w: &mut io.Writer, v: bool) -> !void; 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;
+6 -5
View File
@@ -1,9 +1,10 @@
unit io; unit io;
pub struct Writer { pub enum Writer { Stdout, Stderr, File(u16), Null }
ctx: *void, pub enum Reader { Stdin, File(u16) }
write_fn: fn(*void, []u8) -> !usize, pub fn write(w: Writer, bytes: []u8) -> !usize;
} pub fn read(r: Reader, bytes: []mut u8) -> !usize;
pub struct File { pub struct File {
handle: u16, handle: u16,
pub fn close(self: &mut Self) { } pub fn close(self: Self) -> !void;
pub fn drop(self: &mut Self) { }
} }
+2
View File
@@ -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 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 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 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 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 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 fec.exe --target=bits32 --emit-c TESTS\M4\BAD-OPEN.FE -o TESTS\M4\BAD-OPEN.C > nul
+7
View File
@@ -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);
}
+1 -1
View File
@@ -2,6 +2,6 @@ unit m4_bad_writer;
fn main() -> i32 { fn main() -> i32 {
var x: i32 = 0; var x: i32 = 0;
@fprint(&mut x, "bad"); @fprint(x, "bad");
return 0; return 0;
} }
+7 -7
View File
@@ -8,15 +8,15 @@ fn main() -> i32 {
var raw3: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; 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 raw4: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0];
var raw5: [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 buf: []mut u8 = raw[..];
var buf2: []u8 = raw2[..]; var buf2: []mut u8 = raw2[..];
var buf3: []u8 = raw3[..]; var buf3: []mut u8 = raw3[..];
var buf4: []u8 = raw4[..]; var buf4: []mut u8 = raw4[..];
var buf5: []u8 = raw5[..]; var buf5: []mut u8 = raw5[..];
let w: io.Writer = io.buf_writer(&mut buf); let w: io.Writer = io.null_writer();
const LOCAL_FMT: str = "value={}\n"; const LOCAL_FMT: str = "value={}\n";
@print(FMT, 7, 15, 'A', "yes", true); @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 n: usize = @sprint(buf2, "A\x42\u0043defghi");
let n2: usize = @sprint(buf3, "xy"); let n2: usize = @sprint(buf3, "xy");
let inner_n: usize = @sprint(buf4, "xy"); let inner_n: usize = @sprint(buf4, "xy");
+1 -1
View File
@@ -1,5 +1,5 @@
unit m4_prop; unit m4_prop;
pub fn propagate(w: &mut io.Writer) -> !void { pub fn propagate(w: io.Writer) -> !void {
try @fprint(w, "a{}b", 1); try @fprint(w, "a{}b", 1);
} }
+4 -16
View File
@@ -1,23 +1,11 @@
#include "prop.c" #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) int main(void)
{ {
fe_writer w; fe_writer w;
unsigned short result; unsigned short result;
w.ctx=0; w.tag=2;
w.write_fn=fail_write; w.handle=99;
result=fe_m4_prop_propagate(&w); result=fe_m4_prop_propagate(w);
return (result==7 && calls==1) ? 0 : 1; return result==1 ? 0 : 1;
} }
+3 -3
View File
@@ -2,7 +2,7 @@ unit m4_try_fprint;
fn main() -> !void { fn main() -> !void {
var raw: [4]u8 = [0, 0, 0, 0]; var raw: [4]u8 = [0, 0, 0, 0];
var buf: []u8 = raw[..]; var buf: []mut u8 = raw[..];
let w: io.Writer = io.buf_writer(&mut buf); let w: io.Writer = io.null_writer();
try @fprint(&mut w, "ok"); try @fprint(w, "ok");
} }