Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 48 additions & 39 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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_<field>` 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_<field>` 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_<field>` 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
Expand Down
2 changes: 1 addition & 1 deletion apps/LisMusic/lis_audio.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/LisMusic/lis_config.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/LisMusic/lis_net.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/LisMusic/lis_store.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down
2 changes: 1 addition & 1 deletion apps/aevg_video/aevg_video.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/aevg_video_png/aevg_video_png.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions apps/analog_clock/analog_clock.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions apps/analog_clock_png/analog_clock_png.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion apps/boing/boing.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/falling_blocks/falling_blocks.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/falling_blocks/fb_engine.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions apps/font_picker/font_picker.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions apps/font_picker/picker_engine.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions apps/frames_demo/frames_demo.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion apps/gesture_probe/gesture_probe.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/grand_perspective/gp_layout.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions apps/grand_perspective/gp_model.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/grand_perspective/gp_nav.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/grand_perspective/gp_state.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion apps/inspector/inspector.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/maerkdown/maerkdown.ae
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions apps/maerkdown/mdown.ae
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ 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
return w as 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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading