🐛 Bug Report
A storage vector .get(...) used as an expression statement can compile away evaluation of its index expression.
If the index expression should fail, the generated Aleo finalizer can still succeed. In the reproducer below, id_numbers.get(1u32 / x); should fail for x = 0u32, but the generated artifact omits the division and the @should_fail test succeeds.
Affected upstream commit: 4f2aa458190b53b055e606ba1b432ed2dd232ddc
Fork CI repro:
https://github.com/Kuhai9801/leo/actions/runs/27871600925
This appears related to the storage-vector lowering / DCE boundary. Vector::get is treated as non-discardable at the intrinsic level, but after lowering the unused expression statement can lose the reconstructed key expression while leaving only a length read in the finalizer.
Steps to Reproduce
Code snippet to reproduce
program vector_get_stmt_drop.aleo {
storage id_numbers: [u64];
fn dropped_get_key(public x: u32) -> Final {
return final {
id_numbers.get(1u32 / x);
};
}
@noupgrade
constructor() {}
}
Test:
import vector_get_stmt_drop.aleo;
program test_vector_get_stmt_drop.aleo {
@test
@should_fail
fn divzero_get() -> Final {
let f: Final = vector_get_stmt_drop.aleo::dropped_get_key(0u32);
return final {
f.run();
};
}
@noupgrade
constructor() {}
}
Run:
Stack trace & error message
0 / 1 tests passed.
FAILED: test_vector_get_stmt_drop.aleo/divzero_get | Test succeeded when failure was expected.
Error [ECLI0377046]: 1 out of 1 tests failed
The generated Aleo finalizer omits the division in the vector-get key expression:
finalize dropped_get_key:
input r0 as u32.public;
get.or_use id_numbers__len__[false] 0u32 into r1;
There is no instruction corresponding to 1u32 / x.
Expected Behavior
The source expression id_numbers.get(1u32 / x); should still evaluate the index expression even when the returned option is unused.
For x = 0u32, the finalizer should fail, or the compiler should reject the source if it cannot preserve that behavior. The generated artifact should not silently turn a failing source expression into a successful finalizer.
Your Environment
- Leo Version:
leo 4.3.0, built from fork branch with no compiler source changes
- Affected commit:
4f2aa458190b53b055e606ba1b432ed2dd232ddc
- Rust Version: stable
- OS: GitHub Actions
ubuntu-latest
🐛 Bug Report
A storage vector
.get(...)used as an expression statement can compile away evaluation of its index expression.If the index expression should fail, the generated Aleo finalizer can still succeed. In the reproducer below,
id_numbers.get(1u32 / x);should fail forx = 0u32, but the generated artifact omits the division and the@should_failtest succeeds.Affected upstream commit:
4f2aa458190b53b055e606ba1b432ed2dd232ddcFork CI repro:
https://github.com/Kuhai9801/leo/actions/runs/27871600925
This appears related to the storage-vector lowering / DCE boundary.
Vector::getis treated as non-discardable at the intrinsic level, but after lowering the unused expression statement can lose the reconstructed key expression while leaving only a length read in the finalizer.Steps to Reproduce
Code snippet to reproduce
Test:
Run:
leo testStack trace & error message
The generated Aleo finalizer omits the division in the vector-get key expression:
There is no instruction corresponding to
1u32 / x.Expected Behavior
The source expression
id_numbers.get(1u32 / x);should still evaluate the index expression even when the returned option is unused.For
x = 0u32, the finalizer should fail, or the compiler should reject the source if it cannot preserve that behavior. The generated artifact should not silently turn a failing source expression into a successful finalizer.Your Environment
leo 4.3.0, built from fork branch with no compiler source changes4f2aa458190b53b055e606ba1b432ed2dd232ddcubuntu-latest