Skip to content

Commit f7b37ec

Browse files
authored
MoonBit fixes (#1553)
* Fix use-after-free issue in moonbit * Make the moonbit test runner work with the latest compiler * Make existing tests pass including fixed-length list * fmt
1 parent 7544b80 commit f7b37ec

File tree

7 files changed

+275
-23
lines changed

7 files changed

+275
-23
lines changed

crates/moonbit/src/lib.rs

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2369,6 +2369,22 @@ impl Bindgen for FunctionBindgen<'_, '_> {
23692369
}
23702370

23712371
Instruction::Return { amt, .. } => {
2372+
// Bind return operands to locals BEFORE cleanup to avoid
2373+
// use-after-free when operands contain inline loads from
2374+
// return_area or other freed memory.
2375+
let return_locals: Vec<String> = if *amt > 0 {
2376+
operands
2377+
.iter()
2378+
.map(|op| {
2379+
let local = self.locals.tmp("ret");
2380+
uwriteln!(self.src, "let {local} = {op}");
2381+
local
2382+
})
2383+
.collect()
2384+
} else {
2385+
Vec::new()
2386+
};
2387+
23722388
for clean in &self.cleanup {
23732389
let address = &clean.address;
23742390
self.r#gen.ffi_imports.insert(ffi::FREE);
@@ -2377,19 +2393,14 @@ impl Bindgen for FunctionBindgen<'_, '_> {
23772393

23782394
if self.needs_cleanup_list {
23792395
self.r#gen.ffi_imports.insert(ffi::FREE);
2380-
uwrite!(
2381-
self.src,
2382-
"
2383-
cleanup_list.each(mbt_ffi_free)
2384-
",
2385-
);
2396+
uwriteln!(self.src, "cleanup_list.each(mbt_ffi_free)",);
23862397
}
23872398

23882399
match *amt {
23892400
0 => (),
2390-
1 => uwriteln!(self.src, "return {}", operands[0]),
2401+
1 => uwriteln!(self.src, "return {}", return_locals[0]),
23912402
_ => {
2392-
let results = operands.join(", ");
2403+
let results = return_locals.join(", ");
23932404
uwriteln!(self.src, "return ({results})");
23942405
}
23952406
}
@@ -2708,10 +2719,94 @@ impl Bindgen for FunctionBindgen<'_, '_> {
27082719
Instruction::ErrorContextLower { .. }
27092720
| Instruction::ErrorContextLift { .. }
27102721
| Instruction::DropHandle { .. } => todo!(),
2711-
Instruction::FixedLengthListLift { .. } => todo!(),
2712-
Instruction::FixedLengthListLower { .. } => todo!(),
2713-
Instruction::FixedLengthListLowerToMemory { .. } => todo!(),
2714-
Instruction::FixedLengthListLiftFromMemory { .. } => todo!(),
2722+
Instruction::FixedLengthListLift {
2723+
element: _,
2724+
size,
2725+
id: _,
2726+
} => {
2727+
let array = self.locals.tmp("array");
2728+
let mut elements = String::new();
2729+
for a in operands.drain(0..(*size as usize)) {
2730+
elements.push_str(&a);
2731+
elements.push_str(", ");
2732+
}
2733+
uwriteln!(self.src, "let {array} : FixedArray[_] = [{elements}]");
2734+
results.push(array);
2735+
}
2736+
Instruction::FixedLengthListLower {
2737+
element: _,
2738+
size,
2739+
id: _,
2740+
} => {
2741+
for i in 0..(*size as usize) {
2742+
results.push(format!("({})[{i}]", operands[0]));
2743+
}
2744+
}
2745+
Instruction::FixedLengthListLowerToMemory {
2746+
element,
2747+
size: _,
2748+
id: _,
2749+
} => {
2750+
let Block {
2751+
body,
2752+
results: block_results,
2753+
} = self.blocks.pop().unwrap();
2754+
assert!(block_results.is_empty());
2755+
2756+
let vec = operands[0].clone();
2757+
let target = operands[1].clone();
2758+
let size = self.r#gen.r#gen.sizes.size(element).size_wasm32();
2759+
let index = self.locals.tmp("index");
2760+
2761+
uwrite!(
2762+
self.src,
2763+
"
2764+
for {index} = 0; {index} < ({vec}).length(); {index} = {index} + 1 {{
2765+
let iter_elem = ({vec})[{index}]
2766+
let iter_base = ({target}) + ({index} * {size})
2767+
{body}
2768+
}}
2769+
",
2770+
);
2771+
}
2772+
Instruction::FixedLengthListLiftFromMemory {
2773+
element,
2774+
size: fll_size,
2775+
id: _,
2776+
} => {
2777+
let Block {
2778+
body,
2779+
results: block_results,
2780+
} = self.blocks.pop().unwrap();
2781+
let address = &operands[0];
2782+
let array = self.locals.tmp("array");
2783+
let ty = self
2784+
.r#gen
2785+
.r#gen
2786+
.pkg_resolver
2787+
.type_name(self.r#gen.name, element);
2788+
let elem_size = self.r#gen.r#gen.sizes.size(element).size_wasm32();
2789+
let index = self.locals.tmp("index");
2790+
2791+
let result = match &block_results[..] {
2792+
[result] => result,
2793+
_ => todo!("result count == {}", block_results.len()),
2794+
};
2795+
2796+
uwrite!(
2797+
self.src,
2798+
"
2799+
let {array} : Array[{ty}] = []
2800+
for {index} = 0; {index} < {fll_size}; {index} = {index} + 1 {{
2801+
let iter_base = ({address}) + ({index} * {elem_size})
2802+
{body}
2803+
{array}.push({result})
2804+
}}
2805+
",
2806+
);
2807+
2808+
results.push(format!("FixedArray::from_array({array}[:])"));
2809+
}
27152810
}
27162811
}
27172812

@@ -2747,11 +2842,10 @@ impl Bindgen for FunctionBindgen<'_, '_> {
27472842

27482843
if !self.cleanup.is_empty() {
27492844
self.needs_cleanup_list = true;
2750-
self.r#gen.ffi_imports.insert(ffi::FREE);
27512845

27522846
for cleanup in &self.cleanup {
27532847
let address = &cleanup.address;
2754-
uwriteln!(self.src, "mbt_ffi_free({address})",);
2848+
uwriteln!(self.src, "cleanup_list.push({address})",);
27552849
}
27562850
}
27572851

crates/moonbit/src/pkg.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ impl PkgResolver {
203203
}
204204
_ => format!("Array[{}]", self.type_name(this, &ty)),
205205
},
206+
TypeDefKind::FixedLengthList(ty, _size) => {
207+
format!("FixedArray[{}]", self.type_name(this, &ty))
208+
}
206209
TypeDefKind::Tuple(tuple) => {
207210
format!(
208211
"({})",

crates/test/src/moonbit.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ impl LanguageMethods for MoonBit {
6060
}
6161

6262
// Compile the MoonBit bindings to a wasm file
63+
let manifest = compile.bindings_dir.join("moon.mod.json");
6364
let mut cmd = Command::new("moon");
6465
cmd.arg("build")
6566
.arg("--no-strip") // for debugging
66-
.arg("-C")
67-
.arg(compile.bindings_dir);
67+
.arg("--manifest-path")
68+
.arg(&manifest);
6869
runner.run_command(&mut cmd)?;
6970
// Build the component
7071
let artifact = compile
@@ -93,27 +94,28 @@ impl LanguageMethods for MoonBit {
9394

9495
fn should_fail_verify(
9596
&self,
96-
_name: &str,
97+
name: &str,
9798
config: &crate::config::WitConfig,
9899
_args: &[String],
99100
) -> bool {
100-
config.async_
101+
// async-resource-func actually works, but most other async tests
102+
// fail during codegen or verification
103+
config.async_ && name != "async-resource-func.wit"
101104
}
102105

103106
fn verify(&self, runner: &Runner, verify: &crate::Verify) -> anyhow::Result<()> {
107+
let manifest = verify.bindings_dir.join("moon.mod.json");
104108
let mut cmd = Command::new("moon");
105109
cmd.arg("check")
106110
.arg("--warn-list")
107111
.arg("-28")
108112
// .arg("--deny-warn")
109-
.arg("--source-dir")
110-
.arg(verify.bindings_dir);
113+
.arg("--manifest-path")
114+
.arg(&manifest);
111115

112116
runner.run_command(&mut cmd)?;
113117
let mut cmd = Command::new("moon");
114-
cmd.arg("build")
115-
.arg("--source-dir")
116-
.arg(verify.bindings_dir);
118+
cmd.arg("build").arg("--manifest-path").arg(&manifest);
117119

118120
runner.run_command(&mut cmd)?;
119121
Ok(())
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//@ [lang]
2+
//@ path = 'gen/world/runner/stub.mbt'
3+
//@ pkg_config = """{ "import": ["test/list-in-variant/interface/test_/list_in_variant/toTest"] }"""
4+
5+
///|
6+
pub fn run() -> Unit {
7+
// list-in-option
8+
let r1 = @toTest.list_in_option(Some(["hello", "world"]))
9+
guard r1 == "hello,world"
10+
let r2 = @toTest.list_in_option(None)
11+
guard r2 == "none"
12+
13+
// list-in-variant
14+
let r3 = @toTest.list_in_variant(@toTest.PayloadOrEmpty::WithData(["foo", "bar", "baz"]))
15+
guard r3 == "foo,bar,baz"
16+
let r4 = @toTest.list_in_variant(@toTest.PayloadOrEmpty::Empty)
17+
guard r4 == "empty"
18+
19+
// list-in-result
20+
let r5 = @toTest.list_in_result(Ok(["a", "b", "c"]))
21+
guard r5 == "a,b,c"
22+
let r6 = @toTest.list_in_result(Err("oops"))
23+
guard r6 == "err:oops"
24+
25+
// list-in-option-with-return (Bug 1 + Bug 2)
26+
let s1 = @toTest.list_in_option_with_return(Some(["hello", "world"]))
27+
guard s1.count == 2U
28+
guard s1.label == "hello,world"
29+
let s2 = @toTest.list_in_option_with_return(None)
30+
guard s2.count == 0U
31+
guard s2.label == "none"
32+
33+
// top-level-list (contrast)
34+
let r7 = @toTest.top_level_list(["x", "y", "z"])
35+
guard r7 == "x,y,z"
36+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
include!(env!("BINDINGS"));
2+
3+
use crate::test::list_in_variant::to_test::*;
4+
5+
struct Component;
6+
7+
export!(Component);
8+
9+
impl Guest for Component {
10+
fn run() {
11+
// list-in-option (Bug 1: list freed inside match arm before FFI call)
12+
let hw: Vec<String> = ["hello", "world"].into_iter().map(Into::into).collect();
13+
assert_eq!(list_in_option(Some(&hw)), "hello,world");
14+
assert_eq!(list_in_option(None), "none");
15+
16+
// list-in-variant (Bug 1: same pattern with variant)
17+
let fbb = PayloadOrEmpty::WithData(vec!["foo".into(), "bar".into(), "baz".into()]);
18+
assert_eq!(list_in_variant(&fbb), "foo,bar,baz");
19+
assert_eq!(list_in_variant(&PayloadOrEmpty::Empty), "empty");
20+
21+
// list-in-result (Bug 1: same pattern with result)
22+
let abc: Vec<String> = ["a", "b", "c"].into_iter().map(Into::into).collect();
23+
assert_eq!(list_in_result(Ok(&abc)), "a,b,c");
24+
assert_eq!(list_in_result(Err("oops")), "err:oops");
25+
26+
// list-in-option-with-return (Bug 1 + Bug 2: freed list + return_area read-after-free)
27+
let hw2: Vec<String> = ["hello", "world"].into_iter().map(Into::into).collect();
28+
let s = list_in_option_with_return(Some(&hw2));
29+
assert_eq!(s.count, 2);
30+
assert_eq!(s.label, "hello,world");
31+
let s = list_in_option_with_return(None);
32+
assert_eq!(s.count, 0);
33+
assert_eq!(s.label, "none");
34+
35+
// top-level-list (NOT affected — contrast case)
36+
let xyz: Vec<String> = ["x", "y", "z"].into_iter().map(Into::into).collect();
37+
assert_eq!(top_level_list(&xyz), "x,y,z");
38+
}
39+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
include!(env!("BINDINGS"));
2+
3+
use crate::exports::test::list_in_variant::to_test::*;
4+
5+
struct Component;
6+
7+
export!(Component);
8+
9+
impl exports::test::list_in_variant::to_test::Guest for Component {
10+
fn list_in_option(data: Option<Vec<String>>) -> String {
11+
match data {
12+
Some(list) => list.join(","),
13+
None => "none".to_string(),
14+
}
15+
}
16+
17+
fn list_in_variant(data: PayloadOrEmpty) -> String {
18+
match data {
19+
PayloadOrEmpty::WithData(list) => list.join(","),
20+
PayloadOrEmpty::Empty => "empty".to_string(),
21+
}
22+
}
23+
24+
fn list_in_result(data: Result<Vec<String>, String>) -> String {
25+
match data {
26+
Ok(list) => list.join(","),
27+
Err(e) => format!("err:{}", e),
28+
}
29+
}
30+
31+
fn list_in_option_with_return(data: Option<Vec<String>>) -> Summary {
32+
match data {
33+
Some(list) => Summary {
34+
count: list.len() as u32,
35+
label: list.join(","),
36+
},
37+
None => Summary {
38+
count: 0,
39+
label: "none".to_string(),
40+
},
41+
}
42+
}
43+
44+
fn top_level_list(items: Vec<String>) -> String {
45+
items.join(",")
46+
}
47+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package test:list-in-variant;
2+
3+
interface to-test {
4+
list-in-option: func(data: option<list<string>>) -> string;
5+
6+
variant payload-or-empty {
7+
empty,
8+
with-data(list<string>),
9+
}
10+
list-in-variant: func(data: payload-or-empty) -> string;
11+
12+
list-in-result: func(data: result<list<string>, string>) -> string;
13+
14+
record summary {
15+
count: u32,
16+
label: string,
17+
}
18+
list-in-option-with-return: func(data: option<list<string>>) -> summary;
19+
20+
top-level-list: func(items: list<string>) -> string;
21+
}
22+
23+
world test {
24+
export to-test;
25+
}
26+
27+
world runner {
28+
import to-test;
29+
30+
export run: func();
31+
}

0 commit comments

Comments
 (0)