Skip to content

Commit 1c3473a

Browse files
fixed a bug where a single value enum whould crash in runtime (#1643)
1 parent 795c240 commit 1c3473a

4 files changed

Lines changed: 122 additions & 13 deletions

File tree

src/values.rs

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,8 @@ impl Value {
322322
}
323323
}
324324
Self::Enum { tag, value, .. } => {
325-
if let CoreTypeConcrete::Enum(info) = Self::resolve_type(ty, registry)? {
325+
let resolved_ty = Self::resolve_type(ty, registry)?;
326+
if let CoreTypeConcrete::Enum(info) = resolved_ty {
326327
native_assert!(*tag < info.variants.len(), "Variant index out of range.");
327328

328329
let payload_type_id = &info.variants[*tag];
@@ -336,7 +337,7 @@ impl Value {
336337
let ptr = arena.alloc_layout(layout).cast::<()>().as_ptr();
337338

338339
match tag_layout.size() {
339-
0 => native_panic!("An enum without variants cannot be instantiated."),
340+
0 => {}
340341
1 => *ptr.cast::<u8>() = *tag as u8,
341342
2 => *ptr.cast::<u16>() = *tag as u16,
342343
4 => *ptr.cast::<u32>() = *tag as u32,
@@ -351,8 +352,12 @@ impl Value {
351352
variant_layouts[*tag].size(),
352353
);
353354

354-
// alloc returns a reference so its never null
355-
NonNull::new_unchecked(arena.alloc(ptr) as *mut _).cast()
355+
if resolved_ty.is_memory_allocated(registry)? {
356+
// alloc returns a reference so its never null
357+
NonNull::new_unchecked(arena.alloc(ptr) as *mut _).cast()
358+
} else {
359+
NonNull::new_unchecked(ptr).cast()
360+
}
356361
} else {
357362
Err(Error::UnexpectedValue(format!(
358363
"expected value of type {:?} but got an enum value",
@@ -1490,7 +1495,9 @@ mod test {
14901495
}
14911496

14921497
#[test]
1493-
fn test_to_ptr_enum_no_variant() {
1498+
fn test_to_ptr_single_variant_enum_u8() {
1499+
// A single-variant enum carries no tag: its tag type is `i0`, so
1500+
// `tag_layout.size() == 0` and the payload is written at offset 0.
14941501
let program = ProgramParser::new()
14951502
.parse(
14961503
"type u8 = u8;
@@ -1500,20 +1507,49 @@ mod test {
15001507

15011508
let registry = ProgramRegistry::<CoreType, CoreLibfunc>::new(&program).unwrap();
15021509

1503-
let result = Value::Enum {
1510+
// Keep the arena alive for as long as we dereference `ptr`. `&Bump::new()`
1511+
// would be dropped at the end of the `let` statement, freeing the arena and
1512+
// turning the read below into a use-after-free.
1513+
let arena = Bump::new();
1514+
let ptr = Value::Enum {
15041515
tag: 0,
15051516
value: Box::new(Value::Uint8(10)),
15061517
debug_name: None,
15071518
}
1508-
.to_ptr(&Bump::new(), &registry, &program.type_declarations[1].id)
1509-
.unwrap_err();
1519+
.to_ptr(&arena, &registry, &program.type_declarations[1].id)
1520+
.unwrap();
15101521

1511-
let error = result.to_string().clone();
1512-
let error_msg = error.split("\n").collect::<Vec<&str>>()[0];
1522+
assert_eq!(unsafe { *ptr.cast::<u8>().as_ptr() }, 10);
1523+
}
15131524

1514-
assert_eq!(
1515-
error_msg,
1516-
"An enum without variants cannot be instantiated."
1525+
#[test]
1526+
fn test_to_ptr_single_variant_enum_array() {
1527+
// A single-variant enum such as `enum Wrapper { Only: Array<felt252> }`
1528+
// carries no tag: its tag type is `i0`, so `tag_layout.size() == 0`.
1529+
let program = ProgramParser::new()
1530+
.parse(
1531+
"type felt252 = felt252;
1532+
type Array<felt252> = Array<felt252>;
1533+
type Wrapper = Enum<ut@Wrapper, Array<felt252>>;",
1534+
)
1535+
.unwrap();
1536+
1537+
let registry = ProgramRegistry::<CoreType, CoreLibfunc>::new(&program).unwrap();
1538+
1539+
let result = Value::Enum {
1540+
tag: 0,
1541+
value: Box::new(Value::Array(vec![
1542+
Value::Felt252(Felt::from(1)),
1543+
Value::Felt252(Felt::from(2)),
1544+
Value::Felt252(Felt::from(3)),
1545+
])),
1546+
debug_name: None,
1547+
}
1548+
.to_ptr(&Bump::new(), &registry, &program.type_declarations[2].id);
1549+
1550+
assert!(
1551+
result.is_ok(),
1552+
"single-variant enum must serialize, got: {result:?}"
15171553
);
15181554
}
15191555

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#[derive(Drop)]
2+
enum Wrapper {
3+
Only: felt252,
4+
}
5+
6+
fn run_test(arr: Array<Wrapper>) -> felt252 {
7+
let mut arr = arr;
8+
let mut sum = 0;
9+
loop {
10+
match arr.pop_front() {
11+
Option::Some(w) => {
12+
match w {
13+
Wrapper::Only(v) => { sum += v; },
14+
};
15+
},
16+
Option::None => { break; },
17+
};
18+
};
19+
sum
20+
}

tests/tests/enums.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use crate::common::{compare_outputs, run_native_program, run_vm_program, DEFAULT_GAS};
2+
use cairo_lang_runner::Arg;
3+
use cairo_native::starknet::DummySyscallHandler;
4+
use cairo_native::utils::testing::load_program_and_runner;
5+
use cairo_native::Value;
6+
use starknet_types_core::felt::Felt;
7+
8+
#[test]
9+
fn single_variant_enum_in_array_matches_vm() {
10+
let program = &load_program_and_runner("programs/enum_single_variant_in_array");
11+
12+
let values = [Felt::from(10), Felt::from(20), Felt::from(30)];
13+
14+
let mut vm_array = Vec::new();
15+
for v in values {
16+
vm_array.push(Arg::Value(Felt::from(0)));
17+
vm_array.push(Arg::Value(v));
18+
}
19+
let result_vm = run_vm_program(
20+
program,
21+
"run_test",
22+
vec![Arg::Array(vm_array)],
23+
Some(DEFAULT_GAS as usize),
24+
)
25+
.unwrap();
26+
27+
let result_native = run_native_program(
28+
program,
29+
"run_test",
30+
&[Value::Array(
31+
values
32+
.iter()
33+
.copied()
34+
.map(|v| Value::Enum {
35+
tag: 0,
36+
value: Box::new(Value::Felt252(v)),
37+
debug_name: None,
38+
})
39+
.collect(),
40+
)],
41+
Some(DEFAULT_GAS),
42+
Option::<DummySyscallHandler>::None,
43+
);
44+
45+
compare_outputs(
46+
&program.1,
47+
&program.2.find_function("run_test").unwrap().id,
48+
&result_vm,
49+
&result_native,
50+
)
51+
.expect("single-variant enum in array must agree between VM and native");
52+
}

tests/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod coupon;
99
pub mod dict;
1010
pub mod e2e_libfuncs;
1111
pub mod ec;
12+
pub mod enums;
1213
pub mod felt252;
1314
pub mod int_range;
1415
pub mod libfuncs;

0 commit comments

Comments
 (0)