diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03f1dc0a..0f296739 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,48 +104,57 @@ jobs: # sites under tests/ and vg/test/ are `return spec.run_summary(fw)`; # a new spec MUST return it too. # - # v0.627.0 (2026-09-03). SKIPS 0.624.0-0.626.0, which are known-bad - # for this repo: #1866 (in 0.624.0) made a setter's store READ the - # box's `_heap_` tracker, and a box made with - # `malloc(n) as *T` never had that tracker initialised — non-zero - # garbage there frees a garbage pointer. That is this repo's idiom - # for every escaping app state (font_picker's App, auto_hide's - # DemoState, chromed's ToggleFace, turtle's TurtleUiState), chosen - # because a closure capturing a plain local captures it BY VALUE at - # creation time while field reads through a pointer stay live. The - # font preview sets string fields through setters per glyph and - # SEGFAULTED on one click: measured 0.613.0 survives / 0.626.0 dies, - # same source, same click. Fixed upstream by e932c61c (#1873) — the - # parameter case still SETS the tracker, so #1866's leak repair - # stands, but no longer READS one it cannot trust. + # v0.643.0 (2026-09-06). The bump to this release forced a fleet-wide + # source change and root-caused an aether regression along the way; + # both are recorded here because they are inseparable from the pin. # - # v0.626.0 also shipped `math.lrint(x) -> long` at this repo's - # request: vg/ carries 30 `extern lrint(x: float) -> long`, and NO - # Aether extern can spell libm's prototype (`-> long` emits int64_t, - # `-> int` emits int, C `long` is neither) — invisible on Linux - # where int64_t IS long, a hard ERROR on macOS/iOS where it is - # long long. Migrating those 30 is follow-up work, not done here. - # (It was also our first suspect for the segfault above, and - # measurement ruled it out: migrating vg/font.ae alone still - # crashed.) - AETHER_REF: ${{ vars.AETHER_REF || 'v0.627.0' }} - # v0.283 is the first release carrying aeb's `bldr` module. - # 9c84d786 converted all 109 build nodes to aeb Shape A - # (`bldr.build() {}`, b-free); v0.282 has no `bldr` at all, so every - # node failed type-checking with `Undefined function 'bldr.build'` - # and the fan-out scheduled nothing. That took main red for four - # consecutive commits. + # aether 1307dfa8 (the first commit after v0.642.0, "Place heap + # trackers inline...") moved each hidden `int _heap_` tracker + # from a TRAILING block to INLINE (right after its string field). + # That is correct for the struct-prefix-punning bug it targets, but it + # GREW the sizeof of pure-Aether structs carrying a string field not + # in last position — a mid-struct int + its padding costs more than + # end-packing the trackers. This repo allocated ~158 structs with a + # HAND-COMPUTED byte count (`malloc(40) as *SvgNode`), each sized for + # the trailing layout. Under inline layout every one under-allocated + # and the final tracker store ran off the end of the block: ASan shows + # a 4-byte heap-buffer-overflow in SvgNode (needs 48, we malloc'd 40), + # surfacing as glibc `malloc assertion failure in sysmalloc` / + # `invalid size` / `free(): invalid pointer` across 14 vg suites. + # Bisected 0.641 GOOD / 0.642 GOOD / 0.643 BAD, culprit confirmed by + # building 1307dfa8 directly. See asks/REGRESSION-0643-inline-heap- + # tracker-grows-struct.md. # - # v0.282 was itself pinned for b1bfa5e (encode_name ae-escapes a - # dot-prefixed fan-out root, so `.all.ae` links — v0.281 predated it - # and failed on the root's entry symbol). v0.283 carries that too, - # so this is a strict move forward. + # Fix on OUR side (the durable one, independent of any aether choice): + # all 158 sites swept `malloc(N)` -> `malloc(sizeof(T))`, which is + # layout-exact and immune to future tracker-placement changes. Raw + # `malloc(8)` byte buffers (no struct cast) left alone. Full 4-platform + # matrix re-verified all-green on 0.643.0 + aeb v0.296. # - # Verified against current main before bumping: with this repo's - # existing AETHER_REF (v0.553.0, unchanged) and aeb v0.283, the whole - # fan-out builds — exit 0, 0 failed nodes. aeb v0.282 with the same - # ae fails every node. The ae pin needs no change; `bldr` is aeb's. - AEB_REF: ${{ vars.AEB_REF || 'v0.283' }} + # Two earlier pins, still relevant history: + # - v0.627.0 skipped known-bad 0.624.0-0.626.0: #1866 made a setter's + # store READ an uninitialised `_heap_` tracker (this repo's + # `malloc(n) as *T` boxes never set it) -> free of garbage; fixed by + # e932c61c (#1873). The sizeof sweep above ALSO hardens against that + # class, since the boxes are now correctly sized. + # - v0.626.0 shipped `math.lrint(x) -> long`; vg/ still carries 30 + # `extern lrint(x: float) -> long` (unspellable libm prototype: an + # ERROR on macOS/iOS where long is long long). Migrating those 30 to + # math.lrint remains follow-up, not done here. + AETHER_REF: ${{ vars.AETHER_REF || 'v0.643.0' }} + # v0.296 (2026-09-06), bumped alongside the aether v0.643.0 move and + # verified building the whole fan-out under it (exit 0, 0 failed nodes, + # full matrix green). aeb is NOT implicated in the 0.643.0 regression + # above — the failing vg suites compile via `aetherc` + `gcc` directly + # with no aeb in the loop, so the fault was purely aether codegen. + # + # History: v0.283 was the first release carrying aeb's `bldr` module + # (9c84d786 converted all build nodes to Shape A `bldr.build() {}`; + # v0.282 had no `bldr`, so every node failed with + # `Undefined function 'bldr.build'`). v0.282 itself carried b1bfa5e + # (encode_name ae-escapes a dot-prefixed fan-out root so `.all.ae` + # links). v0.296 carries both, so this is a strict move forward. + AEB_REF: ${{ vars.AEB_REF || 'v0.296' }} run: | curl -fsSL https://raw.githubusercontent.com/aether-lang-dev/aether/main/get.sh \ | PREFIX="$HOME/.local" sh diff --git a/apps/LisMusic/lis_audio.ae b/apps/LisMusic/lis_audio.ae index 8f9aa143..daeb0a17 100644 --- a/apps/LisMusic/lis_audio.ae +++ b/apps/LisMusic/lis_audio.ae @@ -38,7 +38,7 @@ struct Player { } mk_player() -> *Player { - p = malloc(48) as *Player + p = malloc(sizeof(Player)) as *Player p.title = "Lumière" p.author = "Unknown" p.voice = 0.5 diff --git a/apps/LisMusic/lis_config.ae b/apps/LisMusic/lis_config.ae index fb9e5f5e..caba6a10 100644 --- a/apps/LisMusic/lis_config.ae +++ b/apps/LisMusic/lis_config.ae @@ -49,7 +49,7 @@ struct Config { } mk_config() -> *Config { - c = malloc(80) as *Config + c = malloc(sizeof(Config)) as *Config c.player = mk_player() c.history = mk_history() c.st_title = 0 diff --git a/apps/LisMusic/lis_net.ae b/apps/LisMusic/lis_net.ae index ccf5c510..1ef1afb5 100644 --- a/apps/LisMusic/lis_net.ae +++ b/apps/LisMusic/lis_net.ae @@ -36,7 +36,7 @@ struct Song { } mk_song(title: string, artist: string) -> *Song { - s = malloc(16) as *Song + s = malloc(sizeof(Song)) as *Song s.title = title s.artist = artist return s diff --git a/apps/LisMusic/lis_store.ae b/apps/LisMusic/lis_store.ae index d8194a59..6f0d60a0 100644 --- a/apps/LisMusic/lis_store.ae +++ b/apps/LisMusic/lis_store.ae @@ -24,7 +24,7 @@ struct History { } mk_history() -> *History { - h = malloc(16) as *History + h = malloc(sizeof(History)) as *History db, err = sqlite.open("history.db") if err != "" { println("[lis_store] open failed: ${err} (history disabled)") diff --git a/apps/aevg_video/aevg_video.ae b/apps/aevg_video/aevg_video.ae index 0727a136..e7919da7 100644 --- a/apps/aevg_video/aevg_video.ae +++ b/apps/aevg_video/aevg_video.ae @@ -32,7 +32,7 @@ extern string_retain(s: string) mk_clip(path: string, fw: int, fh: int, fps: float) -> *Clip { data, len, err = read_binary(path) - c = malloc(48) as *Clip + c = malloc(sizeof(Clip)) as *Clip if string.length(err) > 0 { println("clip read failed: ${err}") c.data = ""; c.len = 0; c.fw = fw; c.fh = fh; c.frames = 0; c.fps = fps diff --git a/apps/aevg_video_png/aevg_video_png.ae b/apps/aevg_video_png/aevg_video_png.ae index 2740c940..9e8d484d 100644 --- a/apps/aevg_video_png/aevg_video_png.ae +++ b/apps/aevg_video_png/aevg_video_png.ae @@ -62,7 +62,7 @@ gen_clip() -> *Clip { } data = region.frame_finish(buf, w, h * n) string_retain(data) - c = malloc(32) as *Clip + c = malloc(sizeof(Clip)) as *Clip c.data = data c.len = total c.frames = n diff --git a/apps/analog_clock/analog_clock.ae b/apps/analog_clock/analog_clock.ae index a1cce640..5d34096c 100644 --- a/apps/analog_clock/analog_clock.ae +++ b/apps/analog_clock/analog_clock.ae @@ -44,7 +44,7 @@ const RADIUS: float = 90.0 // per-frame tick sets from now_local() — so the clock shows the actual time. struct Clock { secs: float } // seconds since local midnight extern malloc(size: int) -> ptr -mk_clock() -> *Clock { c = malloc(8) as *Clock; c.secs = now_secs(); return c } +mk_clock() -> *Clock { c = malloc(sizeof(Clock)) as *Clock; c.secs = now_secs(); return c } // Seconds since local midnight (0..86399) from the host's wall clock. now_secs() -> float { @@ -66,7 +66,7 @@ frac(v: float) -> float { return v - math.floor(v) } // cross fn boundaries as heap pointers; no float-tuple returns). struct Pt4 { x1: float y1: float x2: float y2: float } mk_pt4(x1: float, y1: float, x2: float, y2: float) -> *Pt4 { - p = malloc(32) as *Pt4 + p = malloc(sizeof(Pt4)) as *Pt4 p.x1 = x1; p.y1 = y1; p.x2 = x2; p.y2 = y2 return p } diff --git a/apps/analog_clock_png/analog_clock_png.ae b/apps/analog_clock_png/analog_clock_png.ae index 03b37446..84d9760b 100644 --- a/apps/analog_clock_png/analog_clock_png.ae +++ b/apps/analog_clock_png/analog_clock_png.ae @@ -33,7 +33,7 @@ struct Clock { secs: float } struct Pt4 { x1: float y1: float x2: float y2: float } mk4(a: float, b: float, c: float, d: float) -> *Pt4 { - p = malloc(32) as *Pt4 + p = malloc(sizeof(Pt4)) as *Pt4 p.x1 = a p.y1 = b p.x2 = c @@ -65,7 +65,7 @@ main() { out = os_getenv("AEVG_OUT") if string.length(out) == 0 { out = "/tmp/clock.png" } - clk = malloc(8) as *Clock + clk = malloc(sizeof(Clock)) as *Clock // The host's current local time (seconds since local midnight) — the same // source the windowed analog_clock ticks from. AEVG_CLOCK_SECS overrides it // for a deterministic CI/test render. diff --git a/apps/boing/boing.ae b/apps/boing/boing.ae index a042385a..25234770 100644 --- a/apps/boing/boing.ae +++ b/apps/boing/boing.ae @@ -55,7 +55,7 @@ struct Ball { } mk_ball() -> *Ball { - b = malloc(40) as *Ball + b = malloc(sizeof(Ball)) as *Ball b.x = RAD * 1.0 + 50.0 b.y = RAD * 1.0 + 50.0 b.vx = 4.0 diff --git a/apps/falling_blocks/falling_blocks.ae b/apps/falling_blocks/falling_blocks.ae index bd6c4b92..589510e8 100644 --- a/apps/falling_blocks/falling_blocks.ae +++ b/apps/falling_blocks/falling_blocks.ae @@ -50,7 +50,7 @@ struct View { status_lbl: int } mk_view() -> *View { - v = malloc(16) as *View + v = malloc(sizeof(View)) as *View v.score_lbl = 0 v.status_lbl = 0 return v diff --git a/apps/falling_blocks/fb_engine.ae b/apps/falling_blocks/fb_engine.ae index f5c61ca3..360e8533 100644 --- a/apps/falling_blocks/fb_engine.ae +++ b/apps/falling_blocks/fb_engine.ae @@ -135,7 +135,7 @@ struct Game { } mk_game() -> *Game { - g = malloc(96) as *Game + g = malloc(sizeof(Game)) as *Game arr, _e = intarr.new_filled(CELLS, -1) g.board = arr g.shapes = build_shapes() diff --git a/apps/font_picker/font_picker.ae b/apps/font_picker/font_picker.ae index 508df1a2..a1f10719 100644 --- a/apps/font_picker/font_picker.ae +++ b/apps/font_picker/font_picker.ae @@ -66,7 +66,7 @@ const FP_PREVIEW: string = "Handgloves 123" // One visible row: the family index plus the strings the row renders. struct Row { fi: int label: string cat: string fam: string fav: int } mk_row(fi: int, label: string, cat: string, fam: string, fav: int) -> ptr { - r = malloc(56) as *Row + r = malloc(sizeof(Row)) as *Row r.fi = fi r.label = label r.cat = cat @@ -90,7 +90,7 @@ struct App { pv_d: string // cached preview path data, rebuilt per selection } mk_app() -> *App { - a = malloc(80) as *App + a = malloc(sizeof(App)) as *App a.list = picker_engine.pl_new() a.st_status = 0 a.st_sel = 0 diff --git a/apps/font_picker/picker_engine.ae b/apps/font_picker/picker_engine.ae index 3ca19d8f..64a6f4ac 100644 --- a/apps/font_picker/picker_engine.ae +++ b/apps/font_picker/picker_engine.ae @@ -174,7 +174,7 @@ fam_variant_at(f: ptr, i: int) -> string { fm = f as *Fam; return csv_at(fm.vari // Parse the compressed record. `path` is our extension: the font file this // family renders from (upstream loads by CSS family name instead). fam_parse(raw: string, path: string) -> ptr { - fm = malloc(128) as *Fam + fm = malloc(sizeof(Fam)) as *Fam nm = field_at(raw, 0) ca = field_at(raw, 1) va = field_at(raw, 2) @@ -358,7 +358,7 @@ struct PickList { } pl_new() -> ptr { - l = malloc(160) as *PickList + l = malloc(sizeof(PickList)) as *PickList l.fams = list.list_new() vis, _ve = floatarr.new(FP_MAX) fav, _fe = floatarr.new(FP_MAX) diff --git a/apps/frames_demo/frames_demo.ae b/apps/frames_demo/frames_demo.ae index 58b36fce..8e3d3581 100644 --- a/apps/frames_demo/frames_demo.ae +++ b/apps/frames_demo/frames_demo.ae @@ -103,7 +103,7 @@ struct CubeSpin { } mk_cube(ax: float, ay: float, vx: float, vy: float, tint: int) -> ptr { - c = malloc(64) as *CubeSpin + c = malloc(sizeof(CubeSpin)) as *CubeSpin c.ax = ax c.ay = ay c.vx = vx @@ -322,7 +322,7 @@ main() { // "malloc(): unaligned tcache chunk detected" at startup rather than as // anything pointing at this line. Sized with headroom deliberately -- // the next field added should not corrupt the heap. - app = malloc(256) as *App + app = malloc(sizeof(App)) as *App app.st_bounds = ui_state_s("bounds: pending") app.st_z = ui_state_s("z: pending") app.st_hit = ui_state_s("hit: none") diff --git a/apps/gesture_probe/gesture_probe.ae b/apps/gesture_probe/gesture_probe.ae index ead8fd54..a2487e8c 100644 --- a/apps/gesture_probe/gesture_probe.ae +++ b/apps/gesture_probe/gesture_probe.ae @@ -91,7 +91,7 @@ tail_lines(s: string, keep: int) -> string { } main() { - st = malloc(96) as *ProbeState + st = malloc(sizeof(ProbeState)) as *ProbeState st.canvas = 0 st.log = 0 st.counts = 0 diff --git a/apps/grand_perspective/gp_layout.ae b/apps/grand_perspective/gp_layout.ae index f86f535e..c3a36956 100644 --- a/apps/grand_perspective/gp_layout.ae +++ b/apps/grand_perspective/gp_layout.ae @@ -54,7 +54,7 @@ struct Rect { } mk_rect(x: float, y: float, w: float, h: float, size: long, depth: int, entry: *FileEntry) -> *Rect { - r = malloc(64) as *Rect + r = malloc(sizeof(Rect)) as *Rect r.x = x; r.y = y; r.w = w; r.h = h r.size = size; r.depth = depth; r.entry = entry r.nested = 0 diff --git a/apps/grand_perspective/gp_model.ae b/apps/grand_perspective/gp_model.ae index 9424d338..0fa2b32e 100644 --- a/apps/grand_perspective/gp_model.ae +++ b/apps/grand_perspective/gp_model.ae @@ -92,7 +92,7 @@ struct FileEntry { } mk_entry(name: string, path: string, size: long, is_dir: int, parent: *FileEntry) -> *FileEntry { - e = malloc(64) as *FileEntry + e = malloc(sizeof(FileEntry)) as *FileEntry e.name = name; string_retain(name) // retain: the field outlives the arg's scope e.path = path; string_retain(path) e.total_size = size @@ -150,7 +150,7 @@ struct TypeStat { } mk_tstat(ext: string) -> *TypeStat { - t = malloc(48) as *TypeStat + t = malloc(sizeof(TypeStat)) as *TypeStat t.ext = ext; string_retain(ext) t.bytes = 0 t.files = 0 diff --git a/apps/grand_perspective/gp_nav.ae b/apps/grand_perspective/gp_nav.ae index 001a4cf0..9314419e 100644 --- a/apps/grand_perspective/gp_nav.ae +++ b/apps/grand_perspective/gp_nav.ae @@ -36,7 +36,7 @@ extern aether_ui_text_set_string(handle: int, text: string) relayout(s: *AppState) { s.rects = list.new() - ctx = malloc(16) as *LayoutCtx + ctx = malloc(sizeof(LayoutCtx)) as *LayoutCtx ctx.remaining = RECT_BUDGET layout_treemap(ctx, s.rects, gp_model.MAP_X, 0.0, gp_model.CANVAS_W, gp_model.CANVAS_H, s.current.children, 0) // List pane rows: the current dir's children, largest first (a fresh diff --git a/apps/grand_perspective/gp_state.ae b/apps/grand_perspective/gp_state.ae index f9c5ee44..e2e4f818 100644 --- a/apps/grand_perspective/gp_state.ae +++ b/apps/grand_perspective/gp_state.ae @@ -108,7 +108,7 @@ struct AppState { // caller (main) runs gp_nav.relayout + gp_nav.retarget_watch right after, // keeping this module free of upward dependencies. mk_state(root: *FileEntry) -> *AppState { - s = malloc(384) as *AppState + s = malloc(sizeof(AppState)) as *AppState s.root = root s.current = root s.stack = list.new() diff --git a/apps/inspector/inspector.ae b/apps/inspector/inspector.ae index 9c4b25ac..d98b4a4a 100644 --- a/apps/inspector/inspector.ae +++ b/apps/inspector/inspector.ae @@ -43,7 +43,7 @@ struct Node { } mk_node(label: string, detail: string) -> *Node { - n = malloc(64) as *Node + n = malloc(sizeof(Node)) as *Node n.label = label n.detail = detail return n diff --git a/apps/maerkdown/maerkdown.ae b/apps/maerkdown/maerkdown.ae index f84ad092..74817817 100644 --- a/apps/maerkdown/maerkdown.ae +++ b/apps/maerkdown/maerkdown.ae @@ -571,7 +571,7 @@ probe(ed: *Ed) -> int { } main() { - ed = malloc(176) as *Ed + ed = malloc(sizeof(Ed)) as *Ed ed.doc = mdown.md_doc_new() ed.lo = null ed.cb = 0 diff --git a/apps/maerkdown/mdown.ae b/apps/maerkdown/mdown.ae index 42ccd9d6..fd36939e 100644 --- a/apps/maerkdown/mdown.ae +++ b/apps/maerkdown/mdown.ae @@ -84,7 +84,7 @@ struct MdDoc { // ---- construction / destruction ------------------------------------------- md_word_new(txt: string, wf: int) -> ptr { - w = malloc(24) as *MdWord + w = malloc(sizeof(MdWord)) as *MdWord string_retain(txt) w.txt = txt w.wf = wf @@ -92,7 +92,7 @@ md_word_new(txt: string, wf: int) -> ptr { } md_block_new(kind: int) -> ptr { - b = malloc(24) as *MdBlock + b = malloc(sizeof(MdBlock)) as *MdBlock b.kind = kind b.words = list.list_new() return b as ptr @@ -101,7 +101,7 @@ md_block_new(kind: int) -> ptr { // A new document is never empty: it holds one empty paragraph, the block // the caret lives in before anything is typed. md_doc_new() -> ptr { - d = malloc(16) as *MdDoc + d = malloc(sizeof(MdDoc)) as *MdDoc d.blocks = list.list_new() _ = list.list_add_raw(d.blocks, md_block_new(MD_PARA)) return d as ptr @@ -397,7 +397,7 @@ scan_token(sc: *MdScan, bp: ptr, tok: string) { parse_inline(dp: ptr, kind: int, line: string) { d = dp as *MdDoc bp = md_block_new(kind) - sc = malloc(32) as *MdScan + sc = malloc(sizeof(MdScan)) as *MdScan sc.bold = 0 sc.ital = 0 sc.code = 0 @@ -426,7 +426,7 @@ parse_inline(dp: ptr, kind: int, line: string) { } md_parse(src: string) -> ptr { - d = malloc(16) as *MdDoc + d = malloc(sizeof(MdDoc)) as *MdDoc d.blocks = list.list_new() fence = 0 lines = string.string_split(src, "\n") @@ -522,7 +522,7 @@ md_word_source(wp: ptr) -> string { // scanner via a throwaway block so the two can never drift. md_parse_word(tok: string) -> ptr { bp = md_block_new(MD_PARA) - sc = malloc(32) as *MdScan + sc = malloc(sizeof(MdScan)) as *MdScan sc.bold = 0 sc.ital = 0 sc.code = 0 diff --git a/apps/maerkdown/wordflow.ae b/apps/maerkdown/wordflow.ae index f214cf70..fdff0d68 100644 --- a/apps/maerkdown/wordflow.ae +++ b/apps/maerkdown/wordflow.ae @@ -98,7 +98,7 @@ struct WfLayout { } box_new(x: float, y: float, w: float, h: float, bi: int, wi: int) -> ptr { - b = malloc(48) as *WfBox + b = malloc(sizeof(WfBox)) as *WfBox b.x = x b.y = y b.w = w @@ -114,7 +114,7 @@ box_new(x: float, y: float, w: float, h: float, bi: int, wi: int) -> ptr { // buf[0..bpos] (character-wise editing). The layout owns the caret // position so render and editor never re-derive it. wf_layout(doc: ptr, view_w: float, cb: int, cg: int, buf: string, bpos: int) -> ptr { - lo = malloc(64) as *WfLayout + lo = malloc(sizeof(WfLayout)) as *WfLayout lo.boxes = list.list_new() lo.view_w = view_w lo.caret_x = WF_PAD_L diff --git a/apps/rubiks_cube/cube_engine.ae b/apps/rubiks_cube/cube_engine.ae index 74dc8c30..9f70c3ef 100644 --- a/apps/rubiks_cube/cube_engine.ae +++ b/apps/rubiks_cube/cube_engine.ae @@ -69,7 +69,7 @@ set_cell(c: *Cube, face: int, row: int, col: int, v: int) { } mk_cube() -> *Cube { - c = malloc(32) as *Cube + c = malloc(sizeof(Cube)) as *Cube fa, _e0 = intarr.new(54) c.faces = fa hs, _e1 = intarr.new(HIST_CAP) diff --git a/apps/rubiks_cube/rubiks_cube.ae b/apps/rubiks_cube/rubiks_cube.ae index 1e85458e..b1589134 100644 --- a/apps/rubiks_cube/rubiks_cube.ae +++ b/apps/rubiks_cube/rubiks_cube.ae @@ -53,7 +53,7 @@ struct View { show_legend: int } mk_view() -> *View { - v = malloc(64) as *View + v = malloc(sizeof(View)) as *View v.drag_active = 0 v.drag_face = -1 v.moves_lbl = 0 diff --git a/apps/sketchpad/sketchpad.ae b/apps/sketchpad/sketchpad.ae index ba94b31a..f3b649f4 100644 --- a/apps/sketchpad/sketchpad.ae +++ b/apps/sketchpad/sketchpad.ae @@ -96,7 +96,7 @@ finish_stroke(app: *App) { pp_free(app.cur) pp_free(simplified) app.cur = null - st = malloc(16) as *Stroke + st = malloc(sizeof(Stroke)) as *Stroke st.line = smoothed st.outline = outline _ = list.list_add_raw(app.strokes, st as ptr) @@ -183,7 +183,7 @@ probe(app: *App) { } main() { - app = malloc(64) as *App + app = malloc(sizeof(App)) as *App app.strokes = list.list_new() app.cur = null app.mode = 0 diff --git a/apps/svg_tetris/svg_tetris.ae b/apps/svg_tetris/svg_tetris.ae index 9248bbb1..26f398ae 100644 --- a/apps/svg_tetris/svg_tetris.ae +++ b/apps/svg_tetris/svg_tetris.ae @@ -64,7 +64,7 @@ struct View { status_lbl: int } mk_view() -> *View { - v = malloc(16) as *View + v = malloc(sizeof(View)) as *View v.score_lbl = 0 v.status_lbl = 0 return v diff --git a/apps/svg_tetris/tetris_engine.ae b/apps/svg_tetris/tetris_engine.ae index 7418f92f..e71f7915 100644 --- a/apps/svg_tetris/tetris_engine.ae +++ b/apps/svg_tetris/tetris_engine.ae @@ -137,7 +137,7 @@ board_set(g: *Game, col: int, row: int, v: int) { } mk_game() -> *Game { - g = malloc(96) as *Game + g = malloc(sizeof(Game)) as *Game g.shapes = build_shapes() arr, _e = intarr.new_filled(CELLS, -1) g.board = arr diff --git a/apps/trajans_column/trajans_column.ae b/apps/trajans_column/trajans_column.ae index 24fd5501..6e30d016 100644 --- a/apps/trajans_column/trajans_column.ae +++ b/apps/trajans_column/trajans_column.ae @@ -41,7 +41,7 @@ struct Col { status: int } mk_col() -> *Col { - c = malloc(16) as *Col + c = malloc(sizeof(Col)) as *Col c.active = 0 c.status = 0 return c diff --git a/apps/tumbling_cube/tumbling_cube.ae b/apps/tumbling_cube/tumbling_cube.ae index fe9c7950..24c13a28 100644 --- a/apps/tumbling_cube/tumbling_cube.ae +++ b/apps/tumbling_cube/tumbling_cube.ae @@ -127,7 +127,7 @@ struct Spin { lbl_glass: ptr } mk_spin() -> *Spin { - s = malloc(192) as *Spin + s = malloc(sizeof(Spin)) as *Spin s.ax = 0.0 - 0.42 s.ay = 0.62 s.vx = 0.35 diff --git a/apps/turtle/turtle.ae b/apps/turtle/turtle.ae index a1d1487d..d3237015 100644 --- a/apps/turtle/turtle.ae +++ b/apps/turtle/turtle.ae @@ -202,7 +202,7 @@ main() { // ui.auto_hide's setup_timer bug: field-writes through an // already-valid pointer sidestep the whole issue, since the // POINTER itself is assigned once, before any closure exists. - app_state = malloc(80) as *TurtleUiState + app_state = malloc(sizeof(TurtleUiState)) as *TurtleUiState app_state.editor = 0 app_state.canvas = 0 app_state.scale = 1.0 diff --git a/apps/turtle/turtle_interp.ae b/apps/turtle/turtle_interp.ae index 57725590..a6cf797c 100644 --- a/apps/turtle/turtle_interp.ae +++ b/apps/turtle/turtle_interp.ae @@ -49,7 +49,7 @@ new_turtle(cid: int, canvas_w: float, canvas_h: float) -> *TurtleState { // 9 fields (2 int, 7 float); generously over-allocated rather than // hand-computed — an undersized malloc here is exactly the silent // heap-corruption trap aether-ui/LLM.md warns about on macOS. - t = malloc(224) as *TurtleState + t = malloc(sizeof(TurtleState)) as *TurtleState t.cid = cid t.cx = canvas_w / 2.0 t.cy = canvas_h / 2.0 diff --git a/apps/video_frame/video_frame.ae b/apps/video_frame/video_frame.ae index 12bed963..7653c857 100644 --- a/apps/video_frame/video_frame.ae +++ b/apps/video_frame/video_frame.ae @@ -228,7 +228,7 @@ main() { // A missing or unreadable clip is not a failure -- the app still opens // and draws its frames, just with an empty video rect. The spec asserts // only that pixels reach the screen, which the chrome alone satisfies. - app = malloc(256) as *App + app = malloc(sizeof(App)) as *App app.shown = 0 app.pts = 0 app.t0 = 0.0 - 1.0 diff --git a/asks/REGRESSION-0643-inline-heap-tracker-grows-struct.md b/asks/REGRESSION-0643-inline-heap-tracker-grows-struct.md new file mode 100644 index 00000000..000c8d17 --- /dev/null +++ b/asks/REGRESSION-0643-inline-heap-tracker-grows-struct.md @@ -0,0 +1,124 @@ +# REGRESSION (aether v0.643.0): inline heap-string trackers grow pure-Aether struct size — hand-sized `malloc(N)` now overflows + +**To:** the sibling looking after `../aether` +**From:** aether-ui line +**Status:** ROOT-CAUSED to a single commit + fixed on our side. This is an FYI + a design question, not a blocker. We are NOT asking you to revert. + +--- + +## TL;DR + +Bumping our CI pin to **aether v0.643.0** turned 14 pure-Aether `vg/` unit +suites red with glibc heap-corruption aborts (`malloc assertion failure in +sysmalloc`, `malloc(): invalid size`, `free(): invalid pointer` — three +different detectors, i.e. general metadata corruption, not one bad free). + +Bisected to the **first commit after v0.642.0**: + +> **`1307dfa8` — "Place heap trackers inline so struct-prefix punning stays sound (#1879 regression)"** + +That commit moves each hidden `int _heap_` tracker from a **trailing** +block (after all declared fields) to **inline** (immediately after its string +field). Correct fix for the punning bug it targets — but it **changes the total +`sizeof` of pure-Aether structs that carry a string field not in last position** +(interleaving an `int` + its alignment padding mid-struct instead of packing the +trackers at the end). Any code that allocated such a struct with a **hand-computed +byte count** now under-allocates and the tracker store runs off the end of the +block. + +We had ~158 `malloc() as *T` sites doing exactly that. We've fixed all +of them (`malloc(sizeof(T))`), which is the right call regardless. Filing this so +you know downstream byte-count code broke, and to raise one design question below. + +## Clean A/B (same source, same link line, only the toolchain differs) + +| aether | `vg/test/test_parser` | +|--------|------------------------| +| v0.641.0 | `=== test_parser passed ===` (rc 0) | +| v0.642.0 | passed (rc 0) | +| **v0.643.0** | **`Fatal glibc error: malloc assertion failure in sysmalloc`** (rc 134) | +| **`1307dfa8` (first commit past 0.642)** | **abort (rc 134)** — culprit confirmed directly | + +## ASan pinned the exact write + +Built `test_parser` `-fsanitize=address` on 0.643: + +``` +ERROR: AddressSanitizer: heap-buffer-overflow ... WRITE of size 4 + #0 parser_mk_node vg/svg/parser.ae:56 <- n.text = "" (stores _heap_text) +0x...b8 is located 0 bytes to the right of 40-byte region + allocated by: + #1 parser_mk_node vg/svg/parser.ae:52 <- malloc(40) +``` + +The struct: + +``` +struct SvgNode { tag: string; attrs: ptr; children: ptr; text: string } +``` + +- **Trailing layout (≤0.642):** `tag,attrs,children,text` (32 B) + `_heap_tag`,`_heap_text` appended (8 B) = **40 B**. Our hand-written `malloc(40)` was exact. +- **Inline layout (0.643):** `tag`(8) `_heap_tag`(4) pad(4) `attrs`(8) `children`(8) `text`(8) `_heap_text`(4) pad(4) = **48 B**. The store to `_heap_text` lands at offset 40 — one `int` past a 40-byte block. 4-byte heap overflow → metadata corruption. + +Generated C on 0.643 (confirming the interleave): + +```c +typedef struct SvgNode { + const char* tag; + int _heap_tag; /* was trailing; now inline */ + void* attrs; + void* children; + const char* text; + int _heap_text; +} SvgNode; +``` + +## Reproducer (30 s, no aeb involved) + +```sh +cd aether-ui +aetherc --lib "$PWD" vg/test/test_parser.ae /tmp/tp.c +gcc /tmp/tp.c vg/test/text_metrics_stub.c $(ae cflags) -o /tmp/tp +/tmp/tp # 0.643: abort; ≤0.642: "=== test_parser passed ===" +``` + +Add `-g -fsanitize=address` to the gcc line to see the overflow directly. + +## Scope on our side + +**158 `malloc() as *T` sites across 98 files** (vg/, ui/, apps/, +examples/). Every one was a latent overflow under 0.643 the moment its struct +had a string field placed before another field. We swept them all to +`malloc(sizeof(T))` — layout-exact, always ≥ the old count, immune to any future +tracker-placement change. Full 4-platform matrix re-verified. **This is our fix +and it's landing; you don't need to do anything for us to be green.** + +## The design question (your call — no action requested) + +`1307dfa8` was a *permutation* of tracker positions, but it also *grew* total +`sizeof` (mid-struct `int`+padding costs more than end-packing two `int`s +together). Two things worth a thought on your side: + +1. **Is the size growth intended?** If trackers could be placed inline *without* + growing total size (e.g. packing the interleaved ints into existing tail + padding, or a stable size contract), downstream hand-sized allocations would + have survived. Probably not worth it — `sizeof` is the correct idiom and you + already ship it — but flagging in case struct-size stability across releases + is something you want to hold. + +2. **Could the compiler warn?** A hand-written `malloc(40) as *SvgNode` where + `sizeof(SvgNode) == 48` is statically detectable at the cast. A lint ("literal + malloc size < sizeof(cast target)") would have turned this from a heap- + corruption hunt into a compile-time nudge. The pattern is common enough in our + tree (158 sites) that it may be common elsewhere. + +Neither blocks us. `sizeof(T)` is the right answer and we've adopted it fleet-wide. + +## What we did + +- Bisect + ASan root-cause (above). +- Swept 158 sites `malloc(N)` → `malloc(sizeof(T))`; raw-buffer `malloc(8)` + (no struct cast) left untouched. +- Held the CI pin at v0.627.0 until the sweep is verified green on 0.643, then + moving AETHER_REF → v0.643.0 and AEB_REF → v0.296 together. (aeb is innocent — + these tests never touch it.) diff --git a/examples/a11y_demo/a11y_demo.ae b/examples/a11y_demo/a11y_demo.ae index 8ca1ade5..4f4dcd59 100644 --- a/examples/a11y_demo/a11y_demo.ae +++ b/examples/a11y_demo/a11y_demo.ae @@ -25,7 +25,7 @@ import std.list extern malloc(size: int) -> ptr struct Row { label: string } mk_row(s: string) -> ptr { - r = malloc(16) as *Row + r = malloc(sizeof(Row)) as *Row r.label = s return r as ptr } diff --git a/examples/auto_hide_demo/auto_hide_demo.ae b/examples/auto_hide_demo/auto_hide_demo.ae index 52679353..c06c1862 100644 --- a/examples/auto_hide_demo/auto_hide_demo.ae +++ b/examples/auto_hide_demo/auto_hide_demo.ae @@ -32,7 +32,7 @@ struct DemoState { main() { ui.window("Edge Zone Demo", 420, 300) { - d = malloc(24) as *DemoState + d = malloc(sizeof(DemoState)) as *DemoState d.status = 0 d.handle_wrap = 0 d.sidebar = 0 diff --git a/examples/dblclick_demo/dblclick_demo.ae b/examples/dblclick_demo/dblclick_demo.ae index 9b7620a3..6def70c4 100644 --- a/examples/dblclick_demo/dblclick_demo.ae +++ b/examples/dblclick_demo/dblclick_demo.ae @@ -12,7 +12,7 @@ extern malloc(size: int) -> ptr struct Item { name: string } mk_item(n: int) -> ptr { - it = malloc(16) as *Item + it = malloc(sizeof(Item)) as *Item it.name = string.concat("item ", string.from_int(n)) return it as ptr } diff --git a/examples/disclosure_demo/disclosure_demo.ae b/examples/disclosure_demo/disclosure_demo.ae index efa766b3..c4da6747 100644 --- a/examples/disclosure_demo/disclosure_demo.ae +++ b/examples/disclosure_demo/disclosure_demo.ae @@ -30,7 +30,7 @@ extern malloc(size: int) -> ptr struct App { filters: ptr metrics: ptr sort: ptr } main() { - app = malloc(32) as *App + app = malloc(sizeof(App)) as *App window("Disclosure — collapsible sections", 480, 420) { vstack(6) { diff --git a/examples/each_demo/each_demo.ae b/examples/each_demo/each_demo.ae index 54f42278..70e82bb2 100644 --- a/examples/each_demo/each_demo.ae +++ b/examples/each_demo/each_demo.ae @@ -25,7 +25,7 @@ extern malloc(size: int) -> ptr struct Row { name: string } mk_row(n: int) -> ptr { - r = malloc(16) as *Row + r = malloc(sizeof(Row)) as *Row r.name = string.concat("row ", string.from_int(n)) return r as ptr } diff --git a/examples/golden_gallery/golden_gallery.ae b/examples/golden_gallery/golden_gallery.ae index e946349c..f46aa68e 100644 --- a/examples/golden_gallery/golden_gallery.ae +++ b/examples/golden_gallery/golden_gallery.ae @@ -244,7 +244,7 @@ pin(cid: int) { } main() { - app = malloc(40) as *App + app = malloc(sizeof(App)) as *App verdict = ui_state_s("golden: unchecked") app.st_verdict = verdict diff --git a/examples/listbox_demo/listbox_demo.ae b/examples/listbox_demo/listbox_demo.ae index c9a7a9a6..f2291dd4 100644 --- a/examples/listbox_demo/listbox_demo.ae +++ b/examples/listbox_demo/listbox_demo.ae @@ -22,7 +22,7 @@ extern malloc(size: int) -> ptr struct Person { name: string, size: string } mk_person(n: int) -> ptr { - p = malloc(24) as *Person + p = malloc(sizeof(Person)) as *Person p.name = string.concat("person ", string.from_int(n)) p.size = string.concat(string.from_int(n * 7), " KB") return p as ptr diff --git a/examples/multiselect_demo/multiselect_demo.ae b/examples/multiselect_demo/multiselect_demo.ae index 4e23bec6..c6d79761 100644 --- a/examples/multiselect_demo/multiselect_demo.ae +++ b/examples/multiselect_demo/multiselect_demo.ae @@ -15,7 +15,7 @@ extern malloc(size: int) -> ptr struct Item { name: string } mk_item(n: int) -> ptr { - it = malloc(16) as *Item + it = malloc(sizeof(Item)) as *Item it.name = string.concat("item ", string.from_int(n)) return it as ptr } diff --git a/examples/rbind_demo/rbind_demo.ae b/examples/rbind_demo/rbind_demo.ae index 69cd480c..87062417 100644 --- a/examples/rbind_demo/rbind_demo.ae +++ b/examples/rbind_demo/rbind_demo.ae @@ -27,7 +27,7 @@ extern malloc(size: int) -> ptr struct Row { name: string } mk_row(name: string) -> ptr { - r = malloc(16) as *Row + r = malloc(sizeof(Row)) as *Row r.name = name return r as ptr } diff --git a/examples/reorder_demo/reorder_demo.ae b/examples/reorder_demo/reorder_demo.ae index 1edcdc3b..dfbaa4f1 100644 --- a/examples/reorder_demo/reorder_demo.ae +++ b/examples/reorder_demo/reorder_demo.ae @@ -26,7 +26,7 @@ extern malloc(size: int) -> ptr struct Row { label: string } mk_row(s: string) -> ptr { - r = malloc(16) as *Row + r = malloc(sizeof(Row)) as *Row r.label = s return r as ptr } diff --git a/examples/selmode_demo/selmode_demo.ae b/examples/selmode_demo/selmode_demo.ae index f3fd14bb..babb7003 100644 --- a/examples/selmode_demo/selmode_demo.ae +++ b/examples/selmode_demo/selmode_demo.ae @@ -22,7 +22,7 @@ extern malloc(size: int) -> ptr struct Item { name: string } mk_item(n: int) -> ptr { - it = malloc(16) as *Item + it = malloc(sizeof(Item)) as *Item it.name = string.concat("item ", string.from_int(n)) return it as ptr } diff --git a/examples/stroker_demo/stroker_demo.ae b/examples/stroker_demo/stroker_demo.ae index dadbe2ea..f61e4d60 100644 --- a/examples/stroker_demo/stroker_demo.ae +++ b/examples/stroker_demo/stroker_demo.ae @@ -112,7 +112,7 @@ compare_pixels(app: *App) { } main() { - app = malloc(40) as *App + app = malloc(sizeof(App)) as *App app.hits = 0 cmp = ui_state_s("pixels: not compared yet") hits = ui_state_s("hits: 0") diff --git a/examples/table_demo/table_demo.ae b/examples/table_demo/table_demo.ae index 28524494..e3defd67 100644 --- a/examples/table_demo/table_demo.ae +++ b/examples/table_demo/table_demo.ae @@ -22,7 +22,7 @@ extern malloc(size: int) -> ptr struct Person { name: string, kb: int } mk_p(name: string, kb: int) -> ptr { - p = malloc(24) as *Person + p = malloc(sizeof(Person)) as *Person p.name = name p.kb = kb return p as ptr diff --git a/examples/tabledeleg_demo/tabledeleg_demo.ae b/examples/tabledeleg_demo/tabledeleg_demo.ae index d39bf469..e2b4d8dc 100644 --- a/examples/tabledeleg_demo/tabledeleg_demo.ae +++ b/examples/tabledeleg_demo/tabledeleg_demo.ae @@ -13,7 +13,7 @@ extern malloc(size: int) -> ptr struct Row { name: string, pct: int } mk_row(name: string, pct: int) -> ptr { - r = malloc(24) as *Row + r = malloc(sizeof(Row)) as *Row r.name = name r.pct = pct return r as ptr diff --git a/examples/themes_demo/themes_demo.ae b/examples/themes_demo/themes_demo.ae index 9abaa390..16a6b2f4 100644 --- a/examples/themes_demo/themes_demo.ae +++ b/examples/themes_demo/themes_demo.ae @@ -29,7 +29,7 @@ extern malloc(size: int) -> ptr struct Row { label: string } mk_row(s: string) -> ptr { - r = malloc(16) as *Row + r = malloc(sizeof(Row)) as *Row r.label = s return r as ptr } diff --git a/examples/undo_demo/undo_demo.ae b/examples/undo_demo/undo_demo.ae index 3edcc65c..ff092b87 100644 --- a/examples/undo_demo/undo_demo.ae +++ b/examples/undo_demo/undo_demo.ae @@ -24,7 +24,7 @@ main() { total = text("total: 0") name = text("name: Ellen") divider() - cnt = malloc(8) as *Cnt + cnt = malloc(sizeof(Cnt)) as *Cnt cnt.v = 0 hstack(8) { _a = btn("Add 5") callback { diff --git a/examples/vg3d_demo/vg3d_demo.ae b/examples/vg3d_demo/vg3d_demo.ae index 91d10f22..7e1cf41d 100644 --- a/examples/vg3d_demo/vg3d_demo.ae +++ b/examples/vg3d_demo/vg3d_demo.ae @@ -158,7 +158,7 @@ probe(app: *App) { } main() { - app = malloc(72) as *App + app = malloc(sizeof(App)) as *App app.angle = 0 st_a = ui_state_s("angle: 0") st_v = ui_state_s("v0: ?") diff --git a/examples/vlist_demo/vlist_demo.ae b/examples/vlist_demo/vlist_demo.ae index 77f3e873..7f8163fd 100644 --- a/examples/vlist_demo/vlist_demo.ae +++ b/examples/vlist_demo/vlist_demo.ae @@ -14,7 +14,7 @@ extern malloc(size: int) -> ptr struct Item { n: int } mk_item(n: int) -> ptr { - it = malloc(16) as *Item + it = malloc(sizeof(Item)) as *Item it.n = n return it as ptr } diff --git a/examples/zen_demo/zen_demo.ae b/examples/zen_demo/zen_demo.ae index f69ce783..389e482a 100644 --- a/examples/zen_demo/zen_demo.ae +++ b/examples/zen_demo/zen_demo.ae @@ -23,7 +23,7 @@ extern malloc(size: int) -> ptr struct Row { label: string } mk_row(s: string) -> ptr { - r = malloc(16) as *Row + r = malloc(sizeof(Row)) as *Row r.label = s return r as ptr } diff --git a/ui/chrome.ae b/ui/chrome.ae index 617581f1..3220da17 100644 --- a/ui/chrome.ae +++ b/ui/chrome.ae @@ -69,7 +69,7 @@ struct ChromeTokens { } chrome_tokens() -> ptr { - t = malloc(96) as *ChromeTokens + t = malloc(sizeof(ChromeTokens)) as *ChromeTokens t.bg = "#F3F4F6" t.bg_hover = "#E5E7EB" t.bg_active = "#D1D5DB" diff --git a/ui/chromed.ae b/ui/chromed.ae index 304e5a07..d0e48000 100644 --- a/ui/chromed.ae +++ b/ui/chromed.ae @@ -111,7 +111,7 @@ _toggle_render(tf: *ToggleFace) { } _mk_toggle(label: string, user_boxed: ptr) -> int { - tf = malloc(24) as *ToggleFace + tf = malloc(sizeof(ToggleFace)) as *ToggleFace tf.handle = 0 tf.cv = 0 tf.label = label diff --git a/ui/dialog.ae b/ui/dialog.ae index 137de596..e68a3cf6 100644 --- a/ui/dialog.ae +++ b/ui/dialog.ae @@ -37,7 +37,7 @@ dialog_close_of(dp: ptr) -> int { d = dp as *DlgHdr; return d.close } // dialog_header(title) callback { … } — a framed title row. The callback // fires when the ✕ is pressed (or driven). dialog_header(_ctx: ptr, title: string, on_close: fn) -> ptr { - d = malloc(16) as *DlgHdr + d = malloc(sizeof(DlgHdr)) as *DlgHdr outer = vstack(_ctx, 4) row = hstack(outer, 8) diff --git a/ui/disclosure.ae b/ui/disclosure.ae index be553edb..bc360775 100644 --- a/ui/disclosure.ae +++ b/ui/disclosure.ae @@ -61,7 +61,7 @@ disclosure_toggle(dp: ptr) -> int { // disclosure(label, start_open) { … } — a header + collapsible body. // Returns the Disc so the caller can drive it programmatically. disclosure(_ctx: ptr, label: string, start_open: int, body_fn: fn) -> ptr { - d = malloc(48) as *Disc + d = malloc(sizeof(Disc)) as *Disc d.open = start_open string_retain(label) d.label = label diff --git a/ui/frames.ae b/ui/frames.ae index a5d2c7b4..e39958eb 100644 --- a/ui/frames.ae +++ b/ui/frames.ae @@ -124,7 +124,7 @@ frame(_ctx: ptr, title: string, x: float, y: float, w: float, h: float, } frames_new(scene: ptr, x: float, y: float, w: float, h: float) -> ptr { - hst = malloc(160) as *FrameHost + hst = malloc(sizeof(FrameHost)) as *FrameHost hst.scene = scene hst.frames = list.new() hst.next_z = 0 @@ -153,7 +153,7 @@ frames_new(scene: ptr, x: float, y: float, w: float, h: float) -> ptr { frame_add(host: ptr, title: string, x: float, y: float, w: float, h: float) -> ptr { hst = host as *FrameHost - f = malloc(128) as *Frame + f = malloc(sizeof(Frame)) as *Frame f.host = host f.title = title string_retain(f.title) diff --git a/ui/module.ae b/ui/module.ae index dff9b91f..0f6019be 100644 --- a/ui/module.ae +++ b/ui/module.ae @@ -1743,7 +1743,7 @@ struct StyleRule { struct StyleSheet { rules: ptr } // std.list of *StyleRule create_styles() -> ptr { - s = malloc(8) as *StyleSheet + s = malloc(sizeof(StyleSheet)) as *StyleSheet s.rules = list.new() return s as ptr } @@ -1766,7 +1766,7 @@ _style_rule_of(sp: ptr, sel: string) -> ptr { found = _style_find(sp, sel) if found != null { return found } s = sp as *StyleSheet - r = malloc(288) as *StyleRule + r = malloc(sizeof(StyleRule)) as *StyleRule r.sel = sel r.has_color = 0 r.has_bg = 0 @@ -2182,7 +2182,7 @@ struct RoleEntry { name: string, hex: int, has: int } struct ColorScheme { roles: ptr } color_scheme() -> ptr { - sc = malloc(8) as *ColorScheme + sc = malloc(sizeof(ColorScheme)) as *ColorScheme sc.roles = list.new() return sc as ptr } @@ -2212,7 +2212,7 @@ role(scp: ptr, name: string, hex: int) { return } sc = scp as *ColorScheme - r = malloc(24) as *RoleEntry + r = malloc(sizeof(RoleEntry)) as *RoleEntry r.name = name r.hex = hex r.has = 1 @@ -2470,7 +2470,7 @@ struct WStateEntry { name: string, sheet: ptr } struct WStates { target: int, entries: ptr, cur: string } ui_states(target: int) -> ptr { - ws = malloc(24) as *WStates + ws = malloc(sizeof(WStates)) as *WStates ws.target = target ws.entries = list.new() ws.cur = "" @@ -2526,7 +2526,7 @@ on_state(_ctx: ptr, name: string) -> ptr { add_state(wsp: ptr, name: string, sheet: ptr) { ws = wsp as *WStates - e = malloc(16) as *WStateEntry + e = malloc(sizeof(WStateEntry)) as *WStateEntry e.name = name e.sheet = sheet _ = list.add(ws.entries, e as ptr) @@ -2614,7 +2614,7 @@ command_fire(cp: ptr) { // command(label, accel, cb) — accel may be "" for no keystroke. command(label: string, accel: string, cb: fn) -> ptr { - c = malloc(56) as *Command + c = malloc(sizeof(Command)) as *Command c.label = label c.accel = accel c.cb = box_closure(cb) @@ -2707,7 +2707,7 @@ command_attach(cp: ptr, handle: int) { struct CommandGroup { cmds: ptr } _commands_factory() -> ptr { - g = malloc(8) as *CommandGroup + g = malloc(sizeof(CommandGroup)) as *CommandGroup g.cmds = list.new() return g as ptr } @@ -3208,7 +3208,7 @@ canvas_on_scroll(canvas_id: int, cb: fn) { } canvas_on_hover(canvas_id: int, cb: fn) { - s = malloc(16) as *CanvasHoverState + s = malloc(sizeof(CanvasHoverState)) as *CanvasHoverState s.inside = 0 s.watchdog = 0 @@ -3300,7 +3300,7 @@ each(_ctx: ptr, orientation: string, spacing: int, render: fn) -> ptr { if _ctx != 0 { aether_ui_widget_add_child_ctx(_ctx, h) } - e = malloc(24) as *EachGroup + e = malloc(sizeof(EachGroup)) as *EachGroup e.container = h e.render = box_closure(render) e.count = 0 @@ -3555,7 +3555,7 @@ listbox_multi(_ctx: ptr, spacing: int, render: fn) -> ptr { } _listbox_make(_ctx: ptr, spacing: int, render: fn, multi: int) -> ptr { - lb = malloc(96) as *ListBox + lb = malloc(sizeof(ListBox)) as *ListBox lb.rows = null lb.selected = 0 - 1 lb.on_sel = null @@ -3659,7 +3659,7 @@ _listbox_invoke_reorder(cb: fn, from: int, to: int) { // a drop calls listbox_move(source_index, target_index). The drag gesture is // backend C (GTK4 GtkDragSource/GtkDropTarget); the model reorder is shared. _listbox_make_reorder(_ctx: ptr, spacing: int, render: fn) -> ptr { - lb = malloc(96) as *ListBox + lb = malloc(sizeof(ListBox)) as *ListBox lb.rows = null lb.selected = 0 - 1 lb.on_sel = null @@ -3960,7 +3960,7 @@ struct TableDef { table_cols() -> ptr { return list.new() } table_col(cols: ptr, title: string, w: int) { - c = malloc(24) as *TableCol + c = malloc(sizeof(TableCol)) as *TableCol c.title = title c.w = w c.delegate = null @@ -4001,7 +4001,7 @@ col_delegate(_ctx: ptr, title: string, w: int, render: fn) { } table_col_delegate(cols: ptr, title: string, w: int, render: fn) { - c = malloc(24) as *TableCol + c = malloc(sizeof(TableCol)) as *TableCol c.title = title c.w = w c.delegate = box_closure(render) @@ -4022,7 +4022,7 @@ _table_invoke_sort(cb: fn, c: int) { // table(cols, cell) -> table-handle. cell = |item, c| -> string. table(_ctx: ptr, cols: ptr, cell: fn) -> ptr { - t = malloc(104) as *TableDef + t = malloc(sizeof(TableDef)) as *TableDef t.cols = cols t.cell = box_closure(cell) t.sort_cb = null @@ -4399,7 +4399,7 @@ struct TreeNode { label: string, children: ptr, expanded: int } struct TreeDef { roots: ptr, lb: ptr, flat: ptr } tree_node(label: string) -> ptr { - n = malloc(24) as *TreeNode + n = malloc(sizeof(TreeNode)) as *TreeNode n.label = label n.children = null n.expanded = 0 @@ -4423,7 +4423,7 @@ _tree_flatten_into(flat: ptr, nodes: ptr, depth: int) { while i < n { np, _e = list.get(nodes, i) node = np as *TreeNode - tr = malloc(16) as *TreeRow + tr = malloc(sizeof(TreeRow)) as *TreeRow tr.node = np tr.depth = depth _ = list.add(flat, tr as ptr) @@ -4442,7 +4442,7 @@ _tree_reflatten(tp: ptr) { } tree(_ctx: ptr, roots: ptr) -> ptr { - t = malloc(24) as *TreeDef + t = malloc(sizeof(TreeDef)) as *TreeDef t.roots = roots t.flat = null tp = t as ptr @@ -4507,7 +4507,7 @@ struct VList { } vlist(_ctx: ptr, orientation: string, window: int, render: fn) -> ptr { - v = malloc(48) as *VList + v = malloc(sizeof(VList)) as *VList v.render = box_closure(render) v.items = null v.window = window diff --git a/vg/backend/dispatch.ae b/vg/backend/dispatch.ae index 014e44d1..86d7e279 100644 --- a/vg/backend/dispatch.ae +++ b/vg/backend/dispatch.ae @@ -48,7 +48,7 @@ struct Dispatch { // Construct a dispatch table. `be` is the concrete backend's handle; // `cb` is a single closure that switches on the op selector. backend_make(be: ptr, cb: fn) -> ptr { - d = malloc(16) as *Dispatch + d = malloc(sizeof(Dispatch)) as *Dispatch d.be = be d.cb = cb return d diff --git a/vg/backend/gtk.ae b/vg/backend/gtk.ae index 545ff9c5..a653ce22 100644 --- a/vg/backend/gtk.ae +++ b/vg/backend/gtk.ae @@ -100,7 +100,7 @@ struct GtkBackend { // shape factories dispatch through — the GtkBackend handle is the // `be` threaded into the op-switch closure. gtk_backend_new(canvas_id: int) -> ptr { - b = malloc(16) as *GtkBackend + b = malloc(sizeof(GtkBackend)) as *GtkBackend b.canvas_id = canvas_id b.raster_skipped = 0 return dispatch.backend_make(b) callback |be: ptr, op: int, opts: ptr| { diff --git a/vg/backend/record.ae b/vg/backend/record.ae index ca0996fb..9021400d 100644 --- a/vg/backend/record.ae +++ b/vg/backend/record.ae @@ -72,14 +72,14 @@ struct RecordedCall { } mk_call(kind: string, opts: ptr) -> *RecordedCall { - c = malloc(24) as *RecordedCall + c = malloc(sizeof(RecordedCall)) as *RecordedCall c.kind = kind c.opts = opts return c } backend_new() -> ptr { - b = malloc(16) as *Backend + b = malloc(sizeof(Backend)) as *Backend b.calls = list.new() // Wrap the recording handle in a dispatch table. The single // closure switches on the op selector and forwards to this diff --git a/vg/font.ae b/vg/font.ae index b97ac958..9a1fd76e 100644 --- a/vg/font.ae +++ b/vg/font.ae @@ -119,7 +119,7 @@ imod16(v: int) -> int { // ─── Loading ───────────────────────────────────────────────────────────────── font_load(path: string) -> ptr { - f = malloc(112) as *TtfFont + f = malloc(sizeof(TtfFont)) as *TtfFont f.data = ""; f.len = 0; f.ok = 0; f.upem = 0; f.loca_fmt = 0 f.nglyphs = 0; f.nhm = 0; f.cmap4 = 0; f.glyf = 0; f.loca = 0; f.hmtx = 0 f.kern0 = 0 @@ -415,7 +415,7 @@ struct Outl { ok: int } mk_outl() -> *Outl { - o = malloc(80) as *Outl + o = malloc(sizeof(Outl)) as *Outl o.cap = 65536 o.ecap = 1024 pts, _pe = floatarr.new(o.cap) diff --git a/vg/geom/bbox.ae b/vg/geom/bbox.ae index c5c5f0d1..bf9fd322 100644 --- a/vg/geom/bbox.ae +++ b/vg/geom/bbox.ae @@ -44,7 +44,7 @@ struct Bounds { } mk_bounds() -> *Bounds { - b = malloc(40) as *Bounds + b = malloc(sizeof(Bounds)) as *Bounds b.has = 0 b.min_x = 0.0 b.min_y = 0.0 @@ -54,7 +54,7 @@ mk_bounds() -> *Bounds { } mk_bounds_from(min_x: float, min_y: float, max_x: float, max_y: float) -> *Bounds { - b = malloc(40) as *Bounds + b = malloc(sizeof(Bounds)) as *Bounds b.has = 1 b.min_x = min_x b.min_y = min_y diff --git a/vg/geom/easing.ae b/vg/geom/easing.ae index baef9caf..5629ef6a 100644 --- a/vg/geom/easing.ae +++ b/vg/geom/easing.ae @@ -128,7 +128,7 @@ parse_hex_color(color: string) -> *RGB { hex = string.concat(hex, c2) } - out = malloc(24) as *RGB + out = malloc(sizeof(RGB)) as *RGB out.r = hex2(hex, 0) out.g = hex2(hex, 2) out.b = hex2(hex, 4) diff --git a/vg/geom/mat4.ae b/vg/geom/mat4.ae index f2a2208e..99872b29 100644 --- a/vg/geom/mat4.ae +++ b/vg/geom/mat4.ae @@ -36,7 +36,7 @@ struct M4 { } _m4_zero() -> *M4 { - m = malloc(8) as *M4 + m = malloc(sizeof(M4)) as *M4 m.e = floatarr_new_raw(16) i = 0 while i < 16 { diff --git a/vg/geom/mesh.ae b/vg/geom/mesh.ae index b79bce86..149e6660 100644 --- a/vg/geom/mesh.ae +++ b/vg/geom/mesh.ae @@ -50,7 +50,7 @@ const MESH_MIN_I: int = 24 const MESH_MIN_F: int = 6 mesh_new() -> ptr { - m = malloc(80) as *Mesh + m = malloc(sizeof(Mesh)) as *Mesh m.vx = floatarr_new_raw(MESH_MIN_V * 3) m.nverts = 0 m.vcap = MESH_MIN_V diff --git a/vg/geom/path_builder.ae b/vg/geom/path_builder.ae index 21b9f525..5ffbc275 100644 --- a/vg/geom/path_builder.ae +++ b/vg/geom/path_builder.ae @@ -68,7 +68,7 @@ struct PathBuilder { } pb_new(ctx: ptr, backend: ptr) -> ptr { - pb = malloc(48) as *PathBuilder + pb = malloc(sizeof(PathBuilder)) as *PathBuilder pb.ctx = ctx pb.backend = backend pb.parts = "" diff --git a/vg/geom/polypath.ae b/vg/geom/polypath.ae index f2021412..34c4a311 100644 --- a/vg/geom/polypath.ae +++ b/vg/geom/polypath.ae @@ -78,7 +78,7 @@ const PP_MIN_CAP: int = 16 const PP_MIN_CTRS: int = 4 pp_new() -> ptr { - p = malloc(64) as *PolyPath + p = malloc(sizeof(PolyPath)) as *PolyPath p.pts = floatarr_new_raw(PP_MIN_CAP * 2) p.npts = 0 p.cap = PP_MIN_CAP @@ -622,7 +622,7 @@ struct PPStrokeCtr { _pp_stroke_prep(pp: ptr, ci: int, is_closed: int) -> *PPStrokeCtr { p = pp as *PolyPath - st = malloc(64) as *PPStrokeCtr + st = malloc(sizeof(PPStrokeCtr)) as *PPStrokeCtr start = _pp_ctr_start(p, ci) last = intarr_get_raw(p.ends, ci) cap = last - start + 2 diff --git a/vg/geom/region.ae b/vg/geom/region.ae index 0ba897bf..126cb268 100644 --- a/vg/geom/region.ae +++ b/vg/geom/region.ae @@ -26,7 +26,7 @@ struct Region { } rgn_new() -> ptr { - r = malloc(64) as *Region + r = malloc(sizeof(Region)) as *Region r.cap = 8 r.n = 0 r.rects = floatarr_new_raw(r.cap * 4) diff --git a/vg/geom/transform.ae b/vg/geom/transform.ae index 32e5b234..770c43f1 100644 --- a/vg/geom/transform.ae +++ b/vg/geom/transform.ae @@ -59,7 +59,7 @@ struct Pt { } mk_pt(x: float, y: float) -> *Pt { - p = malloc(16) as *Pt + p = malloc(sizeof(Pt)) as *Pt p.x = x p.y = y return p @@ -77,7 +77,7 @@ struct Affine { } affine_new(a: float, b: float, c: float, d: float, e: float, f: float) -> *Affine { - m = malloc(48) as *Affine + m = malloc(sizeof(Affine)) as *Affine m.a = a m.b = b m.c = c @@ -175,7 +175,7 @@ struct Projective { proj_new(a: float, b: float, c: float, d: float, e: float, f: float, g: float, h: float) -> *Projective { - m = malloc(64) as *Projective + m = malloc(sizeof(Projective)) as *Projective m.a = a m.b = b m.c = c diff --git a/vg/geom/vec3.ae b/vg/geom/vec3.ae index f6c8ea58..5d250269 100644 --- a/vg/geom/vec3.ae +++ b/vg/geom/vec3.ae @@ -24,7 +24,7 @@ struct V3 { } v3_new(x: float, y: float, z: float) -> *V3 { - v = malloc(24) as *V3 + v = malloc(sizeof(V3)) as *V3 v.x = x v.y = y v.z = z diff --git a/vg/grammar/animations.ae b/vg/grammar/animations.ae index 5cfd2eed..c35f2976 100644 --- a/vg/grammar/animations.ae +++ b/vg/grammar/animations.ae @@ -66,7 +66,7 @@ struct AnimationHandle { } mk_handle(id: int) -> *AnimationHandle { - h = malloc(16) as *AnimationHandle + h = malloc(sizeof(AnimationHandle)) as *AnimationHandle h.id = id h.stopped = 0 h.completed = 0 @@ -99,7 +99,7 @@ mk_active(handle: ptr, element: ptr, tick_fn: ptr, start_time_ms: long, duration_ms: long, loop: int, yoyo: int) -> *ActiveAnimation { // 3 ptr + 2 long + 2 int + padding → 56-64 bytes; 80 is safe. - a = malloc(80) as *ActiveAnimation + a = malloc(sizeof(ActiveAnimation)) as *ActiveAnimation a.handle = handle a.element = element a.tick_fn = tick_fn @@ -130,7 +130,7 @@ struct AnimationManager { } mk_manager() -> *AnimationManager { - m = malloc(16) as *AnimationManager + m = malloc(sizeof(AnimationManager)) as *AnimationManager m.animations = list.new() m.next_id = 1 return m diff --git a/vg/grammar/bind.ae b/vg/grammar/bind.ae index 528c7ff7..fe03f855 100644 --- a/vg/grammar/bind.ae +++ b/vg/grammar/bind.ae @@ -63,7 +63,7 @@ struct Entry { } mk_entry(item: ptr, el: ptr) -> ptr { - e = malloc(16) as *Entry + e = malloc(sizeof(Entry)) as *Entry e.item = item e.el = el return e @@ -94,7 +94,7 @@ entry_el(p: ptr) -> ptr { e = p as *Entry; return e.el } // trailing-callback syntax only supports one closure per call site. new_region() -> ptr { - r = malloc(40) as *BindingRegion + r = malloc(sizeof(BindingRegion)) as *BindingRegion r.items_cb = null r.render_cb = null r.trackby_cb = null @@ -240,7 +240,7 @@ struct NewSlot { } mk_slot(item: ptr, idx: int) -> ptr { - s = malloc(16) as *NewSlot + s = malloc(sizeof(NewSlot)) as *NewSlot s.item = item s.idx = idx return s diff --git a/vg/grammar/context.ae b/vg/grammar/context.ae index 2b130e99..38091596 100644 --- a/vg/grammar/context.ae +++ b/vg/grammar/context.ae @@ -98,7 +98,7 @@ mapping_new(vb_min_x: float, vb_min_y: float, canvas_width: float, canvas_height: float, scale: float, offset_x: float, offset_y: float, transform_handle: ptr) -> ptr { - m = malloc(80) as *ViewBoxMapping + m = malloc(sizeof(ViewBoxMapping)) as *ViewBoxMapping m.vb_min_x = vb_min_x m.vb_min_y = vb_min_y m.vb_width = vb_width @@ -142,7 +142,7 @@ struct AevgContext { // Construct a context with empty registries and a single identity // transform on the transform stack (mirrors the TS constructor). new_context(mapping_handle: ptr) -> ptr { - c = malloc(96) as *AevgContext + c = malloc(sizeof(AevgContext)) as *AevgContext c.mapping = mapping_handle c.gradients = map.new() c.filters = map.new() diff --git a/vg/grammar/defs.ae b/vg/grammar/defs.ae index 8ac28bb8..f224d1ff 100644 --- a/vg/grammar/defs.ae +++ b/vg/grammar/defs.ae @@ -73,7 +73,7 @@ struct GradientStop { } mk_stop(offset: float, color: string) -> *GradientStop { - s = malloc(24) as *GradientStop + s = malloc(sizeof(GradientStop)) as *GradientStop s.offset = offset s.color = color return s @@ -119,7 +119,7 @@ struct GradientDef { mk_gradient_def() -> *GradientDef { // 1 int + 1 ptr + 12 float + 2 string ≈ 12 + 8 + 96 + 16 = 132 → 192 safe. // (was 9 float / 128 before rx, ry, rot_deg were added) - g = malloc(192) as *GradientDef + g = malloc(sizeof(GradientDef)) as *GradientDef g.type_code = GRADIENT_LINEAR g.stops = list.new() g.x1 = 0.0 @@ -392,7 +392,7 @@ struct FilterDef { mk_filter_def() -> *FilterDef { // 7 float + 1 int + padding ≈ 64 bytes - f = malloc(64) as *FilterDef + f = malloc(sizeof(FilterDef)) as *FilterDef f.region_x = 0.0 - 0.1 f.region_y = 0.0 - 0.1 f.region_w = 1.2 @@ -502,7 +502,7 @@ struct ClipPathShape { mk_clip_shape() -> *ClipPathShape { // 1 int + 7 float ≈ 64 bytes - s = malloc(64) as *ClipPathShape + s = malloc(sizeof(ClipPathShape)) as *ClipPathShape s.kind = 0 s.x = 0.0; s.y = 0.0; s.w = 0.0; s.h = 0.0 s.cx = 0.0; s.cy = 0.0; s.r = 0.0 @@ -523,7 +523,7 @@ struct ClipPathDef { } mk_clip_path_def() -> *ClipPathDef { - cp = malloc(16) as *ClipPathDef + cp = malloc(sizeof(ClipPathDef)) as *ClipPathDef cp.shapes = list.new() return cp } diff --git a/vg/grammar/element.ae b/vg/grammar/element.ae index 3a5d8e8b..fb1e25a1 100644 --- a/vg/grammar/element.ae +++ b/vg/grammar/element.ae @@ -238,7 +238,7 @@ element_new(underlying: ptr) -> ptr { // 1 last_pos_props ptr = 8 // shape_type int + 4 geo floats ≈ 40 with pad // Sum ≈ 320 with padding. malloc(384) keeps a safe margin. - e = malloc(512) as *AevgElement + e = malloc(sizeof(AevgElement)) as *AevgElement e.underlying = underlying e.hit_poly = null e.context = null diff --git a/vg/grammar/events.ae b/vg/grammar/events.ae index bcdb9acd..1b46805f 100644 --- a/vg/grammar/events.ae +++ b/vg/grammar/events.ae @@ -73,7 +73,7 @@ struct EventState { } event_state_new() -> ptr { - s = malloc(48) as *EventState + s = malloc(sizeof(EventState)) as *EventState s.hovered_element = null s.dragged_element = null s.last_drag_x = 0.0 diff --git a/vg/grammar/factories.ae b/vg/grammar/factories.ae index 987e2905..b2191626 100644 --- a/vg/grammar/factories.ae +++ b/vg/grammar/factories.ae @@ -65,7 +65,7 @@ struct AevgOptions { } opts_new() -> ptr { - o = malloc(64) as *AevgOptions + o = malloc(sizeof(AevgOptions)) as *AevgOptions o.view_box = "" o.width = 0.0 o.height = 0.0 @@ -123,7 +123,7 @@ vb4_width(p: ptr) -> float { v = p as *ViewBox4; return v.width } vb4_height(p: ptr) -> float { v = p as *ViewBox4; return v.height } mk_vb4(mnx: float, mny: float, w: float, h: float) -> *ViewBox4 { - v = malloc(32) as *ViewBox4 + v = malloc(sizeof(ViewBox4)) as *ViewBox4 v.min_x = mnx v.min_y = mny v.width = w diff --git a/vg/grammar/rendering.ae b/vg/grammar/rendering.ae index 1685dec7..cf490e9d 100644 --- a/vg/grammar/rendering.ae +++ b/vg/grammar/rendering.ae @@ -199,7 +199,7 @@ stroke_width(p: ptr) -> float { v = p as *StrokeMap; return v.width } stroke_opacity_factor(p: ptr) -> float { v = p as *StrokeMap; return v.opacity_factor } mk_stroke_map(w: float, op: float) -> *StrokeMap { - s = malloc(16) as *StrokeMap + s = malloc(sizeof(StrokeMap)) as *StrokeMap s.width = w s.opacity_factor = op return s diff --git a/vg/grammar/utils.ae b/vg/grammar/utils.ae index 535b312e..4d24aab6 100644 --- a/vg/grammar/utils.ae +++ b/vg/grammar/utils.ae @@ -223,7 +223,7 @@ aspect_align_y(p: ptr) -> string { v = p as *AspectRatio; return v.align_y } aspect_meet_or_slice(p: ptr) -> string { v = p as *AspectRatio; return v.meet_or_slice } mk_aspect(ax: string, ay: string, mos: string) -> *AspectRatio { - p = malloc(48) as *AspectRatio + p = malloc(sizeof(AspectRatio)) as *AspectRatio p.align_x = ax p.align_y = ay p.meet_or_slice = mos @@ -373,7 +373,7 @@ bounds4_max_x(p: ptr) -> float { v = p as *Bounds4; return v.max_x } bounds4_max_y(p: ptr) -> float { v = p as *Bounds4; return v.max_y } mk_bounds4(mnx: float, mny: float, mxx: float, mxy: float) -> *Bounds4 { - p = malloc(32) as *Bounds4 + p = malloc(sizeof(Bounds4)) as *Bounds4 p.min_x = mnx; p.min_y = mny; p.max_x = mxx; p.max_y = mxy return p } diff --git a/vg/module.ae b/vg/module.ae index fc650949..4d5a53e1 100644 --- a/vg/module.ae +++ b/vg/module.ae @@ -176,7 +176,7 @@ struct VgNode { } mk_node(scene: ptr, payload: ptr) -> ptr { - n = malloc(16) as *VgNode + n = malloc(sizeof(VgNode)) as *VgNode n.scene = scene n.payload = payload s = scene as *VgScene @@ -243,7 +243,7 @@ scene_canvas(p: ptr) -> int { s = p as *VgScene; return s.canvas_id } // canvas" (headless / recording). vg_root wraps it as the root VgNode the // shape verbs accept as _ctx. scene_new(ctx: ptr, backend: ptr) -> ptr { - s = malloc(160) as *VgScene + s = malloc(sizeof(VgScene)) as *VgScene s.ctx = ctx s.backend = backend s.canvas_id = 0 @@ -640,7 +640,7 @@ group_end(_ctx: ptr, alpha: float) { record_group_marker(scene: ptr, kind: int, alpha: float) { s = scene as *VgScene if s.pending == null { s.pending = list.new() } - pend = malloc(80) as *VgPending + pend = malloc(sizeof(VgPending)) as *VgPending pend.kind = kind pend.attrs = null pend.element = element.element_new(null) @@ -949,7 +949,7 @@ tween_opacity(scene: ptr, el: ptr, from: float, to: float, ms: long, ease: int) record_shape(scene: ptr, kind: int, attrs: ptr) -> ptr { s = scene as *VgScene el = element.element_new(null) - p = malloc(72) as *VgPending + p = malloc(sizeof(VgPending)) as *VgPending p.kind = kind p.attrs = attrs p.element = el @@ -962,7 +962,7 @@ record_shape(scene: ptr, kind: int, attrs: ptr) -> ptr { record_text(scene: ptr, x: float, y: float, size: float, anchor: string, content: string) -> ptr { s = scene as *VgScene el = element.element_new(null) - p = malloc(80) as *VgPending + p = malloc(sizeof(VgPending)) as *VgPending p.kind = 4 p.attrs = null p.element = el diff --git a/vg/raster/rasterize.ae b/vg/raster/rasterize.ae index dbe922e1..6225e37f 100644 --- a/vg/raster/rasterize.ae +++ b/vg/raster/rasterize.ae @@ -59,7 +59,7 @@ struct RGBA { } mk_rgba(r: int, g: int, b: int, a: int) -> *RGBA { - p = malloc(32) as *RGBA + p = malloc(sizeof(RGBA)) as *RGBA p.r = r p.g = g p.b = b @@ -294,7 +294,7 @@ fill_circle_in_buffer(buf: ptr, buf_w: int, buf_h: int, struct IntCell { v: int } mk_cell(v: int) -> *IntCell { - c = malloc(8) as *IntCell + c = malloc(sizeof(IntCell)) as *IntCell c.v = v return c } diff --git a/vg/raster/render_as_raster.ae b/vg/raster/render_as_raster.ae index 542a72ea..61c869d5 100644 --- a/vg/raster/render_as_raster.ae +++ b/vg/raster/render_as_raster.ae @@ -359,7 +359,7 @@ render_shadow_path(ctx: ptr, backend: ptr, kind: int, if cbb < 0 { } b64 = utils.base64_encode_bytes(enc, pc) - c = malloc(96) as *ShadowCache + c = malloc(sizeof(ShadowCache)) as *ShadowCache c.b64 = b64 c.out_w = buf_w c.out_h = buf_h diff --git a/vg/region.ae b/vg/region.ae index 2c287538..3c519f2f 100644 --- a/vg/region.ae +++ b/vg/region.ae @@ -84,7 +84,7 @@ struct LiveRegion { // in a 30×20 viewBox region). ext_* are floats (viewBox), pixel_* ints. live_raster_new(x: float, y: float, ext_w: float, ext_h: float, pixel_w: int, pixel_h: int) -> ptr { - r = malloc(112) as *LiveRegion + r = malloc(sizeof(LiveRegion)) as *LiveRegion r.kind = 0 r.x = x r.y = y @@ -156,7 +156,7 @@ frame_finish(buf: ptr, w: int, h: int) -> string { // draw_fn is a fn(region, t); the fn→ptr coercion at this boundary boxes it // into the ptr field (the grammar_events/grammar_bind handler-storage pattern). live_draw_new(x: float, y: float, w: float, h: float, draw_fn: fn) -> ptr { - r = malloc(112) as *LiveRegion + r = malloc(sizeof(LiveRegion)) as *LiveRegion r.kind = 1 r.x = x r.y = y diff --git a/vg/svg/normalizer.ae b/vg/svg/normalizer.ae index 475a4840..8597e270 100644 --- a/vg/svg/normalizer.ae +++ b/vg/svg/normalizer.ae @@ -136,14 +136,14 @@ cmd_args(handle: ptr) -> ptr { } mk_path_cmd(type_code: int, args: ptr) -> *PathCommand { - c = malloc(16) as *PathCommand + c = malloc(sizeof(PathCommand)) as *PathCommand c.type = type_code c.args = args return c } mk_norm_cmd(type_code: int, args: ptr) -> *NormalizedCommand { - c = malloc(16) as *NormalizedCommand + c = malloc(sizeof(NormalizedCommand)) as *NormalizedCommand c.type = type_code c.args = args return c @@ -282,7 +282,7 @@ struct NormState { } mk_state() -> *NormState { - s = malloc(72) as *NormState + s = malloc(sizeof(NormState)) as *NormState s.cx = 0.0 s.cy = 0.0 s.sx = 0.0 diff --git a/vg/svg/parser.ae b/vg/svg/parser.ae index d1340806..dcebd873 100644 --- a/vg/svg/parser.ae +++ b/vg/svg/parser.ae @@ -49,7 +49,7 @@ struct SvgNode { } mk_node(tag: string) -> *SvgNode { - n = malloc(40) as *SvgNode + n = malloc(sizeof(SvgNode)) as *SvgNode n.tag = tag n.attrs = map.new() n.children = list.new() @@ -274,7 +274,7 @@ struct Token { } mk_token(kind: int) -> *Token { - t = malloc(40) as *Token + t = malloc(sizeof(Token)) as *Token t.kind = kind t.tag = "" t.attrs = null diff --git a/vg/svg/transpiler.ae b/vg/svg/transpiler.ae index 9b5daaa1..dc094699 100644 --- a/vg/svg/transpiler.ae +++ b/vg/svg/transpiler.ae @@ -273,7 +273,7 @@ emit_children(sb: ptr, node: ptr, indent: int) { // gap shared with the surface form). A counter box names the vars. emit_children_explicit(sb: ptr, root: ptr, indent: int) { - ctr = malloc(16) as *VarCounter + ctr = malloc(sizeof(VarCounter)) as *VarCounter ctr.n = 0 ctr.root = root // for href→node resolution emit_kids_explicit(sb, root, indent, ctr) diff --git a/vg/test/test_backend_dispatch.ae b/vg/test/test_backend_dispatch.ae index 9a5a3b35..d620b4ec 100644 --- a/vg/test/test_backend_dispatch.ae +++ b/vg/test/test_backend_dispatch.ae @@ -39,14 +39,14 @@ struct Tag { } mk_rec() -> ptr { - r = malloc(16) as *Rec + r = malloc(sizeof(Rec)) as *Rec r.ops = list.new() r.last_opts = null return r } mk_tag(op: int) -> ptr { - t = malloc(8) as *Tag + t = malloc(sizeof(Tag)) as *Tag t.op = op return t } diff --git a/vg/test/test_grammar_animations.ae b/vg/test/test_grammar_animations.ae index fbdfff46..08766885 100644 --- a/vg/test/test_grammar_animations.ae +++ b/vg/test/test_grammar_animations.ae @@ -42,7 +42,7 @@ struct Recorder { } mk_recorder() -> *Recorder { - r = malloc(16) as *Recorder + r = malloc(sizeof(Recorder)) as *Recorder r.last_t = 0.0 - 1.0 r.call_count = 0 return r diff --git a/vg/test/test_grammar_bind.ae b/vg/test/test_grammar_bind.ae index 93698fef..a28bfb96 100644 --- a/vg/test/test_grammar_bind.ae +++ b/vg/test/test_grammar_bind.ae @@ -51,7 +51,7 @@ struct DataItem { } mk_item(id: string, value: int) -> ptr { - d = malloc(16) as *DataItem + d = malloc(sizeof(DataItem)) as *DataItem d.id = string.copy(id) d.value = value return d diff --git a/vg/test/test_grammar_events.ae b/vg/test/test_grammar_events.ae index e041303b..95b7df0d 100644 --- a/vg/test/test_grammar_events.ae +++ b/vg/test/test_grammar_events.ae @@ -43,7 +43,7 @@ struct Recorder { } mk_recorder() -> *Recorder { - r = malloc(48) as *Recorder + r = malloc(sizeof(Recorder)) as *Recorder r.n_calls = 0 r.last_x = 0.0 r.last_y = 0.0 diff --git a/vg/test/test_grammar_interaction.ae b/vg/test/test_grammar_interaction.ae index d28184ce..e50b2194 100644 --- a/vg/test/test_grammar_interaction.ae +++ b/vg/test/test_grammar_interaction.ae @@ -51,12 +51,12 @@ mk_tracked(ctx: ptr, x: float, y: float, w: float, h: float) -> ptr { } struct Recorder { n_calls: int last_hovered: int } -mk_recorder() -> *Recorder { r = malloc(16) as *Recorder; r.n_calls = 0; r.last_hovered = 0 - 1; return r } +mk_recorder() -> *Recorder { r = malloc(sizeof(Recorder)) as *Recorder; r.n_calls = 0; r.last_hovered = 0 - 1; return r } // Captured key strings. A closure stores via string.copy (the struct-string // idiom from test_grammar_bind) so the value survives past the handler. struct KeyLog { down: string up: string } -mk_keylog() -> *KeyLog { k = malloc(16) as *KeyLog; k.down = ""; k.up = ""; return k } +mk_keylog() -> *KeyLog { k = malloc(sizeof(KeyLog)) as *KeyLog; k.down = ""; k.up = ""; return k } main() { println("=== test_grammar_interaction ===") diff --git a/vg/test/test_live_region.ae b/vg/test/test_live_region.ae index 3969fefd..36d1c9ac 100644 --- a/vg/test/test_live_region.ae +++ b/vg/test/test_live_region.ae @@ -46,7 +46,7 @@ struct Rec { calls: int last_t: float drew: int } // slack and nothing complains; macOS's allocator uses a 16-byte bin and the // write corrupts the free list, aborting the process several statements later. // A real heap overflow that only one libc was kind enough to hide. -mk_rec() -> *Rec { r = malloc(24) as *Rec; r.calls = 0; r.last_t = 0.0; r.drew = 0; return r } +mk_rec() -> *Rec { r = malloc(sizeof(Rec)) as *Rec; r.calls = 0; r.last_t = 0.0; r.drew = 0; return r } main() { println("=== test_live_region ===") diff --git a/vg/test/test_refresh.ae b/vg/test/test_refresh.ae index 856ce67f..f2f0995d 100644 --- a/vg/test/test_refresh.ae +++ b/vg/test/test_refresh.ae @@ -37,7 +37,7 @@ mk_test_ctx() -> ptr { struct DataItem { id: string } mk_item(id: string) -> ptr { - d = malloc(8) as *DataItem + d = malloc(sizeof(DataItem)) as *DataItem d.id = string.copy(id) return d } diff --git a/vg/test/test_vg_bindpos.ae b/vg/test/test_vg_bindpos.ae index a804b9ae..dee9cf9c 100644 --- a/vg/test/test_vg_bindpos.ae +++ b/vg/test/test_vg_bindpos.ae @@ -43,7 +43,7 @@ mk_ctx() -> ptr { // Mutable position the bind_pos closure reads. struct Pos { cx: float cy: float } -mk_pos(x: float, y: float) -> *Pos { p = malloc(16) as *Pos; p.cx = x; p.cy = y; return p } +mk_pos(x: float, y: float) -> *Pos { p = malloc(sizeof(Pos)) as *Pos; p.cx = x; p.cy = y; return p } main() { println("=== test_vg_bindpos ===") diff --git a/vg/test/test_vg_bindto.ae b/vg/test/test_vg_bindto.ae index 98d68692..d0f90c43 100644 --- a/vg/test/test_vg_bindto.ae +++ b/vg/test/test_vg_bindto.ae @@ -37,7 +37,7 @@ mk_ctx() -> ptr { // Data item: an id string + a cx (so each renders at a distinct spot). struct Item { id: string cx: float } mk_item(id: string, cx: float) -> *Item { - it = malloc(16) as *Item + it = malloc(sizeof(Item)) as *Item it.id = string.copy(id) it.cx = cx return it diff --git a/vg/test/test_vg_clock.ae b/vg/test/test_vg_clock.ae index 65215ae2..35a98023 100644 --- a/vg/test/test_vg_clock.ae +++ b/vg/test/test_vg_clock.ae @@ -35,7 +35,7 @@ mk_ctx() -> ptr { // The clock's current hour (0..12), mutable — the "poll" source. struct Clock { hour: float } -mk_clock(h: float) -> *Clock { c = malloc(8) as *Clock; c.hour = h; return c } +mk_clock(h: float) -> *Clock { c = malloc(sizeof(Clock)) as *Clock; c.hour = h; return c } // Hour-hand endpoint for `hour`: center (50,50), length 30, 12-o'clock is up // (angle measured clockwise from +Y up). x2 = 50 + 30*sin(θ), y2 = 50 - 30*cos(θ) diff --git a/vg/test/test_vg_when.ae b/vg/test/test_vg_when.ae index 8961e530..01efbe9d 100644 --- a/vg/test/test_vg_when.ae +++ b/vg/test/test_vg_when.ae @@ -35,7 +35,7 @@ mk_ctx() -> ptr { // A mutable flag a when() closure reads — flip it, refresh, and the element's // visibility follows. struct Flag { on: int } -mk_flag(v: int) -> *Flag { f = malloc(8) as *Flag; f.on = v; return f } +mk_flag(v: int) -> *Flag { f = malloc(sizeof(Flag)) as *Flag; f.on = v; return f } main() { println("=== test_vg_when ===")