unwrapScriptError function

Object unwrapScriptError(
  1. Object error
)

Recovers the value the script actually threw from the interpreter's internal wrappers — public, because an embedder sometimes has to call it.

Since SCD73 an escaping error is unwrapped before it reaches an embedder on its own: the zone d4rt forks wraps every callback it registers, which covers a Stream.listen handler and a Future.then continuation, and the three Timer adapters unwrap themselves because their bodies are async and so never throw out of the registered callback at all. An error abandoned in an unawaited future is unwrapped by the interpreter's own async machinery before it gets that far.

One route is not covered: a handler passed to Stream.handleError, which the SDK invokes without registering it with the zone, so there is nothing to wrap. Reaching it would need d4rt to own the error zone, which it does only when onUncaughtError is set, and which cannot be made unconditional — Dart refuses to deliver an error across an error-zone boundary, so an ordinary script failure would stop reaching the caller of executeBundle (tracked as sce117). An embedder who prefers their own runZonedGuarded to the hook should therefore pass what they receive through this function; it is a no-op on the shapes that are already unwrapped:

runZonedGuarded(() => runner.executeBundle(...), (error, stack) {
  log(unwrapScriptError(error));
});

Two peels, not one, which is why this is not left to the caller to write: the internal wrapper holds the thrown value, and a bridged exception holds its native object one level further in. A host that peeled only the first would get a BridgedInstance it cannot catch on.

Values that are neither are returned unchanged, so passing a native error through this is harmless.

Implementation

Object unwrapScriptError(Object error) {
  var value = error;
  if (value is InternalInterpreterD4rtException) {
    value = value.originalThrownValue ?? value;
  }
  // A bridged exception's native object is the thing a host can catch on;
  // the BridgedInstance shell means nothing outside the interpreter.
  if (value is BridgedInstance) return value.nativeObject;
  return value;
}