Skip to content

Commit 736aaac

Browse files
committed
Document rvalue-match divergent return unwind gap and add cgen regression tests.
Add match-arm outer-return drop test, confirm bare C return omits owned unwind on diverging rvalue arms, and note both paths in bug-hotspots.
1 parent d1fcef0 commit 736aaac

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +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.
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; confirmed by `rvalue_match_divergent_return_omits_owned_unwind` (tc rejects the shape in `test_match_arm_divergent_rvalue.ion`).
1717
- `Box`, `Vec`, `String` layout vs `runtime/ion_runtime.h`
1818
- Single-file merge (`merge_modules`) vs `--mode multi` divergences
1919
- `extern "C"` calls only inside `unsafe` blocks in source; cgen must not strip guards

src/cgen/mod.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3795,8 +3795,10 @@ impl Codegen {
37953795
/// Statement-level `return` goes through `generate_stmt` and `scope_emit_return_unwind`
37963796
/// before `goto epilogue`. This path emits a bare C `return` for diverging arms and does
37973797
/// 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.
3798+
/// Open question resolved for this lowering path: diverging arms emit bare C `return`
3799+
/// without owned-value unwind (`rvalue_match_divergent_return_omits_owned_unwind`).
3800+
/// Ion tc rejects mixed diverging/value rvalue arms for user programs
3801+
/// (`test_match_arm_divergent_rvalue.ion`).
38003802
fn emit_match_arm_result_from_stmts(
38013803
&mut self,
38023804
stmts: &[IRStmt],
@@ -5290,6 +5292,68 @@ fn main() -> int {
52905292
);
52915293
}
52925294

5295+
/// Documents a known cgen gap: `emit_match_arm_result_from_stmts` emits bare `return`
5296+
/// without `scope_emit_return_unwind`. Ion tc rejects this shape for user programs
5297+
/// (`test_match_arm_divergent_rvalue.ion`); this test lowers the IR anyway.
5298+
#[test]
5299+
fn rvalue_match_divergent_return_omits_owned_unwind() {
5300+
let src = r#"enum E {
5301+
A(int);
5302+
B(int);
5303+
}
5304+
5305+
fn main() -> int {
5306+
let (tx, rx): (Sender<int>, Receiver<int>) = channel<int>();
5307+
let mut rx_mut: Receiver<int> = rx;
5308+
let s: String = "hi";
5309+
let e: E = E::A(0);
5310+
let n: int = match e {
5311+
E::A(_) => {
5312+
return 1;
5313+
},
5314+
E::B(v) => {
5315+
v;
5316+
},
5317+
};
5318+
return n;
5319+
}"#;
5320+
let tokens = crate::lexer::Lexer::new(src).tokenize().unwrap();
5321+
let program = crate::parser::Parser::new(tokens).parse().unwrap();
5322+
let ir = crate::ir::IRBuilder::build(&program);
5323+
let mut cg = Codegen::new();
5324+
let c = cg.generate(&ir, "test.ion");
5325+
let divergent_arm = c
5326+
.split("case 0:")
5327+
.nth(1)
5328+
.and_then(|tail| tail.split("break;").next())
5329+
.unwrap_or("");
5330+
assert!(
5331+
divergent_arm.contains("return 1;"),
5332+
"expected bare C return in divergent rvalue arm in:\n{c}"
5333+
);
5334+
assert!(
5335+
!divergent_arm.contains("ion_channel_handle_drop"),
5336+
"rvalue divergent arm omits channel unwind (known gap) in:\n{c}"
5337+
);
5338+
assert!(
5339+
!divergent_arm.contains("ion_string_free"),
5340+
"rvalue divergent arm omits String drop (known gap) in:\n{c}"
5341+
);
5342+
let after_match_return = c
5343+
.split("ret_val = n;")
5344+
.nth(1)
5345+
.and_then(|tail| tail.split("goto epilogue;").next())
5346+
.unwrap_or("");
5347+
assert!(
5348+
after_match_return.contains("ion_channel_handle_drop(tx.channel)"),
5349+
"statement return after rvalue match should still unwind in:\n{c}"
5350+
);
5351+
assert!(
5352+
after_match_return.contains("ion_string_free(s)"),
5353+
"statement return after rvalue match should still drop String in:\n{c}"
5354+
);
5355+
}
5356+
52935357
#[test]
52945358
fn underscore_binding_silenced_once() {
52955359
let src = r#"extern "C" {

0 commit comments

Comments
 (0)