diff --git a/tooling/noir_js/scripts/compile_test_programs.sh b/tooling/noir_js/scripts/compile_test_programs.sh index 738db98bc06..9776a0a5020 100755 --- a/tooling/noir_js/scripts/compile_test_programs.sh +++ b/tooling/noir_js/scripts/compile_test_programs.sh @@ -8,3 +8,5 @@ $NARGO --program-dir ./test/noir_compiled_examples/assert_msg_runtime compile -- $NARGO --program-dir ./test/noir_compiled_examples/fold_fibonacci compile --force --pedantic-solving $NARGO --program-dir ./test/noir_compiled_examples/assert_raw_payload compile --force --pedantic-solving $NARGO --program-dir ./test/noir_compiled_examples/databus compile --force --pedantic-solving +# Compile with no inlining to test runtime call stacks +$NARGO --program-dir ./test/noir_compiled_examples/assert_inside_brillig_nested compile --force --pedantic-solving --inliner-aggressiveness -9223372036854775808 \ No newline at end of file diff --git a/tooling/noir_js/src/debug.ts b/tooling/noir_js/src/debug.ts index 4f915071388..7003bd4abbe 100644 --- a/tooling/noir_js/src/debug.ts +++ b/tooling/noir_js/src/debug.ts @@ -71,13 +71,13 @@ function resolveOpcodeLocations( ); // Adds the acir call stack if the last location is a brillig opcode if (locations.length > 0) { - const runtimeLocations = opcodeLocations[opcodeLocations.length - 1].split('.'); - if (runtimeLocations.length === 2) { - const acirCallstackId = debug.acir_locations[runtimeLocations[0]]; + const decomposedOpcodeLocation = opcodeLocations[opcodeLocations.length - 1].split('.'); + if (decomposedOpcodeLocation.length === 2) { + const acirCallstackId = debug.acir_locations[decomposedOpcodeLocation[0]]; if (acirCallstackId !== undefined) { const callStack = debug.location_tree.locations[acirCallstackId]; const acirCallstack = getCallStackFromLocationNode(callStack, debug.location_tree.locations, files); - locations = locations.concat(acirCallstack); + locations = acirCallstack.concat(locations); } } } @@ -110,7 +110,8 @@ function getCallStackFromLocationNode( callStack = location_tree[callStack.parent]; } - return result; + // Reverse since we explored the child nodes first + return result.reverse(); } /** * Extracts the call stack from the location of a failing opcode and the debug metadata. diff --git a/tooling/noir_js/test/node/execute.test.ts b/tooling/noir_js/test/node/execute.test.ts index f657adefd95..23f7e2d4a24 100644 --- a/tooling/noir_js/test/node/execute.test.ts +++ b/tooling/noir_js/test/node/execute.test.ts @@ -3,6 +3,7 @@ import assert_msg_json from '../noir_compiled_examples/assert_msg_runtime/target import fold_fibonacci_json from '../noir_compiled_examples/fold_fibonacci/target/fold_fibonacci.json' assert { type: 'json' }; import assert_raw_payload_json from '../noir_compiled_examples/assert_raw_payload/target/assert_raw_payload.json' assert { type: 'json' }; import databus_json from '../noir_compiled_examples/databus/target/databus.json' assert { type: 'json' }; +import assert_inside_brillig_nested_json from '../noir_compiled_examples/assert_inside_brillig_nested/target/assert_inside_brillig_nested.json' assert { type: 'json' }; import { Noir, ErrorWithPayload } from '@noir-lang/noir_js'; import { CompiledCircuit } from '@noir-lang/types'; @@ -12,6 +13,7 @@ const assert_lt_program = assert_lt_json as CompiledCircuit; const assert_msg_runtime = assert_msg_json as CompiledCircuit; const fold_fibonacci_program = fold_fibonacci_json as CompiledCircuit; const databus_program = databus_json as CompiledCircuit; +const assert_inside_brillig_nested = assert_inside_brillig_nested_json as CompiledCircuit; it('executes a single-ACIR program correctly', async () => { const inputs = { @@ -40,8 +42,38 @@ it('circuit with a fmt string assert message should fail with the resolved asser } catch (error) { const knownError = error as ErrorWithPayload; expect(knownError.message).to.equal('Circuit execution failed: Expected x < y but got 10 < 5'); - expect(knownError.noirCallStack).to.have.lengthOf(1); - expect(knownError.noirCallStack![0]).to.match(/^at x < y \(.*assert_msg_runtime\/src\/main.nr:3:12\)$/); + } +}); + +it('circuit with a nested assertion should fail with the resolved call stack', async () => { + try { + await new Noir(assert_msg_runtime).execute({ + x: '10', + y: '5', + }); + } catch (error) { + const knownError = error as ErrorWithPayload; + expect(knownError.noirCallStack).to.have.lengthOf(2); + expect(knownError.noirCallStack![0]).to.match( + /^at make_assertion\(x, y\) \(.*assert_msg_runtime\/src\/main.nr:2:5\)$/, + ); + expect(knownError.noirCallStack![1]).to.match(/^at x < y \(.*assert_msg_runtime\/src\/main.nr:7:12\)$/); + } +}); + +it('circuit with a nested assertion inside brillig should fail with the resolved call stack', async () => { + try { + await new Noir(assert_inside_brillig_nested).execute({ + x: '10', + }); + } catch (error) { + const knownError = error as ErrorWithPayload; + const expectedStack = ['acir_wrapper(x)', 'brillig_entrypoint(x)', 'brillig_nested(x)', 'x < 10']; + expect(knownError.noirCallStack).to.have.lengthOf(expectedStack.length); + + for (let i = 0; i < expectedStack.length; i++) { + expect(knownError.noirCallStack![i]).to.contain(expectedStack[i]); + } } }); diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_inside_brillig_nested/Nargo.toml b/tooling/noir_js/test/noir_compiled_examples/assert_inside_brillig_nested/Nargo.toml new file mode 100644 index 00000000000..a96be316b2f --- /dev/null +++ b/tooling/noir_js/test/noir_compiled_examples/assert_inside_brillig_nested/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "assert_inside_brillig_nested" +type = "bin" +authors = [""] +[dependencies] diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_inside_brillig_nested/src/main.nr b/tooling/noir_js/test/noir_compiled_examples/assert_inside_brillig_nested/src/main.nr new file mode 100644 index 00000000000..a121ef0f8a3 --- /dev/null +++ b/tooling/noir_js/test/noir_compiled_examples/assert_inside_brillig_nested/src/main.nr @@ -0,0 +1,17 @@ +fn main(x: u64) { + acir_wrapper(x); +} + +fn acir_wrapper(x: u64) { + unsafe { + brillig_entrypoint(x); + } +} + +unconstrained fn brillig_entrypoint(x: u64) { + brillig_nested(x); +} + +unconstrained fn brillig_nested(x: u64) { + assert(x < 10); +} diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr index 0bcd5c58c24..e9b980afa70 100644 --- a/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr +++ b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr @@ -1,4 +1,8 @@ fn main(x: u64, y: pub u64) { + make_assertion(x, y); +} + +fn make_assertion(x: u64, y: u64) { // A fmtstr assertion message is used to show that noirJS will decode the error payload as a string. assert(x < y, f"Expected x < y but got {x} < {y}"); }