Skip to content

Commit d6aa879

Browse files
finding 2: use JS-provided throw_or_abort_impl for WASM error recovery
In WASM builds, BB_NO_EXCEPTIONS is defined which routes throw_or_abort to std::abort(), killing the WASM process on any error with no recovery. Two changes fix this: C++ side: throw_or_abort_impl now stores the error message in an exported buffer (get_last_error_ptr/get_last_error_length) before aborting. The abort produces a WebAssembly.RuntimeError trap that is catchable in JS. JS side: cbindCall() catches the WASM trap, reads the stored error message via the exported functions, and re-throws as a proper Error with the original message. The WASM instance stays alive. Before: any throw_or_abort in WASM kills the process, PXE crashes. After: the JS caller gets a catchable Error("actual error message").
1 parent e882aec commit d6aa879

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

barretenberg/cpp/src/barretenberg/env/throw_or_abort_impl.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@
55
#include <backward.hpp>
66
#endif
77

8+
#ifdef __wasm__
9+
#include <cstring>
10+
11+
// In WASM builds, store the error message in a global buffer before aborting.
12+
// The JS host catches the WebAssembly.RuntimeError trap and calls
13+
// get_last_error_ptr()/get_last_error_length() to retrieve the message.
14+
static constexpr size_t ERROR_BUF_SIZE = 4096;
15+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
16+
static char last_error_message[ERROR_BUF_SIZE]; // NOLINT(cppcoreguidelines-avoid-c-arrays)
17+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
18+
static size_t last_error_length = 0;
19+
20+
WASM_EXPORT uint32_t get_last_error_length()
21+
{
22+
return static_cast<uint32_t>(last_error_length);
23+
}
24+
25+
WASM_EXPORT const char* get_last_error_ptr()
26+
{
27+
return last_error_message;
28+
}
29+
#endif
30+
831
inline void abort_with_message [[noreturn]] (std::string const& err)
932
{
1033
info("abort: ", err);
@@ -24,6 +47,15 @@ WASM_EXPORT void throw_or_abort_impl [[noreturn]] (const char* err)
2447
#ifndef BB_NO_EXCEPTIONS
2548
throw std::runtime_error(err);
2649
#else
50+
#ifdef __wasm__
51+
// Store error message in exported buffer so JS can read it after the trap.
52+
last_error_length = std::strlen(err);
53+
if (last_error_length >= ERROR_BUF_SIZE) {
54+
last_error_length = ERROR_BUF_SIZE - 1;
55+
}
56+
std::memcpy(last_error_message, err, last_error_length);
57+
last_error_message[last_error_length] = '\0';
58+
#endif
2759
abort_with_message(err);
2860
#endif
2961
}

barretenberg/ts/src/barretenberg_wasm/barretenberg_wasm_main/index.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,27 @@ export class BarretenbergWasmMain extends BarretenbergWasmBase {
222222
view.setUint32(outputPtrLocation, scratchDataPtr, true);
223223
view.setUint32(outputSizeLocation, scratchDataSize, true);
224224

225-
// Call WASM
226-
this.call(cbind, inputPtr, inputBuffer.length, outputPtrLocation, outputSizeLocation);
225+
// Call WASM. If C++ calls throw_or_abort, it stores the error message in
226+
// last_error_message and aborts, producing a WebAssembly.RuntimeError trap.
227+
// We catch the trap and re-throw with the actual error message.
228+
try {
229+
this.call(cbind, inputPtr, inputBuffer.length, outputPtrLocation, outputSizeLocation);
230+
} catch (e) {
231+
// Free custom input buffer before re-throwing
232+
if (needsCustomInputBuffer) {
233+
try {
234+
this.call('bbfree', inputPtr);
235+
} catch {
236+
// WASM state may be inconsistent after trap, ignore cleanup failure
237+
}
238+
}
239+
// Read the error message stored by throw_or_abort_impl before the trap
240+
const errMsg = this.readLastErrorMessage();
241+
if (errMsg) {
242+
throw new Error(errMsg);
243+
}
244+
throw e;
245+
}
227246

228247
// Free custom input buffer if allocated
229248
if (needsCustomInputBuffer) {
@@ -251,6 +270,24 @@ export class BarretenbergWasmMain extends BarretenbergWasmBase {
251270

252271
return encodedResult;
253272
}
273+
274+
/**
275+
* Read the error message stored by throw_or_abort_impl before a WASM trap.
276+
* Returns the message string, or undefined if no message was stored.
277+
*/
278+
private readLastErrorMessage(): string | undefined {
279+
try {
280+
const length = this.call('get_last_error_length');
281+
if (length === 0) {
282+
return undefined;
283+
}
284+
const ptr = this.call('get_last_error_ptr');
285+
const bytes = new Uint8Array(this.getMemory().buffer, ptr, length);
286+
return new TextDecoder().decode(bytes);
287+
} catch {
288+
return undefined;
289+
}
290+
}
254291
}
255292

256293
/**

0 commit comments

Comments
 (0)