Skip to content

Commit d1fcef0

Browse files
committed
Document rvalue-match divergent return unwind gap and add match-arm drop test.
Note the bare-C-return path in emit_match_arm_result_from_stmts and finding-ion-bugs hotspots, add outer_return_drops_after_inner_match_arm_return, and regenerate text_summary.c.
1 parent 46621d1 commit d1fcef0

3 files changed

Lines changed: 123 additions & 72 deletions

File tree

.cursor/skills/finding-ion-bugs/references/bug-hotspots.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CLI errors use `TypeCheckError` Debug form (`UseAfterMove { ... }`). LSP reforma
1313
## Codegen (`src/cgen/`)
1414

1515
- Drop order and `ion_drop_*` for moved fields
16+
- **Return unwind**: statement `return` uses `scope_emit_return_unwind` in `generate_stmt` (`if`/`while`/`loop`/statement `match`/`unsafe`). Rvalue `match` diverging arms use `emit_match_arm_result_from_stmts`, which emits bare C `return` without unwind; tc only (`test_match_arm_divergent_rvalue.ion`). Open: owned bindings live at a diverging rvalue arm may leak; unconfirmed.
1617
- `Box`, `Vec`, `String` layout vs `runtime/ion_runtime.h`
1718
- Single-file merge (`merge_modules`) vs `--mode multi` divergences
1819
- `extern "C"` calls only inside `unsafe` blocks in source; cgen must not strip guards

examples/text_summary/text_summary.c

