| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library _interceptors; | |
| 6 | |
| 7 import 'dart:_js_embedded_names' show | |
| 8 DISPATCH_PROPERTY_NAME, | |
| 9 TYPE_TO_INTERCEPTOR_MAP; | |
| 10 | |
| 11 import 'dart:collection'; | |
| 12 import 'dart:_internal' hide Symbol; | |
| 13 import "dart:_internal" as _symbol_dev show Symbol; | |
| 14 import 'dart:_js_helper' show allMatchesInStringUnchecked, | |
| 15 Null, | |
| 16 JSSyntaxRegExp, | |
| 17 Primitives, | |
| 18 argumentErrorValue, | |
| 19 checkInt, | |
| 20 checkNull, | |
| 21 checkNum, | |
| 22 checkString, | |
| 23 defineProperty, | |
| 24 diagnoseIndexError, | |
| 25 getRuntimeType, | |
| 26 initNativeDispatch, | |
| 27 initNativeDispatchFlag, | |
| 28 regExpGetNative, | |
| 29 regExpCaptureCount, | |
| 30 stringContainsUnchecked, | |
| 31 stringIndexOfStringUnchecked, | |
| 32 stringLastIndexOfUnchecked, | |
| 33 stringReplaceAllFuncUnchecked, | |
| 34 stringReplaceAllUnchecked, | |
| 35 stringReplaceFirstUnchecked, | |
| 36 stringReplaceFirstMappedUnchecked, | |
| 37 stringReplaceRangeUnchecked, | |
| 38 lookupAndCacheInterceptor, | |
| 39 lookupDispatchRecord, | |
| 40 StringMatch, | |
| 41 firstMatchAfter, | |
| 42 NoInline; | |
| 43 import 'dart:_foreign_helper' show | |
| 44 JS, | |
| 45 JS_EFFECT, | |
| 46 JS_EMBEDDED_GLOBAL, | |
| 47 JS_INTERCEPTOR_CONSTANT, | |
| 48 JS_STRING_CONCAT; | |
| 49 import 'dart:math' show Random; | |
| 50 | |
| 51 part 'js_array.dart'; | |
| 52 part 'js_number.dart'; | |
| 53 part 'js_string.dart'; | |
| 54 | |
| 55 String _symbolToString(Symbol symbol) => _symbol_dev.Symbol.getName(symbol); | |
| 56 | |
| 57 _symbolMapToStringMap(Map<Symbol, dynamic> map) { | |
| 58 if (map == null) return null; | |
| 59 var result = new Map<String, dynamic>(); | |
| 60 map.forEach((Symbol key, value) { | |
| 61 result[_symbolToString(key)] = value; | |
| 62 }); | |
| 63 return result; | |
| 64 } | |
| 65 | |
| 66 /** | |
| 67 * Get the interceptor for [object]. Called by the compiler when it needs | |
| 68 * to emit a call to an intercepted method, that is a method that is | |
| 69 * defined in an interceptor class. | |
| 70 */ | |
| 71 getInterceptor(object) { | |
| 72 // This is a magic method: the compiler does specialization of it | |
| 73 // depending on the uses of intercepted methods and instantiated | |
| 74 // primitive types. | |
| 75 | |
| 76 // The [JS] call prevents the type analyzer from making assumptions about the | |
| 77 // return type. | |
| 78 return JS('', 'void 0'); | |
| 79 } | |
| 80 | |
| 81 getDispatchProperty(object) { | |
| 82 return JS('', '#[#]', | |
| 83 object, JS_EMBEDDED_GLOBAL('String', DISPATCH_PROPERTY_NAME)); | |
| 84 } | |
| 85 | |
| 86 setDispatchProperty(object, value) { | |
| 87 defineProperty(object, | |
| 88 JS_EMBEDDED_GLOBAL('String', DISPATCH_PROPERTY_NAME), | |
| 89 value); | |
| 90 } | |
| 91 | |
| 92 // Avoid inlining this method because inlining gives us multiple allocation | |
| 93 // points for records which is bad because it leads to polymorphic access. | |
| 94 @NoInline() | |
| 95 makeDispatchRecord(interceptor, proto, extension, indexability) { | |
| 96 // Dispatch records are stored in the prototype chain, and in some cases, on | |
| 97 // instances. | |
| 98 // | |
| 99 // The record layout and field usage is designed to minimize the number of | |
| 100 // operations on the common paths. | |
| 101 // | |
| 102 // [interceptor] is the interceptor - a holder of methods for the object, | |
| 103 // i.e. the prototype of the interceptor class. | |
| 104 // | |
| 105 // [proto] is usually the prototype, used to check that the dispatch record | |
| 106 // matches the object and is not the dispatch record of a superclass. Other | |
| 107 // values: | |
| 108 // - `false` for leaf classes that need no check. | |
| 109 // - `true` for Dart classes where the object is its own interceptor (unused) | |
| 110 // - a function used to continue matching. | |
| 111 // | |
| 112 // [extension] is used for irregular cases. | |
| 113 // | |
| 114 // [indexability] is used to cache whether or not the object | |
| 115 // implements JavaScriptIndexingBehavior. | |
| 116 // | |
| 117 // proto interceptor extension action | |
| 118 // ----- ----------- --------- ------ | |
| 119 // false I use interceptor I | |
| 120 // true - use object | |
| 121 // P I if object's prototype is P, use I | |
| 122 // F - P if object's prototype is P, call F | |
| 123 | |
| 124 return JS('', '{i: #, p: #, e: #, x: #}', | |
| 125 interceptor, proto, extension, indexability); | |
| 126 } | |
| 127 | |
| 128 dispatchRecordInterceptor(record) => JS('', '#.i', record); | |
| 129 dispatchRecordProto(record) => JS('', '#.p', record); | |
| 130 dispatchRecordExtension(record) => JS('', '#.e', record); | |
| 131 dispatchRecordIndexability(record) => JS('bool|Null', '#.x', record); | |
| 132 | |
| 133 /** | |
| 134 * Returns the interceptor for a native class instance. Used by | |
| 135 * [getInterceptor]. | |
| 136 */ | |
| 137 getNativeInterceptor(object) { | |
| 138 var record = getDispatchProperty(object); | |
| 139 | |
| 140 if (record == null) { | |
| 141 if (initNativeDispatchFlag == null) { | |
| 142 initNativeDispatch(); | |
| 143 record = getDispatchProperty(object); | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 if (record != null) { | |
| 148 var proto = dispatchRecordProto(record); | |
| 149 if (false == proto) return dispatchRecordInterceptor(record); | |
| 150 if (true == proto) return object; | |
| 151 var objectProto = JS('', 'Object.getPrototypeOf(#)', object); | |
| 152 if (JS('bool', '# === #', proto, objectProto)) { | |
| 153 return dispatchRecordInterceptor(record); | |
| 154 } | |
| 155 | |
| 156 var extension = dispatchRecordExtension(record); | |
| 157 if (JS('bool', '# === #', extension, objectProto)) { | |
| 158 // TODO(sra): The discriminator returns a tag. The tag is an uncached or | |
| 159 // instance-cached tag, defaulting to instance-cached if caching | |
| 160 // unspecified. | |
| 161 var discriminatedTag = JS('', '(#)(#, #)', proto, object, record); | |
| 162 throw new UnimplementedError('Return interceptor for $discriminatedTag'); | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 var interceptor = lookupAndCacheInterceptor(object); | |
| 167 if (interceptor == null) { | |
| 168 // JavaScript Objects created via object literals and `Object.create(null)` | |
| 169 // are 'plain' Objects. This test could be simplified and the dispatch path | |
| 170 // be faster if Object.prototype was pre-patched with a non-leaf dispatch | |
| 171 // record. | |
| 172 var proto = JS('', 'Object.getPrototypeOf(#)', object); | |
| 173 if (JS('bool', '# == null || # === Object.prototype', proto, proto)) { | |
| 174 return JS_INTERCEPTOR_CONSTANT(PlainJavaScriptObject); | |
| 175 } else { | |
| 176 return JS_INTERCEPTOR_CONSTANT(UnknownJavaScriptObject); | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 return interceptor; | |
| 181 } | |
| 182 | |
| 183 /** | |
| 184 * Data structure used to map a [Type] to the [Interceptor] and constructors for | |
| 185 * that type. It is JavaScript array of 3N entries of adjacent slots containing | |
| 186 * a [Type], followed by an [Interceptor] class for the type, followed by a | |
| 187 * JavaScript object map for the constructors. | |
| 188 * | |
| 189 * The value of this variable is set by the compiler and contains only types | |
| 190 * that are user extensions of native classes where the type occurs as a | |
| 191 * constant in the program. | |
| 192 * | |
| 193 * The compiler, in CustomElementsAnalysis, assumes that [typeToInterceptorMap] | |
| 194 * is accessed only by code that also calls [findIndexForWebComponentType]. If | |
| 195 * this assumption is invalidated, the compiler will have to be updated. | |
| 196 */ | |
| 197 get typeToInterceptorMap { | |
| 198 return JS_EMBEDDED_GLOBAL('', TYPE_TO_INTERCEPTOR_MAP); | |
| 199 } | |
| 200 | |
| 201 int findIndexForNativeSubclassType(Type type) { | |
| 202 if (JS('bool', '# == null', typeToInterceptorMap)) return null; | |
| 203 List map = JS('JSFixedArray', '#', typeToInterceptorMap); | |
| 204 for (int i = 0; i + 1 < map.length; i += 3) { | |
| 205 if (type == map[i]) { | |
| 206 return i; | |
| 207 } | |
| 208 } | |
| 209 return null; | |
| 210 } | |
| 211 | |
| 212 findInterceptorConstructorForType(Type type) { | |
| 213 var index = findIndexForNativeSubclassType(type); | |
| 214 if (index == null) return null; | |
| 215 List map = JS('JSFixedArray', '#', typeToInterceptorMap); | |
| 216 return map[index + 1]; | |
| 217 } | |
| 218 | |
| 219 /** | |
| 220 * Returns a JavaScript function that runs the constructor on its argument, or | |
| 221 * `null` if there is no such constructor. | |
| 222 * | |
| 223 * The returned function takes one argument, the web component object. | |
| 224 */ | |
| 225 findConstructorForNativeSubclassType(Type type, String name) { | |
| 226 var index = findIndexForNativeSubclassType(type); | |
| 227 if (index == null) return null; | |
| 228 List map = JS('JSFixedArray', '#', typeToInterceptorMap); | |
| 229 var constructorMap = map[index + 2]; | |
| 230 var constructorFn = JS('', '#[#]', constructorMap, name); | |
| 231 return constructorFn; | |
| 232 } | |
| 233 | |
| 234 findInterceptorForType(Type type) { | |
| 235 var constructor = findInterceptorConstructorForType(type); | |
| 236 if (constructor == null) return null; | |
| 237 return JS('', '#.prototype', constructor); | |
| 238 } | |
| 239 | |
| 240 /** | |
| 241 * The base interceptor class. | |
| 242 * | |
| 243 * The code `r.foo(a)` is compiled to `getInterceptor(r).foo$1(r, a)`. The | |
| 244 * value returned by [getInterceptor] holds the methods separately from the | |
| 245 * state of the instance. The compiler converts the methods on an interceptor | |
| 246 * to take the Dart `this` argument as an explicit `receiver` argument. The | |
| 247 * JavaScript `this` parameter is bound to the interceptor. | |
| 248 * | |
| 249 * In order to have uniform call sites, if a method is defined on an | |
| 250 * interceptor, methods of that name on plain unintercepted classes also use the | |
| 251 * interceptor calling convention. The plain classes are _self-interceptors_, | |
| 252 * and for them, `getInterceptor(r)` returns `r`. Methods on plain | |
| 253 * unintercepted classes have a redundant `receiver` argument and, to enable | |
| 254 * some optimizations, must ignore `receiver` in favour of `this`. | |
| 255 * | |
| 256 * In the case of mixins, a method may be placed on both an intercepted class | |
| 257 * and an unintercepted class. In this case, the method must use the `receiver` | |
| 258 * parameter. | |
| 259 * | |
| 260 * | |
| 261 * There are various optimizations of the general call pattern. | |
| 262 * | |
| 263 * When the interceptor can be statically determined, it can be used directly: | |
| 264 * | |
| 265 * CONSTANT_INTERCEPTOR.foo$1(r, a) | |
| 266 * | |
| 267 * If there are only a few classes, [getInterceptor] can be specialized with a | |
| 268 * more efficient dispatch: | |
| 269 * | |
| 270 * getInterceptor$specialized(r).foo$1(r, a) | |
| 271 * | |
| 272 * If it can be determined that the receiver is an unintercepted class, it can | |
| 273 * be called directly: | |
| 274 * | |
| 275 * r.foo$1(r, a) | |
| 276 * | |
| 277 * If, further, it is known that the call site cannot call a foo that is | |
| 278 * mixed-in to a native class, then it is known that the explicit receiver is | |
| 279 * ignored, and space-saving dummy value can be passed instead: | |
| 280 * | |
| 281 * r.foo$1(0, a) | |
| 282 * | |
| 283 * This class defines implementations of *all* methods on [Object] so no | |
| 284 * interceptor inherits an implementation from [Object]. This enables the | |
| 285 * implementations on Object to ignore the explicit receiver argument, which | |
| 286 * allows dummy receiver optimization. | |
| 287 */ | |
| 288 abstract class Interceptor { | |
| 289 const Interceptor(); | |
| 290 | |
| 291 bool operator ==(other) => identical(this, other); | |
| 292 | |
| 293 int get hashCode => Primitives.objectHashCode(this); | |
| 294 | |
| 295 String toString() => Primitives.objectToHumanReadableString(this); | |
| 296 | |
| 297 // [Interceptor.noSuchMethod] is identical to [Object.noSuchMethod]. However, | |
| 298 // each copy is compiled differently. The presence of the method on an | |
| 299 // Interceptor class forces [noSuchMethod] to use interceptor calling | |
| 300 // convention. In the [Interceptor] version, `this` is the explicit receiver | |
| 301 // argument. In the [Object] version, as Object is not an intercepted class, | |
| 302 // `this` is the JavaScript receiver, and the explicit receiver is ignored. | |
| 303 // The noSuchMethod stubs for selectors that use the interceptor calling | |
| 304 // convention do not know the calling convention and forward `this` and | |
| 305 // `receiver` to one of these noSuchMethod implementations which selects the | |
| 306 // correct Dart receiver. | |
| 307 // | |
| 308 // We don't allow [noSuchMethod] on intercepted classes (that would force all | |
| 309 // calls to use interceptor calling convention). If we did allow it, the | |
| 310 // interceptor context would select the correct `this`. | |
| 311 dynamic noSuchMethod(Invocation invocation) { | |
| 312 throw new NoSuchMethodError( | |
| 313 this, | |
| 314 invocation.memberName, | |
| 315 invocation.positionalArguments, | |
| 316 invocation.namedArguments); | |
| 317 } | |
| 318 | |
| 319 Type get runtimeType => getRuntimeType(this); | |
| 320 } | |
| 321 | |
| 322 /** | |
| 323 * The interceptor class for [bool]. | |
| 324 */ | |
| 325 class JSBool extends Interceptor implements bool { | |
| 326 const JSBool(); | |
| 327 | |
| 328 // Note: if you change this, also change the function [S]. | |
| 329 String toString() => JS('String', r'String(#)', this); | |
| 330 | |
| 331 // The values here are SMIs, co-prime and differ about half of the bit | |
| 332 // positions, including the low bit, so they are different mod 2^k. | |
| 333 int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811); | |
| 334 | |
| 335 Type get runtimeType => bool; | |
| 336 } | |
| 337 | |
| 338 /** | |
| 339 * The interceptor class for [Null]. | |
| 340 * | |
| 341 * This class defines implementations for *all* methods on [Object] since | |
| 342 * the methods on Object assume the receiver is non-null. This means that | |
| 343 * JSNull will always be in the interceptor set for methods defined on Object. | |
| 344 */ | |
| 345 class JSNull extends Interceptor implements Null { | |
| 346 const JSNull(); | |
| 347 | |
| 348 bool operator ==(other) => identical(null, other); | |
| 349 | |
| 350 // Note: if you change this, also change the function [S]. | |
| 351 String toString() => 'null'; | |
| 352 | |
| 353 int get hashCode => 0; | |
| 354 | |
| 355 // The spec guarantees that `null` is the singleton instance of the `Null` | |
| 356 // class. In the mirrors library we also have to patch the `type` getter to | |
| 357 // special case `null`. | |
| 358 Type get runtimeType => Null; | |
| 359 | |
| 360 dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); | |
| 361 } | |
| 362 | |
| 363 /** | |
| 364 * The supertype for JSString and JSArray. Used by the backend as to | |
| 365 * have a type mask that contains the objects that we can use the | |
| 366 * native JS [] operator and length on. | |
| 367 */ | |
| 368 abstract class JSIndexable { | |
| 369 int get length; | |
| 370 operator[](int index); | |
| 371 } | |
| 372 | |
| 373 /** | |
| 374 * The supertype for JSMutableArray and | |
| 375 * JavaScriptIndexingBehavior. Used by the backend to have a type mask | |
| 376 * that contains the objects we can use the JS []= operator on. | |
| 377 */ | |
| 378 abstract class JSMutableIndexable extends JSIndexable { | |
| 379 operator[]=(int index, var value); | |
| 380 } | |
| 381 | |
| 382 /** | |
| 383 * The interface implemented by JavaScript objects. These are methods in | |
| 384 * addition to the regular Dart Object methods like [Object.hashCode]. | |
| 385 * | |
| 386 * This is the type that should be exported by a JavaScript interop library. | |
| 387 */ | |
| 388 abstract class JSObject { | |
| 389 } | |
| 390 | |
| 391 | |
| 392 /** | |
| 393 * Interceptor base class for JavaScript objects not recognized as some more | |
| 394 * specific native type. | |
| 395 */ | |
| 396 abstract class JavaScriptObject extends Interceptor implements JSObject { | |
| 397 const JavaScriptObject(); | |
| 398 | |
| 399 // It would be impolite to stash a property on the object. | |
| 400 int get hashCode => 0; | |
| 401 | |
| 402 Type get runtimeType => JSObject; | |
| 403 } | |
| 404 | |
| 405 | |
| 406 /** | |
| 407 * Interceptor for plain JavaScript objects created as JavaScript object | |
| 408 * literals or `new Object()`. | |
| 409 */ | |
| 410 class PlainJavaScriptObject extends JavaScriptObject { | |
| 411 const PlainJavaScriptObject(); | |
| 412 } | |
| 413 | |
| 414 | |
| 415 /** | |
| 416 * Interceptor for unclassified JavaScript objects, typically objects with a | |
| 417 * non-trivial prototype chain. | |
| 418 * | |
| 419 * This class also serves as a fallback for unknown JavaScript exceptions. | |
| 420 */ | |
| 421 class UnknownJavaScriptObject extends JavaScriptObject { | |
| 422 const UnknownJavaScriptObject(); | |
| 423 | |
| 424 String toString() => JS('String', 'String(#)', this); | |
| 425 } | |
| OLD | NEW |