| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of dart._runtime; |
| |
| // We need to set these properties while the sdk is only partially initialized |
| // so we cannot use regular Dart fields. |
| // The default values for these properties are set when the global_ final field |
| // in runtime.dart is initialized. |
| |
| argumentError(value) { |
| throw ArgumentError.value(value); |
| } |
| |
| throwUnimplementedError(String message) { |
| throw UnimplementedError(message); |
| } |
| |
| // TODO(nshahan) Cleanup embeded strings and extract file location at runtime |
| // from the stacktrace. |
| assertFailed(String? message, |
| [String? fileUri, int? line, int? column, String? conditionSource]) { |
| throw AssertionErrorImpl(message, fileUri, line, column, conditionSource); |
| } |
| |
| final _nullFailedSet = JS('!', 'new Set()'); |
| |
| String _nullFailedMessage(variableName) => |
| 'A null value was passed into a non-nullable parameter: $variableName.'; |
| |
| // Run-time null safety assertion per: |
| // https://github.com/dart-lang/language/blob/master/accepted/future-releases/nnbd/feature-specification.md#automatic-debug-assertion-insertion |
| nullFailed(String? fileUri, int? line, int? column, String? variable) { |
| if (_nonNullAsserts) { |
| throw AssertionErrorImpl(_nullFailedMessage(variable), fileUri, line, |
| column, '$variable != null'); |
| } |
| var key = '$fileUri:$line:$column'; |
| if (!JS('!', '#.has(#)', _nullFailedSet, key)) { |
| JS('', '#.add(#)', _nullFailedSet, key); |
| _nullWarn(_nullFailedMessage(variable)); |
| } |
| } |
| |
| throwCyclicInitializationError([String? field]) { |
| throw CyclicInitializationError(field); |
| } |
| |
| throwNullValueError() { |
| // TODO(vsm): Per spec, we should throw an NSM here. Technically, we ought |
| // to thread through method info, but that uglifies the code and can't |
| // actually be queried ... it only affects how the error is printed. |
| throw NoSuchMethodError(null, Symbol('<Unexpected Null Value>'), null, null); |
| } |
| |
| castError(obj, expectedType) { |
| var actualType = getReifiedType(obj); |
| var message = _castErrorMessage(actualType, expectedType); |
| throw TypeErrorImpl(message); |
| } |
| |
| String _castErrorMessage(from, to) { |
| // If both types are generic classes, see if we can infer generic type |
| // arguments for `from` that would allow the subtype relation to work. |
| // TODO(#40326) Fix suggested type or remove this code if no longer needed. |
| // var fromClass = getGenericClass(from); |
| // if (fromClass != null) { |
| // var fromTypeFormals = getGenericTypeFormals(fromClass); |
| // var fromType = instantiateClass(fromClass, fromTypeFormals); |
| // var inferrer = _TypeInferrer(fromTypeFormals); |
| // if (inferrer.trySubtypeMatch(fromType, to)) { |
| // var inferredTypes = inferrer.getInferredTypes(); |
| // if (inferredTypes != null) { |
| // var inferred = instantiateClass(fromClass, inferredTypes); |
| // return "Type '${typeName(from)}' should be '${typeName(inferred)}' " |
| // "to implement expected type '${typeName(to)}'."; |
| // } |
| // } |
| // } |
| return "Expected a value of type '${typeName(to)}', " |
| "but got one of type '${typeName(from)}'"; |
| } |
| |
| /// The symbol that references the thrown Dart Object (typically but not |
| /// necessarily an [Error] or [Exception]), used by the [exception] function. |
| final Object _thrownValue = JS('', 'Symbol("_thrownValue")'); |
| |
| /// For a Dart [Error], this provides access to the JS Error object that |
| /// contains the stack trace if the error was thrown. |
| final Object _jsError = JS('', 'Symbol("_jsError")'); |
| |
| /// Gets the thrown Dart Object from an [error] caught by a JS catch. |
| /// |
| /// If the throw originated in Dart, the result will typically be an [Error] |
| /// or [Exception], but it could be any Dart object. |
| /// |
| /// If the throw originated in JavaScript, then there is not a corresponding |
| /// Dart value, so we just return the error object. |
| Object? getThrown(Object? error) { |
| if (error != null) { |
| // Get the Dart thrown value, if any. |
| var value = JS('', '#[#]', error, _thrownValue); |
| if (value != null) return value; |
| } |
| // Otherwise return the original object. |
| return error; |
| } |
| |
| final _stackTrace = JS('', 'Symbol("_stackTrace")'); |
| |
| /// Returns the stack trace from an [error] caught by a JS catch. |
| /// |
| /// If the throw originated in Dart, we should always have JS Error |
| /// (see [throw_]) so we can create a Dart [StackTrace] from that (or return a |
| /// previously created instance). |
| /// |
| /// If the throw originated in JavaScript and was an `Error`, then we can get |
| /// the corresponding stack trace the same way we do for Dart throws. If the |
| /// throw object was not an Error, then we don't have a JS trace, so we create |
| /// one here. |
| StackTrace stackTrace(Object? error) { |
| if (JS<bool>('!', '!(# instanceof Error)', error)) { |
| // We caught something that isn't a JS Error. |
| // |
| // We should only hit this path when a non-Error was thrown from JS. In |
| // case, there is no stack trace available, so create one here. |
| return _StackTrace.missing(error); |
| } |
| |
| // If we've already created the Dart stack trace object, return it. |
| StackTrace? trace = JS('', '#[#]', error, _stackTrace); |
| if (trace != null) return trace; |
| |
| // Otherwise create the Dart stack trace (by parsing the JS stack), and |
| // cache it so we don't repeat the parsing/allocation. |
| return JS('', '#[#] = #', error, _stackTrace, _StackTrace(error)); |
| } |
| |
| StackTrace stackTraceForError(Error error) { |
| return stackTrace(JS('', '#[#]', error, _jsError)); |
| } |
| |
| /// Implements `rethrow` of [error], allowing rethrow in an expression context. |
| /// |
| /// Note: [error] must be the raw JS error caught in the JS catch, not the |
| /// unwrapped value returned by [getThrown]. |
| @JSExportName('rethrow') |
| void rethrow_(Object error) { |
| JS('', 'throw #', error); |
| } |
| |
| /// Subclass of JS `Error` that wraps a thrown Dart object, and evaluates the |
| /// message lazily by calling `toString()` on the wrapped Dart object. |
| /// |
| /// Also creates a pointer from the thrown Dart object to the JS Error |
| /// (via [_jsError]). This is used to implement [Error.stackTrace], but also |
| /// provides a way to recover the stack trace if we lose track of it. |
| /// [Error] requires preserving the original stack trace if an error is |
| /// rethrown, so we only update the pointer if it wasn't already set. |
| /// |
| /// TODO(jmesserly): Dart Errors should simply be JS Errors. |
| final Object DartError = JS( |
| '!', |
| '''class DartError extends Error { |
| constructor(error) { |
| super(); |
| if (error == null) error = #; |
| this[#] = error; |
| if (error != null && typeof error == "object" && error[#] == null) { |
| error[#] = this; |
| } |
| } |
| get message() { |
| return #(this[#]); |
| } |
| }''', |
| NullThrownError(), |
| _thrownValue, |
| _jsError, |
| _jsError, |
| _toString, |
| _thrownValue); |
| |
| /// Subclass of [DartError] for cases where we're rethrowing with a different, |
| /// original Dart StackTrace object. |
| /// |
| /// This includes the original stack trace in the JS Error message so it doesn't |
| /// get lost if the exception reaches JS. |
| final Object RethrownDartError = JS( |
| '!', |
| '''class RethrownDartError extends # { |
| constructor(error, stackTrace) { |
| super(error); |
| this[#] = stackTrace; |
| } |
| get message() { |
| return super.message + "\\n " + #(this[#]) + "\\n"; |
| } |
| }''', |
| DartError, |
| _stackTrace, |
| _toString, |
| _stackTrace); |
| |
| /// Implements `throw` of [exception], allowing for throw in an expression |
| /// context, and capturing the current stack trace. |
| @JSExportName('throw') |
| void throw_(Object? exception) { |
| /// Wrap the object so we capture a new stack trace, and so it will print |
| /// nicely from JS, as if it were a normal JS error. |
| JS('', 'throw new #(#)', DartError, exception); |
| } |
| |
| /// Returns a JS error for throwing the Dart [exception] Object and using the |
| /// provided stack [trace]. |
| /// |
| /// This is used by dart:async to rethrow unhandled errors in [Zone]s, and by |
| /// `async`/`async*` to rethrow errors from Futures/Streams into the generator |
| /// (so a try/catch in there can catch it). |
| /// |
| /// If the exception and trace originated from the same Dart throw, then we can |
| /// simply return the original JS Error. Otherwise, we have to create a new JS |
| /// Error. The new error will have the correct Dart trace, but it will not have |
| /// the correct JS stack trace (visible if JavaScript ends up handling it). To |
| /// fix that, we use [RethrownDartError] to preserve the Dart trace and make |
| /// sure it gets displayed in the JS error message. |
| /// |
| /// If the stack trace is null, this will preserve the original stack trace |
| /// on the exception, if available, otherwise it will capture the current stack |
| /// trace. |
| Object? createErrorWithStack(Object exception, StackTrace? trace) { |
| if (trace == null) { |
| var error = JS('', '#[#]', exception, _jsError); |
| return error != null ? error : JS('', 'new #(#)', DartError, exception); |
| } |
| if (trace is _StackTrace) { |
| /// Optimization: if this stack trace and exception already have a matching |
| /// Error, we can just rethrow it. |
| var originalError = trace._jsError; |
| if (identical(exception, getThrown(originalError))) { |
| return originalError; |
| } |
| } |
| return JS('', 'new #(#, #)', RethrownDartError, exception, trace); |
| } |
| |
| // This is a utility function: it is only intended to be called from dev |
| // tools. |
| void stackPrint(Object error) { |
| JS('', 'console.log(#.stack ? #.stack : "No stack trace for: " + #)', error, |
| error, error); |
| } |
| |
| class _StackTrace implements StackTrace { |
| final Object? _jsError; |
| final Object? _jsObjectMissingTrace; |
| String? _trace; |
| |
| _StackTrace(this._jsError) : _jsObjectMissingTrace = null; |
| |
| _StackTrace.missing(Object? caughtObj) |
| : _jsObjectMissingTrace = caughtObj != null ? caughtObj : 'null', |
| _jsError = JS('', 'Error()'); |
| |
| String toString() { |
| if (_trace != null) return _trace!; |
| |
| var e = _jsError; |
| String trace = ''; |
| if (e != null && JS<bool>('!', 'typeof # === "object"', e)) { |
| trace = e is NativeError ? e.dartStack() : JS<String>('', '#.stack', e); |
| var mapper = stackTraceMapper; |
| if (trace != null && mapper != null) { |
| trace = mapper(trace); |
| } |
| } |
| if (trace.isEmpty || _jsObjectMissingTrace != null) { |
| String jsToString; |
| try { |
| jsToString = JS('', '"" + #', _jsObjectMissingTrace); |
| } catch (_) { |
| jsToString = '<error converting JS object to string>'; |
| } |
| trace = 'Non-error `$jsToString` thrown by JS does not have stack trace.' |
| '\nCaught in Dart at:\n\n$trace'; |
| } |
| return _trace = trace; |
| } |
| } |