Lines changed: 72 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ extern int write(int fd, uint8_t* buf, int count);
4949
TextCounts summarize_text(ion_string_t* ION_MAYBE_UNUSED text);
5050
int main(void);
5151
ReadResult fs_read_to_string_result(ion_string_t* ION_MAYBE_UNUSED path);
52+
void fmt_print_int(int ION_MAYBE_UNUSED n);
53+
void fmt_println_int(int ION_MAYBE_UNUSED n);
54+
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n);
5255
void io_print(ion_string_t* ION_MAYBE_UNUSED s);
5356
void io_println(ion_string_t* ION_MAYBE_UNUSED s);
5457
void io_print_str(uint8_t* ION_MAYBE_UNUSED s, int ION_MAYBE_UNUSED len);
5558
void io_print_int(int ION_MAYBE_UNUSED n);
56-
void fmt_print_int(int ION_MAYBE_UNUSED n);
57-
void fmt_println_int(int ION_MAYBE_UNUSED n);
58-
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n);
5959
TextCounts summarize_text(ion_string_t* ION_MAYBE_UNUSED text) {
6060
TextCounts ret_val = {0};
6161
int lines = 0;
@@ -162,6 +162,73 @@ ReadResult fs_read_to_string_result(ion_string_t* ION_MAYBE_UNUSED path) {
162162
return ret_val;
163163
}
164164

165+
void fmt_print_int(int ION_MAYBE_UNUSED n) {
166+
io_print_int(n);
167+
goto epilogue;
168+
epilogue:
169+
return;
170+
}
171+
172+
void fmt_println_int(int ION_MAYBE_UNUSED n) {
173+
io_print_int(n);
174+
{
175+
int _newline = write(1, (uint8_t*)"\n", 1);
176+
(void)_newline;
177+
}
178+
goto epilogue;
179+
epilogue:
180+
return;
181+
}
182+
183+
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n) {
184+
ion_string_t* ret_val = 0;
185+
uint8_t buf[12] = {0};
186+
int len = 0;
187+
int negative = 0;
188+
int value = n;
189+
ion_string_t* result = ion_string_new();
190+
if (value == 0) {
191+
ret_val = ion_string_from_literal("0", 1);
192+
(void)result;
193+
(void)negative;
194+
(void)len;
195+
(void)buf;
196+
if (result) { ion_string_free(result); }
197+
goto epilogue;
198+
}
199+
if (value < 0) {
200+
negative = 1;
201+
if (value == (-2147483648)) {
202+
ret_val = ion_string_from_literal("-2147483648", 11);
203+
(void)result;
204+
(void)negative;
205+
(void)len;
206+
(void)buf;
207+
if (result) { ion_string_free(result); }
208+
goto epilogue;
209+
}
210+
value = (0 - value);
211+
}
212+
while (value > 0) {
213+
int digit = (value % 10);
214+
buf[len] = (uint8_t)(48 + digit);
215+
len = (len + 1);
216+
value = (value / 10);
217+
}
218+
if (negative) {
219+
ion_string_push_byte(result, (unsigned char)((uint8_t)45));
220+
}
221+
int i = (len - 1);
222+
while (i >= 0) {
223+
ion_string_push_byte(result, (unsigned char)(({ int __ion_idx_1 = i; (__ion_idx_1 >= 0 && __ion_idx_1 < 12) ? buf[__ion_idx_1] : (ion_panic("Array index out of bounds"), buf[0]); })));
224+
i = (i - 1);
225+
}
226+
ret_val = result;
227+
goto epilogue;
228+
epilogue:
229+
return ret_val;
230+
}
231+
165232
void io_print(ion_string_t* ION_MAYBE_UNUSED s) {
166233
{
167234
int _result = write(1, s->data, (int)s->len);
@@ -236,8 +303,8 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
236303
}
237304
int i = 0;
238305
while (i < (len / 2)) {
239-
uint8_t tmp = ({ int __ion_idx_1 = i; (__ion_idx_1 >= 0 && __ion_idx_1 < 12) ? buf[__ion_idx_1] : (ion_panic("Array index out of bounds"), buf[0]); });
240-
buf[i] = ({ int __ion_idx_2 = ((len - 1) - i); (__ion_idx_2 >= 0 && __ion_idx_2 < 12) ? buf[__ion_idx_2] : (ion_panic("Array index out of bounds"), buf[0]); });
306+
uint8_t tmp = ({ int __ion_idx_2 = i; (__ion_idx_2 >= 0 && __ion_idx_2 < 12) ? buf[__ion_idx_2] : (ion_panic("Array index out of bounds"), buf[0]); });
307+
buf[i] = ({ int __ion_idx_3 = ((len - 1) - i); (__ion_idx_3 >= 0 && __ion_idx_3 < 12) ? buf[__ion_idx_3] : (ion_panic("Array index out of bounds"), buf[0]); });
241308
buf[((len - 1) - i)] = tmp;
242309
i = (i + 1);
243310
}
@@ -256,70 +323,3 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
256323
return;
257324
}
258325

259-
void fmt_print_int(int ION_MAYBE_UNUSED n) {
260-
io_print_int(n);
261-
goto epilogue;
262-
epilogue:
263-
return;
264-
}
265-
266-
void fmt_println_int(int ION_MAYBE_UNUSED n) {
267-
io_print_int(n);
268-
{
269-
int _newline = write(1, (uint8_t*)"\n", 1);
270-
(void)_newline;
271-
}
272-
goto epilogue;
273-
epilogue:
274-
return;
275-
}
276-
277-
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n) {
278-
ion_string_t* ret_val = 0;
279-
uint8_t buf[12] = {0};
280-
int len = 0;
281-
int negative = 0;
282-
int value = n;
283-
ion_string_t* result = ion_string_new();
284-
if (value == 0) {
285-
ret_val = ion_string_from_literal("0", 1);
286-
(void)result;
287-
(void)negative;
288-
(void)len;
289-
(void)buf;
290-
if (result) { ion_string_free(result); }
291-
goto epilogue;
292-
}
293-
if (value < 0) {
294-
negative = 1;
295-
if (value == (-2147483648)) {
296-
ret_val = ion_string_from_literal("-2147483648", 11);
297-
(void)result;
298-
(void)negative;
299-
(void)len;
300-
(void)buf;
301-
if (result) { ion_string_free(result); }
302-
goto epilogue;
303-
}
304-
value = (0 - value);
305-
}
306-
while (value > 0) {
307-
int digit = (value % 10);
308-
buf[len] = (uint8_t)(48 + digit);
309-
len = (len + 1);
310-
value = (value / 10);
311-
}
312-
if (negative) {
313-
ion_string_push_byte(result, (unsigned char)((uint8_t)45));
314-
}
315-
int i = (len - 1);
316-
while (i >= 0) {
317-
ion_string_push_byte(result, (unsigned char)(({ int __ion_idx_3 = i; (__ion_idx_3 >= 0 && __ion_idx_3 < 12) ? buf[__ion_idx_3] : (ion_panic("Array index out of bounds"), buf[0]); })));
318-
i = (i - 1);
319-
}
320-
ret_val = result;
321-
goto epilogue;
322-
epilogue:
323-
return ret_val;
324-
}
325-

