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 |