blob: f6566809d0e3c98c5bb8c6708a89dbfa55764ab4 [file] [log] [blame]
// Copyright (c) 2013, 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.
library _js_helper;
import 'dart:_js_embedded_names'
show
CURRENT_SCRIPT,
DEFERRED_LIBRARY_PARTS,
DEFERRED_PART_URIS,
DEFERRED_PART_HASHES,
GET_TYPE_FROM_NAME,
GET_ISOLATE_TAG,
INITIALIZE_LOADED_HUNK,
INTERCEPTED_NAMES,
INTERCEPTORS_BY_TAG,
IS_HUNK_LOADED,
IS_HUNK_INITIALIZED,
JsBuiltin,
JsGetName,
LEAF_TAGS,
NATIVE_SUPERCLASS_TAG_NAME,
STATIC_FUNCTION_NAME_PROPERTY_NAME;
import 'dart:collection';
import 'dart:async' show Completer, DeferredLoadException, Future;
import 'dart:_foreign_helper'
show
DART_CLOSURE_TO_JS,
getInterceptor,
JS,
JS_BUILTIN,
JS_CONST,
JS_EFFECT,
JS_EMBEDDED_GLOBAL,
JS_GET_FLAG,
JS_GET_NAME,
JS_INTERCEPTOR_CONSTANT,
JS_STRING_CONCAT,
RAW_DART_FUNCTION_REF;
import 'dart:_interceptors';
import 'dart:_internal' as _symbol_dev;
import 'dart:_internal'
show EfficientLengthIterable, MappedIterable, IterableElementError;
import 'dart:_native_typed_data';
import 'dart:_js_names'
show
extractKeys,
unmangleGlobalNameIfPreservedAnyways,
unmangleAllIdentifiersIfPreservedAnyways;
part 'annotations.dart';
part 'constant_map.dart';
part 'native_helper.dart';
part 'regexp_helper.dart';
part 'string_helper.dart';
part 'js_rti.dart';
part 'linked_hash_map.dart';
/// Marks the internal map in dart2js, so that internal libraries can is-check
/// them.
abstract class InternalMap {}
/// Extracts the JavaScript-constructor name from the given isCheckProperty.
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
String isCheckPropertyToJsConstructorName(String isCheckProperty) {
return JS_BUILTIN('returns:String;depends:none;effects:none',
JsBuiltin.isCheckPropertyToJsConstructorName, isCheckProperty);
}
/// Returns true if the given [type] is a function type object.
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
bool isDartFunctionType(Object type) {
// Function type test is using the `in` operator which doesn't work on
// primitive types.
assert(!(type == null || type is num || type is String));
return JS_BUILTIN(
'returns:bool;effects:none;depends:none', JsBuiltin.isFunctionType, type);
}
/// Returns true if the given [type] is a FutureOr type object.
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
bool isDartFutureOrType(Object type) {
// FutureOr test is using the `in` operator which doesn't work on primitive
// types.
assert(!(type == null || type is num || type is String));
return JS_BUILTIN(
'returns:bool;effects:none;depends:none', JsBuiltin.isFutureOrType, type);
}
@ForceInline()
bool isDartVoidTypeRti(Object type) {
return JS_BUILTIN(
'returns:bool;effects:none;depends:none', JsBuiltin.isVoidType, type);
}
/// Retrieves the class name from type information stored on the constructor of
/// [type].
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
String rawRtiToJsConstructorName(Object rti) {
return JS_BUILTIN('String', JsBuiltin.rawRtiToJsConstructorName, rti);
}
/// Given a raw constructor name, return the unminified name, if available,
/// otherwise tag the name with `minified:`.
String unminifyOrTag(String rawClassName) {
String preserved = unmangleGlobalNameIfPreservedAnyways(rawClassName);
if (preserved is String) return preserved;
if (JS_GET_FLAG('MINIFIED')) return 'minified:${rawClassName}';
return rawClassName;
}
/// Returns the rti from the given [constructorName].
// TODO(floitsch): make this a builtin.
jsConstructorNameToRti(String constructorName) {
var getTypeFromName = JS_EMBEDDED_GLOBAL('', GET_TYPE_FROM_NAME);
return JS('', '#(#)', getTypeFromName, constructorName);
}
/// Returns the raw runtime type of the given object [o].
///
/// The argument [o] must be the interceptor for primitive types. If
/// necessary run it through [getInterceptor] first.
// TODO(floitsch): move this to foreign_helper.dart or similar.
// TODO(floitsch): we should call getInterceptor ourselves, but currently
// getInterceptor is not GVNed.
@ForceInline()
Object getRawRuntimeType(Object o) {
return JS_BUILTIN('', JsBuiltin.rawRuntimeType, o);
}
/// Returns whether the given [type] is a subtype of [other].
///
/// The argument [other] is the name of the other type, as computed by
/// [runtimeTypeToString].
@ForceInline()
bool builtinIsSubtype(type, String other) {
return JS_BUILTIN('returns:bool;effects:none;depends:none',
JsBuiltin.isSubtype, other, type);
}
/// Returns true if the given [type] is _the_ `Function` type.
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
bool isDartFunctionTypeRti(Object type) {
return JS_BUILTIN(
'returns:bool;effects:none;depends:none',
JsBuiltin.isGivenTypeRti,
type,
JS_GET_NAME(JsGetName.FUNCTION_CLASS_TYPE_NAME));
}
/// Returns true if the given [type] is _the_ `Null` type.
@ForceInline()
bool isNullType(Object type) {
return JS_BUILTIN(
'returns:bool;effects:none;depends:none',
JsBuiltin.isGivenTypeRti,
type,
JS_GET_NAME(JsGetName.NULL_CLASS_TYPE_NAME));
}
/// Returns whether the given type is the dynamic type.
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
bool isDartDynamicTypeRti(type) {
return JS_BUILTIN(
'returns:bool;effects:none;depends:none', JsBuiltin.isDynamicType, type);
}
@ForceInline()
bool isDartJsInteropTypeArgumentRti(type) {
return JS_BUILTIN('returns:bool;effects:none;depends:none',
JsBuiltin.isJsInteropTypeArgument, type);
}
/// Returns whether the given type is _the_ Dart Object type.
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
bool isDartObjectTypeRti(type) {
return JS_BUILTIN(
'returns:bool;effects:none;depends:none',
JsBuiltin.isGivenTypeRti,
type,
JS_GET_NAME(JsGetName.OBJECT_CLASS_TYPE_NAME));
}
/// Returns whether the given type is _the_ null type.
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
bool isNullTypeRti(type) {
return JS_BUILTIN(
'returns:bool;effects:none;depends:none',
JsBuiltin.isGivenTypeRti,
type,
JS_GET_NAME(JsGetName.NULL_CLASS_TYPE_NAME));
}
/// Returns the metadata of the given [index].
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
getMetadata(int index) {
return JS_BUILTIN(
'returns:var;effects:none;depends:none', JsBuiltin.getMetadata, index);
}
/// Returns the type of the given [index].
// TODO(floitsch): move this to foreign_helper.dart or similar.
@ForceInline()
getType(int index) {
return JS_BUILTIN(
'returns:var;effects:none;depends:none', JsBuiltin.getType, index);
}
/// No-op method that is called to inform the compiler that preambles might
/// be needed when executing the resulting JS file in a command-line
/// JS engine.
requiresPreamble() {}
bool isJsIndexable(var object, var record) {
if (record != null) {
var result = dispatchRecordIndexability(record);
if (result != null) return result;
}
return object is JavaScriptIndexingBehavior;
}
String S(value) {
if (value is String) return value;
if (value is num) {
if (value != 0) {
// ""+x is faster than String(x) for integers on most browsers.
return JS('String', r'"" + (#)', value);
}
} else if (true == value) {
return 'true';
} else if (false == value) {
return 'false';
} else if (value == null) {
return 'null';
}
var res = value.toString();
if (res is! String) throw argumentErrorValue(value);
return res;
}
// Called from generated code.
createInvocationMirror(
String name, internalName, kind, arguments, argumentNames, types) {
// TODO(sra): [types] (the number of type arguments) could be omitted in the
// generated stub code to save an argument. Then we would use `types ?? 0`.
return new JSInvocationMirror(
name, internalName, kind, arguments, argumentNames, types);
}
createUnmangledInvocationMirror(
Symbol symbol, internalName, kind, arguments, argumentNames, types) {
return new JSInvocationMirror(
symbol, internalName, kind, arguments, argumentNames, types);
}
void throwInvalidReflectionError(String memberName) {
throw new UnsupportedError("Can't use '$memberName' in reflection "
"because it is not included in a @MirrorsUsed annotation.");
}
/// Helper to print the given method information to the console the first
/// time it is called with it.
@NoInline()
void consoleTraceHelper(String method) {
if (JS('bool', '!this.cache')) {
JS('', 'this.cache = Object.create(null)');
}
if (JS('bool', '!this.cache[#]', method)) {
JS('', 'console.log(#)', method);
JS('', 'this.cache[#] = true', method);
}
}
List _traceBuffer;
/// Helper to send coverage information as a POST request to a server.
@NoInline()
void postTraceHelper(int id, String name) {
// Note: we can't move this initialization to the declaration of
// [_traceBuffer] because [postTraceHelper] is called very early on functions
// that define constants, this happens before getters and setters are expanded
// and before main starts executing. This initialization here allows us to
// skip the lazy field initialization logic.
if (_traceBuffer == null) _traceBuffer = JS('JSArray', '[]');
if (JS('bool', '#.length == 0', _traceBuffer)) {
JS(
'',
r'''
window.setTimeout((function(buffer) {
return function() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "/coverage_uri_to_amend_by_server");
xhr.send(JSON.stringify(buffer));
buffer.length = 0;
};
})(#), 1000)''',
_traceBuffer);
}
JS('', '#.push([#, #])', _traceBuffer, id, name);
}
class JSInvocationMirror implements Invocation {
static const METHOD = 0;
static const GETTER = 1;
static const SETTER = 2;
/// When [_memberName] is a String, it holds the mangled name of this
/// invocation. When it is a Symbol, it holds the unmangled name.
var /* String or Symbol */ _memberName;
final String _internalName;
final int _kind;
final List _arguments;
final List _namedArgumentNames;
final int _typeArgumentCount;
JSInvocationMirror(this._memberName, this._internalName, this._kind,
this._arguments, this._namedArgumentNames, this._typeArgumentCount);
Symbol get memberName {
if (_memberName is Symbol) return _memberName;
return _memberName = new _symbol_dev.Symbol.unvalidated(_memberName);
}
bool get isMethod => _kind == METHOD;
bool get isGetter => _kind == GETTER;
bool get isSetter => _kind == SETTER;
bool get isAccessor => _kind != METHOD;
List<Type> get typeArguments {
if (_typeArgumentCount == 0) return const <Type>[];
int start = _arguments.length - _typeArgumentCount;
var list = <Type>[];
for (int index = 0; index < _typeArgumentCount; index++) {
list.add(createRuntimeType(_arguments[start + index]));
}
return list;
}
List get positionalArguments {
if (isGetter) return const [];
var argumentCount =
_arguments.length - _namedArgumentNames.length - _typeArgumentCount;
if (argumentCount == 0) return const [];
var list = [];
for (var index = 0; index < argumentCount; index++) {
list.add(_arguments[index]);
}
return JSArray.markUnmodifiableList(list);
}
Map<Symbol, dynamic> get namedArguments {
if (isAccessor) return const <Symbol, dynamic>{};
int namedArgumentCount = _namedArgumentNames.length;
int namedArgumentsStartIndex =
_arguments.length - namedArgumentCount - _typeArgumentCount;
if (namedArgumentCount == 0) return const <Symbol, dynamic>{};
var map = new Map<Symbol, dynamic>();
for (int i = 0; i < namedArgumentCount; i++) {
map[new _symbol_dev.Symbol.unvalidated(_namedArgumentNames[i])] =
_arguments[namedArgumentsStartIndex + i];
}
return new ConstantMapView<Symbol, dynamic>(map);
}
_getCachedInvocation(Object object) {
var interceptor = getInterceptor(object);
var receiver = object;
var name = _internalName;
var arguments = _arguments;
var interceptedNames = JS_EMBEDDED_GLOBAL('', INTERCEPTED_NAMES);
bool isIntercepted = JS('bool',
'Object.prototype.hasOwnProperty.call(#, #)', interceptedNames, name);
if (isIntercepted) {
receiver = interceptor;
if (JS('bool', '# === #', object, interceptor)) {
interceptor = null;
}
} else {
interceptor = null;
}
bool isCatchAll = false;
var method = JS('var', '#[#]', receiver, name);
if (JS('bool', 'typeof # != "function"', method)) {
String baseName = _symbol_dev.Symbol.getName(memberName);
method = JS('', '#[# + "*"]', receiver, baseName);
if (method == null) {
interceptor = getInterceptor(object);
method = JS('', '#[# + "*"]', interceptor, baseName);
if (method != null) {
isIntercepted = true;
receiver = interceptor;
} else {
interceptor = null;
}
}
isCatchAll = true;
}
if (JS('bool', 'typeof # == "function"', method)) {
if (isCatchAll) {
return new CachedCatchAllInvocation(
name, method, isIntercepted, interceptor);
} else {
return new CachedInvocation(name, method, isIntercepted, interceptor);
}
} else {
// In this case, receiver doesn't implement name. So we should
// invoke noSuchMethod instead (which will often throw a
// NoSuchMethodError).
return new CachedNoSuchMethodInvocation(interceptor);
}
}
/// This method is called by [InstanceMirror.delegate].
static invokeFromMirror(JSInvocationMirror invocation, Object victim) {
var cached = invocation._getCachedInvocation(victim);
if (cached.isNoSuchMethod) {
return cached.invokeOn(victim, invocation);
} else {
return cached.invokeOn(victim, invocation._arguments);
}
}
static getCachedInvocation(JSInvocationMirror invocation, Object victim) {
return invocation._getCachedInvocation(victim);
}
}
class CachedInvocation {
// The mangled name of this invocation.
String mangledName;
/// The JS function to call.
var jsFunction;
/// True if this is an intercepted call.
bool isIntercepted;
/// Non-null interceptor if this is an intercepted call through an
/// [Interceptor].
Interceptor cachedInterceptor;
CachedInvocation(this.mangledName, this.jsFunction, this.isIntercepted,
this.cachedInterceptor);
bool get isNoSuchMethod => false;
bool get isGetterStub => JS('bool', '!!#.\$getterStub', jsFunction);
/// Applies [jsFunction] to [victim] with [arguments].
/// Users of this class must take care to check the arguments first.
invokeOn(Object victim, List arguments) {
var receiver = victim;
if (!isIntercepted) {
if (arguments is! JSArray) arguments = new List.from(arguments);
} else {
arguments = [victim]..addAll(arguments);
if (cachedInterceptor != null) receiver = cachedInterceptor;
}
return JS('var', '#.apply(#, #)', jsFunction, receiver, arguments);
}
}
class CachedCatchAllInvocation extends CachedInvocation {
final ReflectionInfo info;
CachedCatchAllInvocation(String name, jsFunction, bool isIntercepted,
Interceptor cachedInterceptor)
: info = new ReflectionInfo(jsFunction),
super(name, jsFunction, isIntercepted, cachedInterceptor);
bool get isGetterStub => false;
invokeOn(Object victim, List arguments) {
var receiver = victim;
int providedArgumentCount;
int fullParameterCount =
info.requiredParameterCount + info.optionalParameterCount;
if (!isIntercepted) {
if (arguments is JSArray) {
providedArgumentCount = arguments.length;
// If we need to add extra arguments before calling, we have
// to copy the arguments array.
if (providedArgumentCount < fullParameterCount) {
arguments = new List.from(arguments);
}
} else {
arguments = new List.from(arguments);
providedArgumentCount = arguments.length;
}
} else {
arguments = [victim]..addAll(arguments);
if (cachedInterceptor != null) receiver = cachedInterceptor;
providedArgumentCount = arguments.length - 1;
}
if (info.areOptionalParametersNamed &&
(providedArgumentCount > info.requiredParameterCount)) {
throw new UnimplementedNoSuchMethodError(
"Invocation of unstubbed method '${info.reflectionName}'"
" with ${arguments.length} arguments.");
} else if (providedArgumentCount < info.requiredParameterCount) {
throw new UnimplementedNoSuchMethodError(
"Invocation of unstubbed method '${info.reflectionName}'"
" with $providedArgumentCount arguments (too few).");
} else if (providedArgumentCount > fullParameterCount) {
throw new UnimplementedNoSuchMethodError(
"Invocation of unstubbed method '${info.reflectionName}'"
" with $providedArgumentCount arguments (too many).");
}
for (int i = providedArgumentCount; i < fullParameterCount; i++) {
arguments.add(getMetadata(info.defaultValue(i)));
}
return JS('var', '#.apply(#, #)', jsFunction, receiver, arguments);
}
}
class CachedNoSuchMethodInvocation {
/// Non-null interceptor if this is an intercepted call through an
/// [Interceptor].
var interceptor;
CachedNoSuchMethodInvocation(this.interceptor);
bool get isNoSuchMethod => true;
bool get isGetterStub => false;
invokeOn(Object victim, Invocation invocation) {
var receiver = (interceptor == null) ? victim : interceptor;
return receiver.noSuchMethod(invocation);
}
}
class ReflectionInfo {
static const int REQUIRED_PARAMETERS_INFO = 0;
static const int OPTIONAL_PARAMETERS_INFO = 1;
static const int FUNCTION_TYPE_INDEX = 2;
static const int FIRST_DEFAULT_ARGUMENT = 3;
/// A JavaScript function object.
final jsFunction;
/// Raw reflection information.
final List data;
/// Is this a getter or a setter.
final bool isAccessor;
/// Number of required parameters.
final int requiredParameterCount;
/// Number of optional parameters.
final int optionalParameterCount;
/// Are optional parameters named.
final bool areOptionalParametersNamed;
/// Either an index to the function type in the embedded `metadata` global or
/// a JavaScript function object which can compute such a type (presumably
/// due to free type variables).
final functionType;
List cachedSortedIndices;
ReflectionInfo.internal(
this.jsFunction,
this.data,
this.isAccessor,
this.requiredParameterCount,
this.optionalParameterCount,
this.areOptionalParametersNamed,
this.functionType);
factory ReflectionInfo(jsFunction) {
List data = JS('JSExtendableArray|Null', r'#.$reflectionInfo', jsFunction);
if (data == null) return null;
data = JSArray.markFixedList(data);
int requiredParametersInfo =
JS('int', '#[#]', data, REQUIRED_PARAMETERS_INFO);
int requiredParameterCount = JS('int', '# >> 2', requiredParametersInfo);
bool isAccessor = (requiredParametersInfo & 2) == 2;
int optionalParametersInfo =
JS('int', '#[#]', data, OPTIONAL_PARAMETERS_INFO);
int optionalParameterCount = JS('int', '# >> 1', optionalParametersInfo);
bool areOptionalParametersNamed = (optionalParametersInfo & 1) == 1;
var functionType = JS('', '#[#]', data, FUNCTION_TYPE_INDEX);
return new ReflectionInfo.internal(
jsFunction,
data,
isAccessor,
requiredParameterCount,
optionalParameterCount,
areOptionalParametersNamed,
functionType);
}
String parameterName(int parameter) {
int metadataIndex;
if (JS_GET_FLAG('MUST_RETAIN_METADATA')) {
metadataIndex = JS('int', '#[2 * # + # + #]', data, parameter,
optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
} else {
metadataIndex = JS('int', '#[# + # + #]', data, parameter,
optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
}
var name = getMetadata(metadataIndex);
return JS('String', '#', name);
}
List<int> parameterMetadataAnnotations(int parameter) {
if (!JS_GET_FLAG('MUST_RETAIN_METADATA')) {
throw new StateError('metadata has not been preserved');
} else {
return JS('', '#[2 * # + # + # + 1]', data, parameter,
optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
}
}
int defaultValue(int parameter) {
if (parameter < requiredParameterCount) return null;
return JS('int', '#[# + # - #]', data, FIRST_DEFAULT_ARGUMENT, parameter,
requiredParameterCount);
}
/// Returns the default value of the [parameter]th entry of the list of
/// parameters sorted by name.
int defaultValueInOrder(int parameter) {
if (parameter < requiredParameterCount) return null;
if (!areOptionalParametersNamed || optionalParameterCount == 1) {
return defaultValue(parameter);
}
int index = sortedIndex(parameter - requiredParameterCount);
return defaultValue(index);
}
/// Returns the default value of the [parameter]th entry of the list of
/// parameters sorted by name.
String parameterNameInOrder(int parameter) {
if (parameter < requiredParameterCount) return null;
if (!areOptionalParametersNamed || optionalParameterCount == 1) {
return parameterName(parameter);
}
int index = sortedIndex(parameter - requiredParameterCount);
return parameterName(index);
}
/// Computes the index of the parameter in the list of named parameters sorted
/// by their name.
int sortedIndex(int unsortedIndex) {
if (cachedSortedIndices == null) {
// TODO(karlklose): cache this between [ReflectionInfo] instances or cache
// [ReflectionInfo] instances by [jsFunction].
cachedSortedIndices = new List(optionalParameterCount);
Map<String, int> positions = <String, int>{};
for (int i = 0; i < optionalParameterCount; i++) {
int index = requiredParameterCount + i;
positions[parameterName(index)] = index;
}
int index = 0;
(positions.keys.toList()..sort()).forEach((String name) {
cachedSortedIndices[index++] = positions[name];
});
}
return cachedSortedIndices[unsortedIndex];
}
@NoInline()
computeFunctionRti(jsConstructor) {
if (JS('bool', 'typeof # == "number"', functionType)) {
return getType(functionType);
} else if (JS('bool', 'typeof # == "function"', functionType)) {
if (jsConstructor != null) {
var fakeInstance = JS('', 'new #()', jsConstructor);
setRuntimeTypeInfo(
fakeInstance, JS('JSExtendableArray', '#["<>"]', fakeInstance));
return JS('=Object|Null', r'#.apply({$receiver:#})', functionType,
fakeInstance);
}
return functionType;
} else {
throw new RuntimeError('Unexpected function type');
}
}
String get reflectionName => JS('String', r'#.$reflectionName', jsFunction);
}
class Primitives {
static int objectHashCode(object) {
int hash = JS('int|Null', r'#.$identityHash', object);
if (hash == null) {
hash = JS('int', '(Math.random() * 0x3fffffff) | 0');
JS('void', r'#.$identityHash = #', object, hash);
}
return JS('int', '#', hash);
}
static int parseInt(String source, int radix) {
checkString(source);
var re = JS('', r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i');
var match = JS('JSExtendableArray|Null', '#.exec(#)', re, source);
int digitsIndex = 1;
int hexIndex = 2;
int decimalIndex = 3;
int nonDecimalHexIndex = 4;
if (match == null) {
// TODO(sra): It might be that the match failed due to unrecognized U+0085
// spaces. We could replace them with U+0020 spaces and try matching
// again.
return null;
}
String decimalMatch = match[decimalIndex];
if (radix == null) {
if (decimalMatch != null) {
// Cannot fail because we know that the digits are all decimal.
return JS('int', r'parseInt(#, 10)', source);
}
if (match[hexIndex] != null) {
// Cannot fail because we know that the digits are all hex.
return JS('int', r'parseInt(#, 16)', source);
}
return null;
}
if (radix is! int) {
throw new ArgumentError.value(radix, 'radix', 'is not an integer');
}
if (radix < 2 || radix > 36) {
throw new RangeError.range(radix, 2, 36, 'radix');
}
if (radix == 10 && decimalMatch != null) {
// Cannot fail because we know that the digits are all decimal.
return JS('int', r'parseInt(#, 10)', source);
}
// If radix >= 10 and we have only decimal digits the string is safe.
// Otherwise we need to check the digits.
if (radix < 10 || decimalMatch == null) {
// We know that the characters must be ASCII as otherwise the
// regexp wouldn't have matched. Lowercasing by doing `| 0x20` is thus
// guaranteed to be a safe operation, since it preserves digits
// and lower-cases ASCII letters.
int maxCharCode;
if (radix <= 10) {
// Allow all digits less than the radix. For example 0, 1, 2 for
// radix 3.
// "0".codeUnitAt(0) + radix - 1;
maxCharCode = (0x30 - 1) + radix;
} else {
// Letters are located after the digits in ASCII. Therefore we
// only check for the character code. The regexp above made already
// sure that the string does not contain anything but digits or
// letters.
// "a".codeUnitAt(0) + (radix - 10) - 1;
maxCharCode = (0x61 - 10 - 1) + radix;
}
assert(match[digitsIndex] is String);
String digitsPart = JS('String', '#[#]', match, digitsIndex);
for (int i = 0; i < digitsPart.length; i++) {
int characterCode = digitsPart.codeUnitAt(i) | 0x20;
if (characterCode > maxCharCode) {
return null;
}
}
}
// The above matching and checks ensures the source has at least one digits
// and all digits are suitable for the radix, so parseInt cannot return NaN.
return JS('int', r'parseInt(#, #)', source, radix);
}
static double parseDouble(String source) {
checkString(source);
// Notice that JS parseFloat accepts garbage at the end of the string.
// Accept only:
// - [+/-]NaN
// - [+/-]Infinity
// - a Dart double literal
// We do allow leading or trailing whitespace.
if (!JS(
'bool',
r'/^\s*[+-]?(?:Infinity|NaN|'
r'(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(#)',
source)) {
return null;
}
var result = JS('num', r'parseFloat(#)', source);
if (result.isNaN) {
var trimmed = source.trim();
if (trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN') {
return result;
}
return null;
}
return result;
}
/** [: r"$".codeUnitAt(0) :] */
static const int DOLLAR_CHAR_VALUE = 36;
/// Creates a string containing the complete type for the class [className]
/// with the given type arguments.
///
/// In minified mode, uses the unminified names if available.
///
/// The given [className] string generally contains the name of the JavaScript
/// constructor of the given class.
static String formatType(String className, List typeArguments) {
return unmangleAllIdentifiersIfPreservedAnyways(
'$className${joinArguments(typeArguments, 0)}');
}
/// Returns the type of [object] as a string (including type arguments).
///
/// In minified mode, uses the unminified names if available.
@NoInline()
static String objectTypeName(Object object) {
if (JS_GET_FLAG('STRONG_MODE')) {
String className = _objectClassName(object);
String arguments = joinArguments(getRuntimeTypeInfo(object), 0);
return '${className}${arguments}';
} else {
return formatType(
_objectRawTypeNameV1(object), getRuntimeTypeInfo(object));
}
}
static String _objectClassName(Object object) {
var interceptor = getInterceptor(object);
// The interceptor is either an object (self-intercepting plain Dart class),
// the prototype of the constructor for an Interceptor class (like
// `JSString.prototype`, `JSNull.prototype`), or an Interceptor object
// instance (`const JSString()`, should use `JSString.prototype`).
//
// These all should have a `constructor` property with a `name` property.
String name;
var interceptorConstructor = JS('', '#.constructor', interceptor);
if (JS('bool', 'typeof # == "function"', interceptorConstructor)) {
var interceptorConstructorName = JS('', '#.name', interceptorConstructor);
if (interceptorConstructorName is String) {
name = interceptorConstructorName;
}
}
if (name == null ||
identical(interceptor, JS_INTERCEPTOR_CONSTANT(Interceptor)) ||
object is UnknownJavaScriptObject) {
// Try to do better. If we do not find something better, leave the name
// as 'UnknownJavaScriptObject' or 'Interceptor' (or the minified name).
//
// When we get here via the UnknownJavaScriptObject test (for JavaScript
// objects from outside the program), the object's constructor has a
// better name that 'UnknownJavaScriptObject'.
//
// When we get here the Interceptor test (for Native classes that are
// declared in the Dart program but have been 'folded' into Interceptor),
// the native class's constructor name is better than the generic
// 'Interceptor' (an abstract class).
// Try the [constructorNameFallback]. This gets the constructor name for
// any browser (used by [getNativeInterceptor]).
String dispatchName = constructorNameFallback(object);
name ??= dispatchName;
if (dispatchName == 'Object') {
// Try to decompile the constructor by turning it into a string and get
// the name out of that. If the decompiled name is a string containing
// an identifier, we use that instead of the very generic 'Object'.
var objectConstructor = JS('', '#.constructor', object);
if (JS('bool', 'typeof # == "function"', objectConstructor)) {
var match = JS('var', r'#.match(/^\s*function\s*([\w$]*)\s*\(/)',
JS('var', r'String(#)', objectConstructor));
var decompiledName = match == null ? null : JS('var', r'#[1]', match);
if (decompiledName is String &&
JS('bool', r'/^\w+$/.test(#)', decompiledName)) {
name = decompiledName;
}
}
}
return JS('String', '#', name);
}
// Type inference does not understand that [name] is now always a non-null
// String. (There is some imprecision in the negation of the disjunction.)
name = JS('String', '#', name);
// TODO(kasperl): If the namer gave us a fresh global name, we may
// want to remove the numeric suffix that makes it unique too.
if (name.length > 1 && identical(name.codeUnitAt(0), DOLLAR_CHAR_VALUE)) {
name = name.substring(1);
}
return unminifyOrTag(name);
}
static String _objectRawTypeNameV1(Object object) {
var interceptor = getInterceptor(object);
// The interceptor is either an object (self-intercepting plain Dart class),
// the prototype of the constructor for an Interceptor class (like
// `JSString.prototype`, `JSNull.prototype`), or an Interceptor object
// instance (`const JSString()`, should use `JSString.prototype`).
//
// These all should have a `constructor` property with a `name` property.
String name;
var interceptorConstructor = JS('', '#.constructor', interceptor);
if (JS('bool', 'typeof # == "function"', interceptorConstructor)) {
var interceptorConstructorName = JS('', '#.name', interceptorConstructor);
if (interceptorConstructorName is String) {
name = interceptorConstructorName;
}
}
if (name == null ||
identical(interceptor, JS_INTERCEPTOR_CONSTANT(Interceptor)) ||
object is UnknownJavaScriptObject) {
// Try to do better. If we do not find something better, leave the name
// as 'UnknownJavaScriptObject' or 'Interceptor' (or the minified name).
//
// When we get here via the UnknownJavaScriptObject test (for JavaScript
// objects from outside the program), the object's constructor has a
// better name that 'UnknownJavaScriptObject'.
//
// When we get here the Interceptor test (for Native classes that are
// declared in the Dart program but have been 'folded' into Interceptor),
// the native class's constructor name is better than the generic
// 'Interceptor' (an abstract class).
// Try the [constructorNameFallback]. This gets the constructor name for
// any browser (used by [getNativeInterceptor]).
String dispatchName = constructorNameFallback(object);
if (dispatchName == 'Object') {
// Try to decompile the constructor by turning it into a string and get
// the name out of that. If the decompiled name is a string containing
// an identifier, we use that instead of the very generic 'Object'.
var objectConstructor = JS('', '#.constructor', object);
if (JS('bool', 'typeof # == "function"', objectConstructor)) {
var match = JS('var', r'#.match(/^\s*function\s*([\w$]*)\s*\(/)',
JS('var', r'String(#)', objectConstructor));
var decompiledName = match == null ? null : JS('var', r'#[1]', match);
if (decompiledName is String &&
JS('bool', r'/^\w+$/.test(#)', decompiledName)) {
name = decompiledName;
}
}
if (name == null) name = dispatchName;
} else {
name = dispatchName;
}
}
// Type inference does not understand that [name] is now always a non-null
// String. (There is some imprecision in the negation of the disjunction.)
name = JS('String', '#', name);
// TODO(kasperl): If the namer gave us a fresh global name, we may
// want to remove the numeric suffix that makes it unique too.
if (name.length > 1 && identical(name.codeUnitAt(0), DOLLAR_CHAR_VALUE)) {
name = name.substring(1);
}
return name;
}
/// In minified mode, uses the unminified names if available.
static String objectToHumanReadableString(Object object) {
String name = objectTypeName(object);
return "Instance of '$name'";
}
static num dateNow() => JS('int', r'Date.now()');
static void initTicker() {
if (timerFrequency != null) return;
// Start with low-resolution. We overwrite the fields if we find better.
timerFrequency = 1000;
timerTicks = dateNow;
if (JS('bool', 'typeof window == "undefined"')) return;
var window = JS('var', 'window');
if (window == null) return;
var performance = JS('var', '#.performance', window);
if (performance == null) return;
if (JS('bool', 'typeof #.now != "function"', performance)) return;
timerFrequency = 1000000;
timerTicks = () => (1000 * JS('num', '#.now()', performance)).floor();
}
static int timerFrequency;
static Function timerTicks;
static String currentUri() {
requiresPreamble();
// In a browser return self.location.href.
if (JS('bool', '!!self.location')) {
return JS('String', 'self.location.href');
}
return null;
}
/// Version of `String.fromCharCode.apply` that chunks the conversion to avoid
/// stack overflows due to very large argument arrays.
///
/// [array] is pre-validated as a JSArray of int values but is not typed as
/// <int> so it can be called with any JSArray.
static String _fromCharCodeApply(List array) {
const kMaxApply = 500;
int end = array.length;
if (end <= kMaxApply) {
return JS('String', r'String.fromCharCode.apply(null, #)', array);
}
String result = '';
for (int i = 0; i < end; i += kMaxApply) {
int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end;
result = JS(
'String',
r'# + String.fromCharCode.apply(null, #.slice(#, #))',
result,
array,
i,
chunkEnd);
}
return result;
}
static String stringFromCodePoints(codePoints) {
List<int> a = <int>[];
for (var i in codePoints) {
if (i is! int) throw argumentErrorValue(i);
if (i <= 0xffff) {
a.add(i);
} else if (i <= 0x10ffff) {
a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff)));
a.add(0xdc00 + (i & 0x3ff));
} else {
throw argumentErrorValue(i);
}
}
return _fromCharCodeApply(a);
}
static String stringFromCharCodes(charCodes) {
for (var i in charCodes) {
if (i is! int) throw argumentErrorValue(i);
if (i < 0) throw argumentErrorValue(i);
if (i > 0xffff) return stringFromCodePoints(charCodes);
}
return _fromCharCodeApply(charCodes);
}
// [start] and [end] are validated.
static String stringFromNativeUint8List(
NativeUint8List charCodes, int start, int end) {
const kMaxApply = 500;
if (end <= kMaxApply && start == 0 && end == charCodes.length) {
return JS('String', r'String.fromCharCode.apply(null, #)', charCodes);
}
String result = '';
for (int i = start; i < end; i += kMaxApply) {
int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end;
result = JS(
'String',
r'# + String.fromCharCode.apply(null, #.subarray(#, #))',
result,
charCodes,
i,
chunkEnd);
}
return result;
}
static String stringFromCharCode(charCode) {
if (0 <= charCode) {
if (charCode <= 0xffff) {
return JS('returns:String;effects:none;depends:none',
'String.fromCharCode(#)', charCode);
}
if (charCode <= 0x10ffff) {
var bits = charCode - 0x10000;
var low = 0xDC00 | (bits & 0x3ff);
var high = 0xD800 | (bits >> 10);
return JS('returns:String;effects:none;depends:none',
'String.fromCharCode(#, #)', high, low);
}
}
throw new RangeError.range(charCode, 0, 0x10ffff);
}
static String stringConcatUnchecked(String string1, String string2) {
return JS_STRING_CONCAT(string1, string2);
}
static String flattenString(String str) {
return JS('returns:String;depends:none;effects:none;throws:never;gvn:true',
'#.charCodeAt(0) == 0 ? # : #', str, str, str);
}
static String getTimeZoneName(DateTime receiver) {
// Firefox and Chrome emit the timezone in parenthesis.
// Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)".
// We extract this name using a regexp.
var d = lazyAsJsDate(receiver);
List match = JS('JSArray|Null', r'/\((.*)\)/.exec(#.toString())', d);
if (match != null) return match[1];
// Internet Explorer 10+ emits the zone name without parenthesis:
// Example: Thu Oct 31 14:07:44 PDT 2013
match = JS(
'JSArray|Null',
// Thu followed by a space.
r'/^[A-Z,a-z]{3}\s'
// Oct 31 followed by space.
r'[A-Z,a-z]{3}\s\d+\s'
// Time followed by a space.
r'\d{2}:\d{2}:\d{2}\s'
// The time zone name followed by a space.
r'([A-Z]{3,5})\s'
// The year.
r'\d{4}$/'
'.exec(#.toString())',
d);
if (match != null) return match[1];
// IE 9 and Opera don't provide the zone name. We fall back to emitting the
// UTC/GMT offset.
// Example (IE9): Wed Nov 20 09:51:00 UTC+0100 2013
// (Opera): Wed Nov 20 2013 11:03:38 GMT+0100
match = JS('JSArray|Null', r'/(?:GMT|UTC)[+-]\d{4}/.exec(#.toString())', d);
if (match != null) return match[0];
return '';
}
static int getTimeZoneOffsetInMinutes(DateTime receiver) {
// Note that JS and Dart disagree on the sign of the offset.
// Subtract to avoid -0.0
return 0 - JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver));
}
static int valueFromDecomposedDate(
years, month, day, hours, minutes, seconds, milliseconds, isUtc) {
final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
checkInt(years);
checkInt(month);
checkInt(day);
checkInt(hours);
checkInt(minutes);
checkInt(seconds);
checkInt(milliseconds);
checkBool(isUtc);
var jsMonth = month - 1;
// The JavaScript Date constructor 'corrects' year NN to 19NN. Sidestep that
// correction by adjusting years out of that range and compensating with an
// adjustment of months. This hack should not be sensitive to leap years but
// use 400 just in case.
if (0 <= years && years < 100) {
years += 400;
jsMonth -= 400 * 12;
}
var value;
if (isUtc) {
value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)', years, jsMonth, day,
hours, minutes, seconds, milliseconds);
} else {
value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()', years,
jsMonth, day, hours, minutes, seconds, milliseconds);
}
if (value.isNaN ||
value < -MAX_MILLISECONDS_SINCE_EPOCH ||
value > MAX_MILLISECONDS_SINCE_EPOCH) {
return null;
}
return JS('int', '#', value);
}
// Lazily keep a JS Date stored in the JS object.
static lazyAsJsDate(DateTime receiver) {
if (JS('bool', r'#.date === (void 0)', receiver)) {
JS('void', r'#.date = new Date(#)', receiver,
receiver.millisecondsSinceEpoch);
}
return JS('var', r'#.date', receiver);
}
// The getters for date and time parts below add a positive integer to ensure
// that the result is really an integer, because the JavaScript implementation
// may return -0.0 instead of 0.
//
// They are marked as @NoThrows() because `receiver` comes from a receiver of
// a method on DateTime (i.e. is not `null`).
// TODO(sra): These methods are GVN-able. dart2js should implement an
// annotation for that.
// TODO(sra): These methods often occur in groups (e.g. day, month and
// year). Is it possible to factor them so that the `Date` is visible and can
// be GVN-ed without a lot of code bloat?
@NoSideEffects()
@NoThrows()
@NoInline()
static getYear(DateTime receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver));
}
@NoSideEffects()
@NoThrows()
@NoInline()
static getMonth(DateTime receiver) {
return (receiver.isUtc)
? JS('JSUInt31', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
: JS('JSUInt31', r'#.getMonth() + 1', lazyAsJsDate(receiver));
}
@NoSideEffects()
@NoThrows()
@NoInline()
static getDay(DateTime receiver) {
return (receiver.isUtc)
? JS('JSUInt31', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
}
@NoSideEffects()
@NoThrows()
@NoInline()
static getHours(DateTime receiver) {
return (receiver.isUtc)
? JS('JSUInt31', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
}
@NoSideEffects()
@NoThrows()
@NoInline()
static getMinutes(DateTime receiver) {
return (receiver.isUtc)
? JS('JSUInt31', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
}
@NoSideEffects()
@NoThrows()
@NoInline()
static getSeconds(DateTime receiver) {
return (receiver.isUtc)
? JS('JSUInt31', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
}
@NoSideEffects()
@NoThrows()
@NoInline()
static getMilliseconds(DateTime receiver) {
return (receiver.isUtc)
? JS(
'JSUInt31', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
}
@NoSideEffects()
@NoThrows()
@NoInline()
static getWeekday(DateTime receiver) {
int weekday = (receiver.isUtc)
? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver))
: JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver));
// Adjust by one because JS weeks start on Sunday.
return (weekday + 6) % 7 + 1;
}
static valueFromDateString(str) {
if (str is! String) throw argumentErrorValue(str);
var value = JS('num', r'Date.parse(#)', str);
if (value.isNaN) throw argumentErrorValue(str);
return value;
}
static getProperty(object, key) {
if (object == null || object is bool || object is num || object is String) {
throw argumentErrorValue(object);
}
return JS('var', '#[#]', object, key);
}
static void setProperty(object, key, value) {
if (object == null || object is bool || object is num || object is String) {
throw argumentErrorValue(object);
}
JS('void', '#[#] = #', object, key, value);
}
static functionNoSuchMethod(
function, List positionalArguments, Map<String, dynamic> namedArguments) {
int argumentCount = 0;
List arguments = [];
List namedArgumentList = [];
if (positionalArguments != null) {
argumentCount += positionalArguments.length;
arguments.addAll(positionalArguments);
}
String names = '';
if (namedArguments != null && !namedArguments.isEmpty) {
namedArguments.forEach((String name, argument) {
names = '$names\$$name';
namedArgumentList.add(name);
arguments.add(argument);
argumentCount++;
});
}
String selectorName =
'${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount$names';
return function.noSuchMethod(createUnmangledInvocationMirror(
#call,
selectorName,
JSInvocationMirror.METHOD,
arguments,
namedArgumentList,
0));
}
/**
* Implements [Function.apply] for the lazy and startup emitters.
*
* There are two types of closures that can reach this function:
*
* 1. tear-offs (including tear-offs of static functions).
* 2. anonymous closures.
*
* They are treated differently (although there are lots of similarities).
* Both have in common that they have
* a [JsGetName.CALL_CATCH_ALL] and
* a [JsGetName.REQUIRED_PARAMETER_PROPERTY] property.
*
* If the closure supports optional parameters, then they also feature
* a [JsGetName.DEFAULT_VALUES_PROPERTY] property.
*
* The catch-all property is a method that takes all arguments (including
* all optional positional or named arguments). If the function accepts
* optional arguments, then the default-values property stores (potentially
* wrapped in a function) the default values for the optional arguments. If
* the function accepts optional positional arguments, then the value is a
* JavaScript array with the default values. Otherwise, when the function
* accepts optional named arguments, it is a JavaScript object.
*
* The default-values property may either contain the value directly, or
* it can be a function that returns the default-values when invoked.
*
* If the function is an anonymous closure, then the catch-all property
* only contains a string pointing to the property that should be used
* instead. For example, if the catch-all property contains the string
* "call$4", then the object's "call$4" property should be used as if it was
* the value of the catch-all property.
*/
static applyFunction2(Function function, List positionalArguments,
Map<String, dynamic> namedArguments) {
// Fast shortcut for the common case.
if (JS('bool', '# instanceof Array', positionalArguments) &&
(namedArguments == null || namedArguments.isEmpty)) {
// Let the compiler know that we did a type-test.
List arguments = (JS('JSArray', '#', positionalArguments));
int argumentCount = arguments.length;
if (argumentCount == 0) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX0);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#]()', function, selectorName);
}
} else if (argumentCount == 1) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX1);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0])', function, selectorName, arguments);
}
} else if (argumentCount == 2) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX2);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0],#[1])', function, selectorName, arguments,
arguments);
}
} else if (argumentCount == 3) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX3);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0],#[1],#[2])', function, selectorName,
arguments, arguments, arguments);
}
} else if (argumentCount == 4) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX4);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0],#[1],#[2],#[3])', function, selectorName,
arguments, arguments, arguments, arguments);
}
} else if (argumentCount == 5) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX5);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS(
'',
'#[#](#[0],#[1],#[2],#[3],#[4])',
function,
selectorName,
arguments,
arguments,
arguments,
arguments,
arguments);
}
}
String selectorName =
'${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount';
var jsStub = JS('var', r'#[#]', function, selectorName);
if (jsStub != null) {
return JS('var', '#.apply(#, #)', jsStub, function, arguments);
}
}
return _genericApplyFunction2(
function, positionalArguments, namedArguments);
}
static _genericApplyFunction2(Function function, List positionalArguments,
Map<String, dynamic> namedArguments) {
List arguments;
if (positionalArguments != null) {
if (JS('bool', '# instanceof Array', positionalArguments)) {
arguments = JS('JSArray', '#', positionalArguments);
} else {
arguments = new List.from(positionalArguments);
}
} else {
arguments = [];
}
int argumentCount = arguments.length;
int requiredParameterCount = JS('int', r'#[#]', function,
JS_GET_NAME(JsGetName.REQUIRED_PARAMETER_PROPERTY));
if (argumentCount < requiredParameterCount) {
return functionNoSuchMethod(function, arguments, namedArguments);
}
var defaultValuesClosure = JS('var', r'#[#]', function,
JS_GET_NAME(JsGetName.DEFAULT_VALUES_PROPERTY));
bool acceptsOptionalArguments = defaultValuesClosure != null;
// Default values are stored inside a JavaScript closure to avoid
// accessing them too early.
var defaultValues =
acceptsOptionalArguments ? JS('', '#()', defaultValuesClosure) : null;
var interceptor = getInterceptor(function);
var jsFunction =
JS('', '#[#]', interceptor, JS_GET_NAME(JsGetName.CALL_CATCH_ALL));
if (jsFunction is String) {
// Anonymous closures redirect to the catch-all property instead of
// storing the catch-all method directly in the catch-all property.
jsFunction = JS('', '#[#]', interceptor, jsFunction);
}
if (!acceptsOptionalArguments) {
if (namedArguments != null && namedArguments.isNotEmpty) {
// Tried to invoke a function that takes a fixed number of arguments
// with named (optional) arguments.
return functionNoSuchMethod(function, arguments, namedArguments);
}
if (argumentCount == requiredParameterCount) {
return JS('var', r'#.apply(#, #)', jsFunction, function, arguments);
}
return functionNoSuchMethod(function, arguments, namedArguments);
}
bool acceptsPositionalArguments =
JS('bool', '# instanceof Array', defaultValues);
if (acceptsPositionalArguments) {
if (namedArguments != null && namedArguments.isNotEmpty) {
// Tried to invoke a function that takes optional positional arguments
// with named arguments.
return functionNoSuchMethod(function, arguments, namedArguments);
}
int defaultsLength = JS('int', '#.length', defaultValues);
int maxArguments = requiredParameterCount + defaultsLength;
if (argumentCount > maxArguments) {
// The function expects fewer arguments.
return functionNoSuchMethod(function, arguments, null);
}
List missingDefaults = JS('JSArray', '#.slice(#)', defaultValues,
argumentCount - requiredParameterCount);
arguments.addAll(missingDefaults);
return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
} else {
// Handle named arguments.
if (argumentCount > requiredParameterCount) {
// Tried to invoke a function that takes named parameters with
// too many positional arguments.
return functionNoSuchMethod(function, arguments, namedArguments);
}
List keys = JS('JSArray', r'Object.keys(#)', defaultValues);
if (namedArguments == null) {
for (String key in keys) {
arguments.add(JS('var', '#[#]', defaultValues, key));
}
} else {
int used = 0;
for (String key in keys) {
if (namedArguments.containsKey(key)) {
used++;
arguments.add(namedArguments[key]);
} else {
arguments.add(JS('var', r'#[#]', defaultValues, key));
}
}
if (used != namedArguments.length) {
return functionNoSuchMethod(function, arguments, namedArguments);
}
}
return JS('var', r'#.apply(#, #)', jsFunction, function, arguments);
}
}
static applyFunction(Function function, List positionalArguments,
Map<String, dynamic> namedArguments) {
// Dispatch on presence of named arguments to improve tree-shaking.
//
// This dispatch is as simple as possible to help the compiler detect the
// common case of `null` namedArguments, either via inlining or
// specialization.
return namedArguments == null
? applyFunctionWithPositionalArguments(function, positionalArguments)
: applyFunctionWithNamedArguments(
function, positionalArguments, namedArguments);
}
static applyFunctionWithPositionalArguments(
Function function, List positionalArguments) {
List arguments;
if (positionalArguments != null) {
if (JS('bool', '# instanceof Array', positionalArguments)) {
arguments = JS('JSArray', '#', positionalArguments);
} else {
arguments = new List.from(positionalArguments);
}
} else {
arguments = [];
}
if (arguments.length == 0) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX0);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#]()', function, selectorName);
}
} else if (arguments.length == 1) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX1);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0])', function, selectorName, arguments);
}
} else if (arguments.length == 2) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX2);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0],#[1])', function, selectorName, arguments,
arguments);
}
} else if (arguments.length == 3) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX3);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0],#[1],#[2])', function, selectorName, arguments,
arguments, arguments);
}
} else if (arguments.length == 4) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX4);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0],#[1],#[2],#[3])', function, selectorName,
arguments, arguments, arguments, arguments);
}
} else if (arguments.length == 5) {
String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX5);
if (JS('bool', '!!#[#]', function, selectorName)) {
return JS('', '#[#](#[0],#[1],#[2],#[3],#[4])', function, selectorName,
arguments, arguments, arguments, arguments, arguments);
}
}
return _genericApplyFunctionWithPositionalArguments(function, arguments);
}
static _genericApplyFunctionWithPositionalArguments(
Function function, List arguments) {
int argumentCount = arguments.length;
String selectorName =
'${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount';
var jsFunction = JS('var', '#[#]', function, selectorName);
if (jsFunction == null) {
var interceptor = getInterceptor(function);
jsFunction =
JS('', '#[#]', interceptor, JS_GET_NAME(JsGetName.CALL_CATCH_ALL));
if (jsFunction == null) {
return functionNoSuchMethod(function, arguments, null);
}
ReflectionInfo info = new ReflectionInfo(jsFunction);
int requiredArgumentCount = info.requiredParameterCount;
int maxArgumentCount =
requiredArgumentCount + info.optionalParameterCount;
if (info.areOptionalParametersNamed ||
requiredArgumentCount > argumentCount ||
maxArgumentCount < argumentCount) {
return functionNoSuchMethod(function, arguments, null);
}
arguments = new List.from(arguments);
for (int pos = argumentCount; pos < maxArgumentCount; pos++) {
arguments.add(getMetadata(info.defaultValue(pos)));
}
}
// We bound 'this' to [function] because of how we compile
// closures: escaped local variables are stored and accessed through
// [function].
return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
}
static applyFunctionWithNamedArguments(Function function,
List positionalArguments, Map<String, dynamic> namedArguments) {
if (namedArguments.isEmpty) {
return applyFunctionWithPositionalArguments(
function, positionalArguments);
}
// TODO(ahe): The following code can be shared with
// JsInstanceMirror.invoke.
var interceptor = getInterceptor(function);
var jsFunction =
JS('', '#[#]', interceptor, JS_GET_NAME(JsGetName.CALL_CATCH_ALL));
if (jsFunction == null) {
return functionNoSuchMethod(
function, positionalArguments, namedArguments);
}
ReflectionInfo info = new ReflectionInfo(jsFunction);
if (info == null || !info.areOptionalParametersNamed) {
return functionNoSuchMethod(
function, positionalArguments, namedArguments);
}
if (positionalArguments != null) {
positionalArguments = new List.from(positionalArguments);
} else {
positionalArguments = [];
}
// Check the number of positional arguments is valid.
if (info.requiredParameterCount != positionalArguments.length) {
return functionNoSuchMethod(
function, positionalArguments, namedArguments);
}
var defaultArguments = new Map();
for (int i = 0; i < info.optionalParameterCount; i++) {
int index = i + info.requiredParameterCount;
var parameterName = info.parameterNameInOrder(index);
var value = info.defaultValueInOrder(index);
var defaultValue = getMetadata(value);
defaultArguments[parameterName] = defaultValue;
}
bool bad = false;
namedArguments.forEach((String parameter, value) {
if (defaultArguments.containsKey(parameter)) {
defaultArguments[parameter] = value;
} else {
// Extraneous named argument.
bad = true;
}
});
if (bad) {
return functionNoSuchMethod(
function, positionalArguments, namedArguments);
}
positionalArguments.addAll(defaultArguments.values);
return JS('', '#.apply(#, #)', jsFunction, function, positionalArguments);
}
static StackTrace extractStackTrace(Error error) {
return getTraceFromException(JS('', r'#.$thrownJsError', error));
}
}
/// Helper class for allocating and using JS object literals as caches.
class JsCache {
/// Returns a JavaScript object suitable for use as a cache.
static allocate() {
var result = JS('=Object', 'Object.create(null)');
// Deleting a property makes V8 assume that it shouldn't create a hidden
// class for [result] and map transitions. Although these map transitions
// pay off if there are many cache hits for the same keys, it becomes
// really slow when there aren't many repeated hits.
JS('void', '#.x=0', result);
JS('void', 'delete #.x', result);
return result;
}
static fetch(cache, String key) {
return JS('', '#[#]', cache, key);
}
static void update(cache, String key, value) {
JS('void', '#[#] = #', cache, key, value);
}
}
/**
* Called by generated code to throw an illegal-argument exception,
* for example, if a non-integer index is given to an optimized
* indexed access.
*/
@NoInline()
iae(argument) {
throw argumentErrorValue(argument);
}
/**
* Called by generated code to throw an index-out-of-range exception, for
* example, if a bounds check fails in an optimized indexed access. This may
* also be called when the index is not an integer, in which case it throws an
* illegal-argument exception instead, like [iae], or when the receiver is null.
*/
@NoInline()
ioore(receiver, index) {
if (receiver == null) receiver.length; // Force a NoSuchMethodError.
throw diagnoseIndexError(receiver, index);
}
/**
* Diagnoses an indexing error. Returns the ArgumentError or RangeError that
* describes the problem.
*/
@NoInline()
Error diagnoseIndexError(indexable, index) {
if (index is! int) return new ArgumentError.value(index, 'index');
int length = indexable.length;
// The following returns the same error that would be thrown by calling
// [RangeError.checkValidIndex] with no optional parameters provided.
if (index < 0 || index >= length) {
return new RangeError.index(index, indexable, 'index', null, length);
}
// The above should always match, but if it does not, use the following.
return new RangeError.value(index, 'index');
}
/**
* Diagnoses a range error. Returns the ArgumentError or RangeError that
* describes the problem.
*/
@NoInline()
Error diagnoseRangeError(start, end, length) {
if (start is! int) {
return new ArgumentError.value(start, 'start');
}
if (start < 0 || start > length) {
return new RangeError.range(start, 0, length, 'start');
}
if (end != null) {
if (end is! int) {
return new ArgumentError.value(end, 'end');
}
if (end < start || end > length) {
return new RangeError.range(end, start, length, 'end');
}
}
// The above should always match, but if it does not, use the following.
return new ArgumentError.value(end, 'end');
}
stringLastIndexOfUnchecked(receiver, element, start) =>
JS('int', r'#.lastIndexOf(#, #)', receiver, element, start);
/// 'factory' for constructing ArgumentError.value to keep the call sites small.
@NoInline()
ArgumentError argumentErrorValue(object) {
return new ArgumentError.value(object);
}
checkNull(object) {
if (object == null) throw argumentErrorValue(object);
return object;
}
@NoInline()
checkNum(value) {
if (value is! num) throw argumentErrorValue(value);
return value;
}
checkInt(value) {
if (value is! int) throw argumentErrorValue(value);
return value;
}
checkBool(value) {
if (value is! bool) throw argumentErrorValue(value);
return value;
}
checkString(value) {
if (value is! String) throw argumentErrorValue(value);
return value;
}
/**
* Wrap the given Dart object and record a stack trace.
*
* The code in [unwrapException] deals with getting the original Dart
* object out of the wrapper again.
*/
@NoInline()
wrapException(ex) {
if (ex == null) ex = new NullThrownError();
var wrapper = JS('', 'new Error()');
// [unwrapException] looks for the property 'dartException'.
JS('void', '#.dartException = #', wrapper, ex);
if (JS('bool', '"defineProperty" in Object')) {
// Define a JavaScript getter for 'message'. This is to work around V8 bug
// (https://code.google.com/p/v8/issues/detail?id=2519). The default
// toString on Error returns the value of 'message' if 'name' is
// empty. Setting toString directly doesn't work, see the bug.
JS('void', 'Object.defineProperty(#, "message", { get: # })', wrapper,
DART_CLOSURE_TO_JS(toStringWrapper));
JS('void', '#.name = ""', wrapper);
} else {
// In the unlikely event the browser doesn't support Object.defineProperty,
// hope that it just calls toString.
JS('void', '#.toString = #', wrapper, DART_CLOSURE_TO_JS(toStringWrapper));
}
return wrapper;
}
/// Do not call directly.
toStringWrapper() {
// This method gets installed as toString on a JavaScript object. Due to the
// weird scope rules of JavaScript, JS 'this' will refer to that object.
return JS('', r'this.dartException').toString();
}
/**
* This wraps the exception and does the throw. It is possible to call this in
* a JS expression context, where the throw statement is not allowed. Helpers
* are never inlined, so we don't risk inlining the throw statement into an
* expression context.
*/
throwExpression(ex) {
JS('void', 'throw #', wrapException(ex));
}
throwRuntimeError(message) {
throw new RuntimeError(message);
}
throwUnsupportedError(message) {
throw new UnsupportedError(message);
}
throwAbstractClassInstantiationError(className) {
throw new AbstractClassInstantiationError(className);
}
// This is used in open coded for-in loops on arrays.
//
// checkConcurrentModificationError(a.length == startLength, a)
//
// is replaced in codegen by:
//
// a.length == startLength || throwConcurrentModificationError(a)
//
// TODO(sra): We would like to annotate this as @NoSideEffects() so that loops
// with no other effects can recognize that the array length does not
// change. However, in the usual case where the loop does have other effects,
// that causes the length in the loop condition to be phi(startLength,a.length),
// which causes confusion in range analysis and the insertion of a bounds check.
@NoInline()
checkConcurrentModificationError(sameLength, collection) {
if (true != sameLength) {
throwConcurrentModificationError(collection);
}
}
@NoInline()
throwConcurrentModificationError(collection) {
throw new ConcurrentModificationError(collection);
}
/**
* Helper class for building patterns recognizing native type errors.
*/
class TypeErrorDecoder {
// Field names are private to help tree-shaking.
/// A regular expression which matches is matched against an error message.
final String _pattern;
/// The group index of "arguments" in [_pattern], or -1 if _pattern has no
/// match for "arguments".
final int _arguments;
/// The group index of "argumentsExpr" in [_pattern], or -1 if _pattern has
/// no match for "argumentsExpr".
final int _argumentsExpr;
/// The group index of "expr" in [_pattern], or -1 if _pattern has no match
/// for "expr".
final int _expr;
/// The group index of "method" in [_pattern], or -1 if _pattern has no match
/// for "method".
final int _method;
/// The group index of "receiver" in [_pattern], or -1 if _pattern has no
/// match for "receiver".
final int _receiver;
/// Pattern used to recognize a NoSuchMethodError error (and
/// possibly extract the method name).
static final TypeErrorDecoder noSuchMethodPattern =
extractPattern(provokeCallErrorOn(buildJavaScriptObject()));
/// Pattern used to recognize an "object not a closure" error (and
/// possibly extract the method name).
static final TypeErrorDecoder notClosurePattern =
extractPattern(provokeCallErrorOn(buildJavaScriptObjectWithNonClosure()));
/// Pattern used to recognize a NoSuchMethodError on JavaScript null
/// call.
static final TypeErrorDecoder nullCallPattern =
extractPattern(provokeCallErrorOn(JS('', 'null')));
/// Pattern used to recognize a NoSuchMethodError on JavaScript literal null
/// call.
static final TypeErrorDecoder nullLiteralCallPattern =
extractPattern(provokeCallErrorOnNull());
/// Pattern used to recognize a NoSuchMethodError on JavaScript
/// undefined call.
static final TypeErrorDecoder undefinedCallPattern =
extractPattern(provokeCallErrorOn(JS('', 'void 0')));
/// Pattern used to recognize a NoSuchMethodError on JavaScript literal
/// undefined call.
static final TypeErrorDecoder undefinedLiteralCallPattern =
extractPattern(provokeCallErrorOnUndefined());
/// Pattern used to recognize a NoSuchMethodError on JavaScript null
/// property access.
static final TypeErrorDecoder nullPropertyPattern =
extractPattern(provokePropertyErrorOn(JS('', 'null')));
/// Pattern used to recognize a NoSuchMethodError on JavaScript literal null
/// property access.
static final TypeErrorDecoder nullLiteralPropertyPattern =
extractPattern(provokePropertyErrorOnNull());
/// Pattern used to recognize a NoSuchMethodError on JavaScript
/// undefined property access.
static final TypeErrorDecoder undefinedPropertyPattern =
extractPattern(provokePropertyErrorOn(JS('', 'void 0')));
/// Pattern used to recognize a NoSuchMethodError on JavaScript literal
/// undefined property access.
static final TypeErrorDecoder undefinedLiteralPropertyPattern =
extractPattern(provokePropertyErrorOnUndefined());
TypeErrorDecoder(this._arguments, this._argumentsExpr, this._expr,
this._method, this._receiver, this._pattern);
/// Returns a JavaScript object literal (map) with at most the
/// following keys:
///
/// * arguments: The arguments as formatted by the JavaScript
/// engine. No browsers are known to provide this information.
///
/// * argumentsExpr: The syntax of the arguments (JavaScript source
/// code). No browsers are known to provide this information.
///
/// * expr: The syntax of the receiver expression (JavaScript source
/// code). Firefox provides this information, for example: "$expr$.$method$
/// is not a function".
///
/// * method: The name of the called method (mangled name). At least Firefox
/// and Chrome/V8 provides this information, for example, "Object [object
/// Object] has no method '$method$'".
///
/// * receiver: The string representation of the receiver. Chrome/V8
/// used to provide this information (by calling user-defined
/// JavaScript toString on receiver), but it has degenerated into
/// "[object Object]" in recent versions.
matchTypeError(message) {
var match = JS(
'JSExtendableArray|Null', 'new RegExp(#).exec(#)', _pattern, message);
if (match == null) return null;
var result = JS('', 'Object.create(null)');
if (_arguments != -1) {
JS('', '#.arguments = #[# + 1]', result, match, _arguments);
}
if (_argumentsExpr != -1) {
JS('', '#.argumentsExpr = #[# + 1]', result, match, _argumentsExpr);
}
if (_expr != -1) {
JS('', '#.expr = #[# + 1]', result, match, _expr);
}
if (_method != -1) {
JS('', '#.method = #[# + 1]', result, match, _method);
}
if (_receiver != -1) {
JS('', '#.receiver = #[# + 1]', result, match, _receiver);
}
return result;
}
/// Builds a JavaScript Object with a toString method saying
/// r"$receiver$".
static buildJavaScriptObject() {
return JS('', r'{ toString: function() { return "$receiver$"; } }');
}
/// Builds a JavaScript Object with a toString method saying
/// r"$receiver$". The property "$method" is defined, but is not a function.
static buildJavaScriptObjectWithNonClosure() {
return JS(
'',
r'{ $method$: null, '
r'toString: function() { return "$receiver$"; } }');
}
/// Extract a pattern from a JavaScript TypeError message.
///
/// The patterns are extracted by forcing TypeErrors on known
/// objects thus forcing known strings into the error message. The
/// known strings are then replaced with wildcards which in theory
/// makes it possible to recognize the desired information even if
/// the error messages are reworded or translated.
static extractPattern(String message) {
// Some JavaScript implementations (V8 at least) include a
// representation of the receiver in the error message, however,
// this representation is not always [: receiver.toString() :],
// sometimes it is [: Object.prototype.toString(receiver) :], and
// sometimes it is an implementation specific method (but that
// doesn't seem to happen for object literals). So sometimes we
// get the text "[object Object]". The shortest way to get that
// string is using "String({})".
// See: http://code.google.com/p/v8/issues/detail?id=2519.
message = JS('String', r"#.replace(String({}), '$receiver$')", message);
// Since we want to create a new regular expression from an unknown string,
// we must escape all regular expression syntax.
message = quoteStringForRegExp(message);
// Look for the special pattern \$camelCase\$ (all the $ symbols
// have been escaped already), as we will soon be inserting
// regular expression syntax that we want interpreted by RegExp.
List<String> match =
JS('JSExtendableArray|Null', r'#.match(/\\\$[a-zA-Z]+\\\$/g)', message);
if (match == null) match = [];
// Find the positions within the substring matches of the error message
// components. This will help us extract information later, such as the
// method name.
int arguments = JS('int', '#.indexOf(#)', match, r'\$arguments\$');
int argumentsExpr = JS('int', '#.indexOf(#)', match, r'\$argumentsExpr\$');
int expr = JS('int', '#.indexOf(#)', match, r'\$expr\$');
int method = JS('int', '#.indexOf(#)', match, r'\$method\$');
int receiver = JS('int', '#.indexOf(#)', match, r'\$receiver\$');
// Replace the patterns with a regular expression wildcard.
// Note: in a perfect world, one would use "(.*)", but not in
// JavaScript, "." does not match newlines.
String pattern = JS(
'String',
r"#.replace(new RegExp('\\\\\\$arguments\\\\\\$', 'g'), "
r"'((?:x|[^x])*)')"
r".replace(new RegExp('\\\\\\$argumentsExpr\\\\\\$', 'g'), "
r"'((?:x|[^x])*)')"
r".replace(new RegExp('\\\\\\$expr\\\\\\$', 'g'), '((?:x|[^x])*)')"
r".replace(new RegExp('\\\\\\$method\\\\\\$', 'g'), '((?:x|[^x])*)')"
r".replace(new RegExp('\\\\\\$receiver\\\\\\$', 'g'), "
r"'((?:x|[^x])*)')",
message);
return new TypeErrorDecoder(
arguments, argumentsExpr, expr, method, receiver, pattern);
}
/// Provokes a TypeError and returns its message.
///
/// The error is provoked so all known variable content can be recognized and
/// a pattern can be inferred.
static String provokeCallErrorOn(expression) {
// This function is carefully created to maximize the possibility
// of decoding the TypeError message and turning it into a general
// pattern.
//
// The idea is to inject something known into something unknown. The
// unknown entity is the error message that the browser provides with a
// TypeError. It is a human readable message, possibly localized in a
// language no dart2js engineer understand. We assume that $name$ would
// never naturally occur in a human readable error message, yet it is easy
// to decode.
//
// For example, evaluate this in V8 version 3.13.7.6:
//
// var $expr$ = null; $expr$.$method$()
//
// The VM throws an instance of TypeError whose message property contains
// "Cannot call method '$method$' of null". We can then reasonably assume
// that if the string contains $method$, that's where the method name will
// be in general. Call this automatically reverse engineering the error
// format string in V8.
//
// So the error message from V8 is turned into this regular expression:
//
// "Cannot call method '(.*)' of null"
//
// Similarly, if we evaluate:
//
// var $expr$ = {toString: function() { return '$receiver$'; }};
// $expr$.$method$()
//
// We get this message: "Object $receiver$ has no method '$method$'"
//
// Which is turned into this regular expression:
//
// "Object (.*) has no method '(.*)'"
//
// Firefox/jsshell is slightly different, it tries to include the source
// code that caused the exception, so we get this message: "$expr$.$method$
// is not a function" which is turned into this regular expression:
//
// "(.*)\\.(.*) is not a function"
var function = JS('', r"""function($expr$) {
var $argumentsExpr$ = '$arguments$';
try {
$expr$.$method$($argumentsExpr$);
} catch (e) {
return e.message;
}
}""");
return JS('String', '(#)(#)', function, expression);
}
/// Similar to [provokeCallErrorOn], but provokes an error directly on
/// literal "null" expression.
static String provokeCallErrorOnNull() {
// See [provokeCallErrorOn] for a detailed explanation.
var function = JS('', r"""function() {
var $argumentsExpr$ = '$arguments$';
try {
null.$method$($argumentsExpr$);
} catch (e) {
return e.message;
}
}""");
return JS('String', '(#)()', function);
}
/// Similar to [provokeCallErrorOnNull], but provokes an error directly on
/// (void 0), that is, "undefined".
static String provokeCallErrorOnUndefined() {
// See [provokeCallErrorOn] for a detailed explanation.
var function = JS('', r"""function() {
var $argumentsExpr$ = '$arguments$';
try {
(void 0).$method$($argumentsExpr$);
} catch (e) {
return e.message;
}
}""");
return JS('String', '(#)()', function);
}
/// Similar to [provokeCallErrorOn], but provokes a property access
/// error.
static String provokePropertyErrorOn(expression) {
// See [provokeCallErrorOn] for a detailed explanation.
var function = JS('', r"""function($expr$) {
try {
$expr$.$method$;
} catch (e) {
return e.message;
}
}""");
return JS('String', '(#)(#)', function, expression);
}
/// Similar to [provokePropertyErrorOn], but provokes an property access
/// error directly on literal "null" expression.
static String provokePropertyErrorOnNull() {
// See [provokeCallErrorOn] for a detailed explanation.
var function = JS('', r"""function() {
try {
null.$method$;
} catch (e) {
return e.message;
}
}""");
return JS('String', '(#)()', function);
}
/// Similar to [provokePropertyErrorOnNull], but provokes an property access
/// error directly on (void 0), that is, "undefined".
static String provokePropertyErrorOnUndefined() {
// See [provokeCallErrorOn] for a detailed explanation.
var function = JS('', r"""function() {
try {
(void 0).$method$;
} catch (e) {
return e.message;
}
}""");
return JS('String', '(#)()', function);
}
}
class NullError extends Error implements NoSuchMethodError {
final String _message;
final String _method;
NullError(this._message, match)
: _method = match == null ? null : JS('', '#.method', match);
String toString() {
if (_method == null) return 'NullError: $_message';
return "NullError: method not found: '$_method' on null";
}
}
class JsNoSuchMethodError extends Error implements NoSuchMethodError {
final String _message;
final String _method;
final String _receiver;
JsNoSuchMethodError(this._message, match)
: _method = match == null ? null : JS('String|Null', '#.method', match),
_receiver =
match == null ? null : JS('String|Null', '#.receiver', match);
String toString() {
if (_method == null) return 'NoSuchMethodError: $_message';
if (_receiver == null) {
return "NoSuchMethodError: method not found: '$_method' ($_message)";
}
return "NoSuchMethodError: "
"method not found: '$_method' on '$_receiver' ($_message)";
}
}
class UnknownJsTypeError extends Error {
final String _message;
UnknownJsTypeError(this._message);
String toString() => _message.isEmpty ? 'Error' : 'Error: $_message';
}
/// A wrapper around an exception, much like the one created by [wrapException]
/// but with a pre-given stack-trace.
class ExceptionAndStackTrace {
dynamic dartException;
StackTrace stackTrace;
ExceptionAndStackTrace(this.dartException, this.stackTrace);
}
/**
* Called from catch blocks in generated code to extract the Dart
* exception from the thrown value. The thrown value may have been
* created by [wrapException] or it may be a 'native' JS exception.
*
* Some native exceptions are mapped to new Dart instances, others are
* returned unmodified.
*/
unwrapException(ex) {
/// If error implements Error, save [ex] in [error.$thrownJsError].
/// Otherwise, do nothing. Later, the stack trace can then be extracted from
/// [ex].
saveStackTrace(error) {
if (error is Error) {
var thrownStackTrace = JS('', r'#.$thrownJsError', error);
if (thrownStackTrace == null) {
JS('void', r'#.$thrownJsError = #', error, ex);
}
}
return error;
}
// Note that we are checking if the object has the property. If it
// has, it could be set to null if the thrown value is null.
if (ex == null) return null;
if (ex is ExceptionAndStackTrace) {
return saveStackTrace(ex.dartException);
}
if (JS('bool', 'typeof # !== "object"', ex)) return ex;
if (JS('bool', r'"dartException" in #', ex)) {
return saveStackTrace(JS('', r'#.dartException', ex));
} else if (!JS('bool', r'"message" in #', ex)) {
return ex;
}
// Grab hold of the exception message. This field is available on
// all supported browsers.
var message = JS('var', r'#.message', ex);
// Internet Explorer has an error number. This is the most reliable way to
// detect specific errors, so check for this first.
if (JS('bool', '"number" in #', ex) &&
JS('bool', 'typeof #.number == "number"', ex)) {
int number = JS('int', '#.number', ex);
// From http://msdn.microsoft.com/en-us/library/ie/hc53e755(v=vs.94).aspx
// "number" is a 32-bit word. The error code is the low 16 bits, and the
// facility code is the upper 16 bits.
var ieErrorCode = number & 0xffff;
var ieFacilityNumber = (number >> 16) & 0x1fff;
// http://msdn.microsoft.com/en-us/library/aa264975(v=vs.60).aspx
// http://msdn.microsoft.com/en-us/library/ie/1dk3k160(v=vs.94).aspx
if (ieFacilityNumber == 10) {
switch (ieErrorCode) {
case 438:
return saveStackTrace(
new JsNoSuchMethodError('$message (Error $ieErrorCode)', null));
case 445:
case 5007:
return saveStackTrace(
new NullError('$message (Error $ieErrorCode)', null));
}
}
}
if (JS('bool', r'# instanceof TypeError', ex)) {
var match;
// Using JS to give type hints to the compiler to help tree-shaking.
// TODO(ahe): That should be unnecessary due to type inference.
var nsme = TypeErrorDecoder.noSuchMethodPattern;
var notClosure = TypeErrorDecoder.notClosurePattern;
var nullCall = TypeErrorDecoder.nullCallPattern;
var nullLiteralCall = TypeErrorDecoder.nullLiteralCallPattern;
var undefCall = TypeErrorDecoder.undefinedCallPattern;
var undefLiteralCall = TypeErrorDecoder.undefinedLiteralCallPattern;
var nullProperty = TypeErrorDecoder.nullPropertyPattern;
var nullLiteralProperty = TypeErrorDecoder.nullLiteralPropertyPattern;
var undefProperty = TypeErrorDecoder.undefinedPropertyPattern;
var undefLiteralProperty = TypeErrorDecoder.undefinedLiteralPropertyPattern;
if ((match = nsme.matchTypeError(message)) != null) {
return saveStackTrace(new JsNoSuchMethodError(message, match));
} else if ((match = notClosure.matchTypeError(message)) != null) {
// notClosure may match "({c:null}).c()" or "({c:1}).c()", so we
// cannot tell if this an attempt to invoke call on null or a
// non-function object.
// But we do know the method name is "call".
JS('', '#.method = "call"', match);
return saveStackTrace(new JsNoSuchMethodError(message, match));
} else if ((match = nullCall.matchTypeError(message)) != null ||
(match = nullLiteralCall.matchTypeError(message)) != null ||
(match = undefCall.matchTypeError(message)) != null ||
(match = undefLiteralCall.matchTypeError(message)) != null ||
(match = nullProperty.matchTypeError(message)) != null ||
(match = nullLiteralCall.matchTypeError(message)) != null ||
(match = undefProperty.matchTypeError(message)) != null ||
(match = undefLiteralProperty.matchTypeError(message)) != null) {
return saveStackTrace(new NullError(message, match));
}
// If we cannot determine what kind of error this is, we fall back
// to reporting this as a generic error. It's probably better than
// nothing.
return saveStackTrace(
new UnknownJsTypeError(message is String ? message : ''));
}
if (JS('bool', r'# instanceof RangeError', ex)) {
if (message is String && contains(message, 'call stack')) {
return new StackOverflowError();
}
// In general, a RangeError is thrown when trying to pass a number as an
// argument to a function that does not allow a range that includes that
// number. Translate to a Dart ArgumentError with the same message.
// TODO(sra): Translate to RangeError.
message = tryStringifyException(ex);
if (message is String) {
message = JS('String', r'#.replace(/^RangeError:\s*/, "")', message);
}
return saveStackTrace(new ArgumentError(message));
}
// Check for the Firefox specific stack overflow signal.
if (JS(
'bool',
r'typeof InternalError == "function" && # instanceof InternalError',
ex)) {
if (message is String && message == 'too much recursion') {
return new StackOverflowError();
}
}
// Just return the exception. We should not wrap it because in case
// the exception comes from the DOM, it is a JavaScript
// object backed by a native Dart class.
return ex;
}
String tryStringifyException(ex) {
// Since this function is called from [unwrapException] which is called from
// code injected into a catch-clause, use JavaScript try-catch to avoid a
// potential loop if stringifying crashes.
return JS(
'String|Null',
r'''
(function(ex) {
try {
return String(ex);
} catch (e) {}
return null;
})(#)
''',
ex);
}
/**
* Called by generated code to fetch the stack trace from an
* exception. Should never return null.
*/
StackTrace getTraceFromException(exception) {
if (exception is ExceptionAndStackTrace) {
return exception.stackTrace;
}
if (exception == null) return new _StackTrace(exception);
_StackTrace trace = JS('_StackTrace|Null', r'#.$cachedTrace', exception);
if (trace != null) return trace;
trace = new _StackTrace(exception);
return JS('_StackTrace', r'#.$cachedTrace = #', exception, trace);
}
class _StackTrace implements StackTrace {
var _exception;
String _trace;
_StackTrace(this._exception);
String toString() {
if (_trace != null) return JS('String', '#', _trace);
String trace;
if (JS('bool', '# !== null', _exception) &&
JS('bool', 'typeof # === "object"', _exception)) {
trace = JS('String|Null', r'#.stack', _exception);
}
return _trace = (trace == null) ? '' : trace;
}
}
int objectHashCode(var object) {
if (object == null || JS('bool', "typeof # != 'object'", object)) {
return object.hashCode;
} else {
return Primitives.objectHashCode(object);
}
}
/**
* Called by generated code to build a map literal. [keyValuePairs] is
* a list of key, value, key, value, ..., etc.
*/
fillLiteralMap(keyValuePairs, Map result) {
// TODO(johnniwinther): Use JSArray to optimize this code instead of calling
// [getLength] and [getIndex].
int index = 0;
int length = getLength(keyValuePairs);
while (index < length) {
var key = getIndex(keyValuePairs, index++);
var value = getIndex(keyValuePairs, index++);
result[key] = value;
}
return result;
}
invokeClosure(Function closure, int numberOfArguments, var arg1, var arg2,
var arg3, var arg4) {
switch (numberOfArguments) {
case 0:
return closure();
case 1:
return closure(arg1);
case 2:
return closure(arg1, arg2);
case 3:
return closure(arg1, arg2, arg3);
case 4:
return closure(arg1, arg2, arg3, arg4);
}
throw new Exception('Unsupported number of arguments for wrapped closure');
}
/**
* Called by generated code to convert a Dart closure to a JS
* closure when the Dart closure is passed to the DOM.
*/
convertDartClosureToJS(closure, int arity) {
if (closure == null) return null;
var function = JS('var', r'#.$identity', closure);
if (JS('bool', r'!!#', function)) return function;
function = JS(
'var',
r'''
(function(closure, arity, invoke) {
return function(a1, a2, a3, a4) {
return invoke(closure, arity, a1, a2, a3, a4);
};
})(#,#,#)''',
closure,
arity,
DART_CLOSURE_TO_JS(invokeClosure));
JS('void', r'#.$identity = #', closure, function);
return function;
}
/// Superclass for Dart closures.
///
/// All static, tear-off, function declaration and function expression closures
/// extend this class, but classes that implement Function via a `call` method
/// do not.
abstract class Closure implements Function {
// TODO(ahe): These constants must be in sync with
// reflection_data_parser.dart.
static const FUNCTION_INDEX = 0;
static const NAME_INDEX = 1;
static const CALL_NAME_INDEX = 2;
static const REQUIRED_PARAMETER_INDEX = 3;
static const OPTIONAL_PARAMETER_INDEX = 4;
static const DEFAULT_ARGUMENTS_INDEX = 5;
/**
* Global counter to prevent reusing function code objects.
*
* V8 will share the underlying function code objects when the same string is
* passed to "new Function". Shared function code objects can lead to
* sub-optimal performance due to polymorphism, and can be prevented by
* ensuring the strings are different, for example, by generating a local
* variable with a name dependent on [functionCounter].
*/
static int functionCounter = 0;
Closure();
/**
* Creates a new closure class for use by implicit getters associated with a
* method.
*
* In other words, creates a tear-off closure.
*
* Called from [closureFromTearOff] as well as from reflection when tearing
* of a method via `getField`.
*
* This method assumes that [functions] was created by the JavaScript function
* `addStubs` in `reflection_data_parser.dart`. That is, a list of JavaScript
* function objects with properties `$stubName` and `$callName`.
*
* Further assumes that [reflectionInfo] is the end of the array created by
* [dart2js.js_emitter.ContainerBuilder.addMemberMethod] starting with
* required parameter count or, in case of the new emitter, the runtime
* representation of the function's type.
*
* Caution: this function may be called when building constants.
* TODO(ahe): Don't call this function when building constants.
*/
static fromTearOff(
receiver,
List functions,
int applyTrampolineIndex,
var reflectionInfo,
bool isStatic,
jsArguments,
String propertyName,
) {
JS_EFFECT(() {
// The functions are called here to model the calls from JS forms below.
// The types in the JS forms in the arguments are propagated in type
// inference.
BoundClosure.receiverOf(JS('BoundClosure', '0'));
BoundClosure.selfOf(JS('BoundClosure', '0'));
getType(JS('int', '0'));
});
// TODO(ahe): All the place below using \$ should be rewritten to go
// through the namer.
var function = JS('', '#[#]', functions, 0);
String name = JS('String|Null', '#.\$stubName', function);
String callName = JS('String|Null', '#[#]', function,
JS_GET_NAME(JsGetName.CALL_NAME_PROPERTY));
// This variable holds either an index into the types-table, or a function
// that can compute a function-rti. (The latter is necessary if the type
// is dependent on generic arguments).
var functionType;
if (reflectionInfo is List) {
JS('', '#.\$reflectionInfo = #', function, reflectionInfo);
ReflectionInfo info = new ReflectionInfo(function);
functionType = info.functionType;
} else {
functionType = reflectionInfo;
}
// function tmp() {};
// tmp.prototype = BC.prototype;
// var proto = new tmp;
// for each computed prototype property:
// proto[property] = ...;
// proto._init = BC;
// var dynClosureConstructor =
// new Function('self', 'target', 'receiver', 'name',
// 'this._init(self, target, receiver, name)');
// proto.constructor = dynClosureConstructor;
// dynClosureConstructor.prototype = proto;
// return dynClosureConstructor;
// We need to create a new subclass of TearOffClosure, one of StaticClosure
// or BoundClosure. For this, we need to create an object whose prototype
// is the prototype is either StaticClosure.prototype or
// BoundClosure.prototype, respectively in pseudo JavaScript code. The
// simplest way to access the JavaScript construction function of a Dart
// class is to create an instance and access its constructor property.
// Creating an instance ensures that any lazy class initialization has taken
// place. The newly created instance could in theory be used directly as the
// prototype, but it might include additional fields that we don't need. So
// we only use the new instance to access the constructor property and use
// Object.create to create the desired prototype.
//
// TODO(sra): Perhaps cache the prototype to avoid the allocation.
var prototype = isStatic
? JS('StaticClosure', 'Object.create(#.constructor.prototype)',
new StaticClosure())
: JS('BoundClosure', 'Object.create(#.constructor.prototype)',
new BoundClosure(null, null, null, null));
JS('', '#.\$initialize = #', prototype, JS('', '#.constructor', prototype));