src/cgen/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3790,6 +3790,13 @@ impl Codegen {
37903790
self.emit_match_arm_result_from_stmts(&body.statements, 0, result_var, result_type);
37913791
}
37923792

3793+
/// Lower rvalue-match arm statements into assignments / control flow inside a `switch`.
3794+
///
3795+
/// Statement-level `return` goes through `generate_stmt` and `scope_emit_return_unwind`
3796+
/// before `goto epilogue`. This path emits a bare C `return` for diverging arms and does
3797+
/// not run owned-value unwind. Type-check coverage only: `test_match_arm_divergent_rvalue.ion`.
3798+
/// Open question: if a diverging arm can leave live owned bindings (channel, String, etc.)
3799+
/// in scope, this may leak; not confirmed and not the statement-return unwind bug.
37933800
fn emit_match_arm_result_from_stmts(
37943801
&mut self,
37953802
stmts: &[IRStmt],
@@ -5240,6 +5247,49 @@ fn main() -> int { return 0; }"#;
52405247
);
52415248
}
52425249

5250+
#[test]
5251+
fn outer_return_drops_after_inner_match_arm_return() {
5252+
let src = r#"enum Opt {
5253+
Some(int);
5254+
None;
5255+
}
5256+
5257+
fn main() -> int {
5258+
let (tx, rx): (Sender<int>, Receiver<int>) = channel<int>();
5259+
let mut rx_mut: Receiver<int> = rx;
5260+
let x: Opt = Opt::Some(1);
5261+
match x {
5262+
Opt::Some(v) => {
5263+
if v == 99 {
5264+
return 0;
5265+
}
5266+
},
5267+
Opt::None => {
5268+
let _pad: int = 0;
5269+
},
5270+
};
5271+
return 1;
5272+
}"#;
5273+
let tokens = crate::lexer::Lexer::new(src).tokenize().unwrap();
5274+
let program = crate::parser::Parser::new(tokens).parse().unwrap();
5275+
let ir = crate::ir::IRBuilder::build(&program);
5276+
let mut cg = Codegen::new();
5277+
let c = cg.generate(&ir, "test.ion");
5278+
let outer = c
5279+
.split("ret_val = 1;")
5280+
.nth(1)
5281+
.and_then(|tail| tail.split("goto epilogue;").next())
5282+
.unwrap_or("");
5283+
assert!(
5284+
outer.contains("ion_channel_handle_drop(tx.channel)"),
5285+
"expected outer return after match arm to drop sender in:\n{c}"
5286+
);
5287+
assert!(
5288+
outer.contains("ion_channel_handle_drop(rx_mut.channel)"),
5289+
"expected outer return after match arm to drop receiver in:\n{c}"
5290+
);
5291+
}
5292+
52435293
#[test]
52445294
fn underscore_binding_silenced_once() {
52455295
let src = r#"extern "C" {

0 commit comments

Comments
 (0)