Skip to content

[Bug] Unused storage vector get can drop evaluation of a failing index expression #29548

Description

@Kuhai9801

🐛 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:

leo test

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions