unwrapScriptError function
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;
}