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