| OLD | NEW |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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 js_backend.interceptor_data; | 5 library js_backend.interceptor_data; |
| 6 | 6 |
| 7 import '../common/names.dart' show Identifiers; | 7 import '../common/names.dart' show Identifiers; |
| 8 import '../common/resolution.dart' show Resolution; | 8 import '../common/resolution.dart' show Resolution; |
| 9 import '../common_elements.dart' show CommonElements; | 9 import '../common_elements.dart' show CommonElements, ElementEnvironment; |
| 10 import '../elements/elements.dart'; | 10 import '../elements/elements.dart' show MemberElement; |
| 11 import '../elements/entities.dart'; | 11 import '../elements/entities.dart'; |
| 12 import '../elements/types.dart'; | 12 import '../elements/types.dart'; |
| 13 import '../js/js.dart' as jsAst; | 13 import '../js/js.dart' as jsAst; |
| 14 import '../types/types.dart' show TypeMask; | 14 import '../types/types.dart' show TypeMask; |
| 15 import '../universe/selector.dart'; | 15 import '../universe/selector.dart'; |
| 16 import '../world.dart' show ClosedWorld; | 16 import '../world.dart' show ClosedWorld; |
| 17 import 'backend_helpers.dart'; | 17 import 'backend_helpers.dart'; |
| 18 import 'namer.dart'; | 18 import 'namer.dart'; |
| 19 import 'native_data.dart'; | 19 import 'native_data.dart'; |
| 20 | 20 |
| (...skipping 28 matching lines...) Expand all Loading... |
| 49 } | 49 } |
| 50 | 50 |
| 51 class InterceptorDataImpl implements InterceptorData { | 51 class InterceptorDataImpl implements InterceptorData { |
| 52 final NativeBasicData _nativeData; | 52 final NativeBasicData _nativeData; |
| 53 final BackendHelpers _helpers; | 53 final BackendHelpers _helpers; |
| 54 final ClosedWorld _closedWorld; | 54 final ClosedWorld _closedWorld; |
| 55 | 55 |
| 56 /// The members of instantiated interceptor classes: maps a member name to the | 56 /// The members of instantiated interceptor classes: maps a member name to the |
| 57 /// list of members that have that name. This map is used by the codegen to | 57 /// list of members that have that name. This map is used by the codegen to |
| 58 /// know whether a send must be intercepted or not. | 58 /// know whether a send must be intercepted or not. |
| 59 final Map<String, Set<Element>> _interceptedElements; | 59 final Map<String, Set<MemberEntity>> _interceptedElements; |
| 60 | 60 |
| 61 /// Set of classes whose methods are intercepted. | 61 /// Set of classes whose methods are intercepted. |
| 62 final Set<ClassElement> _interceptedClasses; | 62 final Set<ClassEntity> _interceptedClasses; |
| 63 | 63 |
| 64 /// Set of classes used as mixins on intercepted (native and primitive) | 64 /// Set of classes used as mixins on intercepted (native and primitive) |
| 65 /// classes. Methods on these classes might also be mixed in to regular Dart | 65 /// classes. Methods on these classes might also be mixed in to regular Dart |
| 66 /// (unintercepted) classes. | 66 /// (unintercepted) classes. |
| 67 final Set<ClassElement> _classesMixedIntoInterceptedClasses; | 67 final Set<ClassEntity> _classesMixedIntoInterceptedClasses; |
| 68 | 68 |
| 69 /// The members of mixin classes that are mixed into an instantiated | 69 /// The members of mixin classes that are mixed into an instantiated |
| 70 /// interceptor class. This is a cached subset of [_interceptedElements]. | 70 /// interceptor class. This is a cached subset of [_interceptedElements]. |
| 71 /// | 71 /// |
| 72 /// Mixin methods are not specialized for the class they are mixed into. | 72 /// Mixin methods are not specialized for the class they are mixed into. |
| 73 /// Methods mixed into intercepted classes thus always make use of the | 73 /// Methods mixed into intercepted classes thus always make use of the |
| 74 /// explicit receiver argument, even when mixed into non-interceptor classes. | 74 /// explicit receiver argument, even when mixed into non-interceptor classes. |
| 75 /// | 75 /// |
| 76 /// These members must be invoked with a correct explicit receiver even when | 76 /// These members must be invoked with a correct explicit receiver even when |
| 77 /// the receiver is not an intercepted class. | 77 /// the receiver is not an intercepted class. |
| 78 final Map<String, Set<Element>> _interceptedMixinElements = | 78 final Map<String, Set<MemberEntity>> _interceptedMixinElements = |
| 79 new Map<String, Set<Element>>(); | 79 new Map<String, Set<MemberEntity>>(); |
| 80 | 80 |
| 81 final Map<String, Set<ClassElement>> _interceptedClassesCache = | 81 final Map<String, Set<ClassEntity>> _interceptedClassesCache = |
| 82 new Map<String, Set<ClassElement>>(); | 82 new Map<String, Set<ClassEntity>>(); |
| 83 | 83 |
| 84 final Set<ClassElement> _noClasses = new Set<ClassElement>(); | 84 final Set<ClassEntity> _noClasses = new Set<ClassEntity>(); |
| 85 | 85 |
| 86 InterceptorDataImpl( | 86 InterceptorDataImpl( |
| 87 this._nativeData, | 87 this._nativeData, |
| 88 this._helpers, | 88 this._helpers, |
| 89 this._closedWorld, | 89 this._closedWorld, |
| 90 this._interceptedElements, | 90 this._interceptedElements, |
| 91 this._interceptedClasses, | 91 this._interceptedClasses, |
| 92 this._classesMixedIntoInterceptedClasses); | 92 this._classesMixedIntoInterceptedClasses); |
| 93 | 93 |
| 94 bool isInterceptedMethod(MemberElement element) { | 94 bool isInterceptedMethod(MemberElement element) { |
| 95 if (!element.isInstanceMember) return false; | 95 if (!element.isInstanceMember) return false; |
| 96 if (element.isGenerativeConstructorBody) { | 96 if (element.isGenerativeConstructorBody) { |
| 97 return _nativeData.isNativeOrExtendsNative(element.enclosingClass); | 97 return _nativeData.isNativeOrExtendsNative(element.enclosingClass); |
| 98 } | 98 } |
| 99 return _interceptedElements[element.name] != null; | 99 return _interceptedElements[element.name] != null; |
| 100 } | 100 } |
| 101 | 101 |
| 102 bool fieldHasInterceptedGetter(FieldElement element) { | 102 bool fieldHasInterceptedGetter(FieldEntity element) { |
| 103 return _interceptedElements[element.name] != null; | 103 return _interceptedElements[element.name] != null; |
| 104 } | 104 } |
| 105 | 105 |
| 106 bool fieldHasInterceptedSetter(FieldElement element) { | 106 bool fieldHasInterceptedSetter(FieldEntity element) { |
| 107 return _interceptedElements[element.name] != null; | 107 return _interceptedElements[element.name] != null; |
| 108 } | 108 } |
| 109 | 109 |
| 110 bool isInterceptedName(String name) { | 110 bool isInterceptedName(String name) { |
| 111 return _interceptedElements[name] != null; | 111 return _interceptedElements[name] != null; |
| 112 } | 112 } |
| 113 | 113 |
| 114 bool isInterceptedSelector(Selector selector) { | 114 bool isInterceptedSelector(Selector selector) { |
| 115 return _interceptedElements[selector.name] != null; | 115 return _interceptedElements[selector.name] != null; |
| 116 } | 116 } |
| 117 | 117 |
| 118 /// Returns `true` iff [selector] matches an element defined in a class mixed | 118 /// Returns `true` iff [selector] matches an element defined in a class mixed |
| 119 /// into an intercepted class. These selectors are not eligible for the | 119 /// into an intercepted class. These selectors are not eligible for the |
| 120 /// 'dummy explicit receiver' optimization. | 120 /// 'dummy explicit receiver' optimization. |
| 121 bool isInterceptedMixinSelector(Selector selector, TypeMask mask) { | 121 bool isInterceptedMixinSelector(Selector selector, TypeMask mask) { |
| 122 Set<Element> elements = | 122 Set<MemberEntity> elements = |
| 123 _interceptedMixinElements.putIfAbsent(selector.name, () { | 123 _interceptedMixinElements.putIfAbsent(selector.name, () { |
| 124 Set<Element> elements = _interceptedElements[selector.name]; | 124 Set<MemberEntity> elements = _interceptedElements[selector.name]; |
| 125 if (elements == null) return null; | 125 if (elements == null) return null; |
| 126 return elements | 126 return elements |
| 127 .where((element) => _classesMixedIntoInterceptedClasses | 127 .where((element) => _classesMixedIntoInterceptedClasses |
| 128 .contains(element.enclosingClass)) | 128 .contains(element.enclosingClass)) |
| 129 .toSet(); | 129 .toSet(); |
| 130 }); | 130 }); |
| 131 | 131 |
| 132 if (elements == null) return false; | 132 if (elements == null) return false; |
| 133 if (elements.isEmpty) return false; | 133 if (elements.isEmpty) return false; |
| 134 return elements.any((element) { | 134 return elements.any((element) { |
| 135 return selector.applies(element) && | 135 return selector.applies(element) && |
| 136 (mask == null || | 136 (mask == null || mask.canHit(element, selector, _closedWorld)); |
| 137 mask.canHit(element as MemberElement, selector, _closedWorld)); | |
| 138 }); | 137 }); |
| 139 } | 138 } |
| 140 | 139 |
| 141 /// True if the given class is an internal class used for type inference | 140 /// True if the given class is an internal class used for type inference |
| 142 /// and never exists at runtime. | 141 /// and never exists at runtime. |
| 143 bool _isCompileTimeOnlyClass(ClassElement class_) { | 142 bool _isCompileTimeOnlyClass(ClassEntity class_) { |
| 144 return class_ == _helpers.jsPositiveIntClass || | 143 return class_ == _helpers.jsPositiveIntClass || |
| 145 class_ == _helpers.jsUInt32Class || | 144 class_ == _helpers.jsUInt32Class || |
| 146 class_ == _helpers.jsUInt31Class || | 145 class_ == _helpers.jsUInt31Class || |
| 147 class_ == _helpers.jsFixedArrayClass || | 146 class_ == _helpers.jsFixedArrayClass || |
| 148 class_ == _helpers.jsUnmodifiableArrayClass || | 147 class_ == _helpers.jsUnmodifiableArrayClass || |
| 149 class_ == _helpers.jsMutableArrayClass || | 148 class_ == _helpers.jsMutableArrayClass || |
| 150 class_ == _helpers.jsExtendableArrayClass; | 149 class_ == _helpers.jsExtendableArrayClass; |
| 151 } | 150 } |
| 152 | 151 |
| 153 /// Returns a set of interceptor classes that contain a member named [name] | 152 /// Returns a set of interceptor classes that contain a member named [name] |
| 154 /// | 153 /// |
| 155 /// Returns an empty set if there is no class. Do not modify the returned set. | 154 /// Returns an empty set if there is no class. Do not modify the returned set. |
| 156 Set<ClassElement> getInterceptedClassesOn(String name) { | 155 Set<ClassEntity> getInterceptedClassesOn(String name) { |
| 157 Set<Element> intercepted = _interceptedElements[name]; | 156 Set<MemberEntity> intercepted = _interceptedElements[name]; |
| 158 if (intercepted == null) return _noClasses; | 157 if (intercepted == null) return _noClasses; |
| 159 return _interceptedClassesCache.putIfAbsent(name, () { | 158 return _interceptedClassesCache.putIfAbsent(name, () { |
| 160 // Populate the cache by running through all the elements and | 159 // Populate the cache by running through all the elements and |
| 161 // determine if the given selector applies to them. | 160 // determine if the given selector applies to them. |
| 162 Set<ClassElement> result = new Set<ClassElement>(); | 161 Set<ClassEntity> result = new Set<ClassEntity>(); |
| 163 for (Element element in intercepted) { | 162 for (MemberEntity element in intercepted) { |
| 164 ClassElement classElement = element.enclosingClass; | 163 ClassEntity classElement = element.enclosingClass; |
| 165 if (_isCompileTimeOnlyClass(classElement)) continue; | 164 if (_isCompileTimeOnlyClass(classElement)) continue; |
| 166 if (_nativeData.isNativeOrExtendsNative(classElement) || | 165 if (_nativeData.isNativeOrExtendsNative(classElement) || |
| 167 interceptedClasses.contains(classElement)) { | 166 interceptedClasses.contains(classElement)) { |
| 168 result.add(classElement); | 167 result.add(classElement); |
| 169 } | 168 } |
| 170 if (_classesMixedIntoInterceptedClasses.contains(classElement)) { | 169 if (_classesMixedIntoInterceptedClasses.contains(classElement)) { |
| 171 Set<ClassElement> nativeSubclasses = | 170 Set<ClassEntity> nativeSubclasses = |
| 172 nativeSubclassesOfMixin(classElement); | 171 nativeSubclassesOfMixin(classElement); |
| 173 if (nativeSubclasses != null) result.addAll(nativeSubclasses); | 172 if (nativeSubclasses != null) result.addAll(nativeSubclasses); |
| 174 } | 173 } |
| 175 } | 174 } |
| 176 return result; | 175 return result; |
| 177 }); | 176 }); |
| 178 } | 177 } |
| 179 | 178 |
| 180 Set<ClassElement> nativeSubclassesOfMixin(ClassElement mixin) { | 179 Set<ClassEntity> nativeSubclassesOfMixin(ClassEntity mixin) { |
| 181 Iterable<MixinApplicationElement> uses = _closedWorld.mixinUsesOf(mixin); | 180 Iterable<ClassEntity> uses = _closedWorld.mixinUsesOf(mixin); |
| 182 Set<ClassElement> result = null; | 181 Set<ClassEntity> result = null; |
| 183 for (MixinApplicationElement use in uses) { | 182 for (ClassEntity use in uses) { |
| 184 _closedWorld.forEachStrictSubclassOf(use, (ClassElement subclass) { | 183 _closedWorld.forEachStrictSubclassOf(use, (ClassEntity subclass) { |
| 185 if (_nativeData.isNativeOrExtendsNative(subclass)) { | 184 if (_nativeData.isNativeOrExtendsNative(subclass)) { |
| 186 if (result == null) result = new Set<ClassElement>(); | 185 if (result == null) result = new Set<ClassEntity>(); |
| 187 result.add(subclass); | 186 result.add(subclass); |
| 188 } | 187 } |
| 189 }); | 188 }); |
| 190 } | 189 } |
| 191 return result; | 190 return result; |
| 192 } | 191 } |
| 193 | 192 |
| 194 bool isInterceptedClass(ClassElement element) { | 193 bool isInterceptedClass(ClassEntity element) { |
| 195 if (element == null) return false; | 194 if (element == null) return false; |
| 196 if (_nativeData.isNativeOrExtendsNative(element)) return true; | 195 if (_nativeData.isNativeOrExtendsNative(element)) return true; |
| 197 if (interceptedClasses.contains(element)) return true; | 196 if (interceptedClasses.contains(element)) return true; |
| 198 if (_classesMixedIntoInterceptedClasses.contains(element)) return true; | 197 if (_classesMixedIntoInterceptedClasses.contains(element)) return true; |
| 199 return false; | 198 return false; |
| 200 } | 199 } |
| 201 | 200 |
| 202 bool isMixedIntoInterceptedClass(ClassElement element) => | 201 bool isMixedIntoInterceptedClass(ClassEntity element) => |
| 203 _classesMixedIntoInterceptedClasses.contains(element); | 202 _classesMixedIntoInterceptedClasses.contains(element); |
| 204 | 203 |
| 205 Iterable<ClassElement> get interceptedClasses => _interceptedClasses; | 204 Iterable<ClassEntity> get interceptedClasses => _interceptedClasses; |
| 206 | 205 |
| 207 bool mayGenerateInstanceofCheck(DartType type) { | 206 bool mayGenerateInstanceofCheck(DartType type) { |
| 208 // We can use an instanceof check for raw types that have no subclass that | 207 // We can use an instanceof check for raw types that have no subclass that |
| 209 // is mixed-in or in an implements clause. | 208 // is mixed-in or in an implements clause. |
| 210 | 209 |
| 211 if (!type.treatAsRaw) return false; | 210 if (!type.treatAsRaw) return false; |
| 212 InterfaceType interfaceType = type; | 211 InterfaceType interfaceType = type; |
| 213 ClassEntity classElement = interfaceType.element; | 212 ClassEntity classElement = interfaceType.element; |
| 214 if (isInterceptedClass(classElement)) return false; | 213 if (isInterceptedClass(classElement)) return false; |
| 215 return _closedWorld.hasOnlySubclasses(classElement); | 214 return _closedWorld.hasOnlySubclasses(classElement); |
| 216 } | 215 } |
| 217 } | 216 } |
| 218 | 217 |
| 219 class InterceptorDataBuilderImpl implements InterceptorDataBuilder { | 218 class InterceptorDataBuilderImpl implements InterceptorDataBuilder { |
| 220 final NativeBasicData _nativeData; | 219 final NativeBasicData _nativeData; |
| 221 final BackendHelpers _helpers; | 220 final BackendHelpers _helpers; |
| 221 final ElementEnvironment _elementEnvironment; |
| 222 final CommonElements _commonElements; | 222 final CommonElements _commonElements; |
| 223 final Resolution _resolution; | 223 final Resolution _resolution; |
| 224 | 224 |
| 225 /// The members of instantiated interceptor classes: maps a member name to the | 225 /// The members of instantiated interceptor classes: maps a member name to the |
| 226 /// list of members that have that name. This map is used by the codegen to | 226 /// list of members that have that name. This map is used by the codegen to |
| 227 /// know whether a send must be intercepted or not. | 227 /// know whether a send must be intercepted or not. |
| 228 final Map<String, Set<Element>> _interceptedElements = | 228 final Map<String, Set<MemberEntity>> _interceptedElements = |
| 229 <String, Set<Element>>{}; | 229 <String, Set<MemberEntity>>{}; |
| 230 | 230 |
| 231 /// Set of classes whose methods are intercepted. | 231 /// Set of classes whose methods are intercepted. |
| 232 final Set<ClassElement> _interceptedClasses = new Set<ClassElement>(); | 232 final Set<ClassEntity> _interceptedClasses = new Set<ClassEntity>(); |
| 233 | 233 |
| 234 /// Set of classes used as mixins on intercepted (native and primitive) | 234 /// Set of classes used as mixins on intercepted (native and primitive) |
| 235 /// classes. Methods on these classes might also be mixed in to regular Dart | 235 /// classes. Methods on these classes might also be mixed in to regular Dart |
| 236 /// (unintercepted) classes. | 236 /// (unintercepted) classes. |
| 237 final Set<ClassElement> _classesMixedIntoInterceptedClasses = | 237 final Set<ClassEntity> _classesMixedIntoInterceptedClasses = |
| 238 new Set<ClassElement>(); | 238 new Set<ClassEntity>(); |
| 239 | 239 |
| 240 InterceptorDataBuilderImpl( | 240 InterceptorDataBuilderImpl(this._nativeData, this._helpers, |
| 241 this._nativeData, this._helpers, this._commonElements, this._resolution); | 241 this._elementEnvironment, this._commonElements, this._resolution); |
| 242 | 242 |
| 243 InterceptorData onResolutionComplete(ClosedWorld closedWorld) { | 243 InterceptorData onResolutionComplete(ClosedWorld closedWorld) { |
| 244 return new InterceptorDataImpl( | 244 return new InterceptorDataImpl( |
| 245 _nativeData, | 245 _nativeData, |
| 246 _helpers, | 246 _helpers, |
| 247 closedWorld, | 247 closedWorld, |
| 248 _interceptedElements, | 248 _interceptedElements, |
| 249 _interceptedClasses, | 249 _interceptedClasses, |
| 250 _classesMixedIntoInterceptedClasses); | 250 _classesMixedIntoInterceptedClasses); |
| 251 } | 251 } |
| 252 | 252 |
| 253 void addInterceptorsForNativeClassMembers(ClassElement cls) { | 253 void addInterceptorsForNativeClassMembers(ClassEntity cls) { |
| 254 cls.ensureResolved(_resolution); | 254 _elementEnvironment.forEachClassMember(cls, |
| 255 cls.forEachMember((ClassElement classElement, Element member) { | 255 (ClassEntity cls, MemberEntity member) { |
| 256 if (member.name == Identifiers.call) { | 256 if (member.name == Identifiers.call) return; |
| 257 return; | |
| 258 } | |
| 259 if (member.isSynthesized) return; | |
| 260 // All methods on [Object] are shadowed by [Interceptor]. | 257 // All methods on [Object] are shadowed by [Interceptor]. |
| 261 if (classElement == _commonElements.objectClass) return; | 258 if (cls == _commonElements.objectClass) return; |
| 262 Set<Element> set = _interceptedElements.putIfAbsent( | 259 Set<MemberEntity> set = _interceptedElements.putIfAbsent( |
| 263 member.name, () => new Set<Element>()); | 260 member.name, () => new Set<MemberEntity>()); |
| 264 set.add(member); | 261 set.add(member); |
| 265 }, includeSuperAndInjectedMembers: true); | 262 }); |
| 266 | 263 |
| 267 // Walk superclass chain to find mixins. | 264 // Walk superclass chain to find mixins. |
| 268 for (; cls != null; cls = cls.superclass) { | 265 _elementEnvironment.forEachMixin(cls, (ClassEntity mixin) { |
| 269 if (cls.isMixinApplication) { | 266 _classesMixedIntoInterceptedClasses.add(mixin); |
| 270 MixinApplicationElement mixinApplication = cls; | 267 }); |
| 271 _classesMixedIntoInterceptedClasses.add(mixinApplication.mixin); | |
| 272 } | |
| 273 } | |
| 274 } | 268 } |
| 275 | 269 |
| 276 void addInterceptors(ClassElement cls) { | 270 void addInterceptors(ClassEntity cls) { |
| 277 if (_interceptedClasses.add(cls)) { | 271 if (_interceptedClasses.add(cls)) { |
| 278 cls.ensureResolved(_resolution); | 272 _elementEnvironment.forEachClassMember(cls, |
| 279 cls.forEachMember((ClassElement classElement, Element member) { | 273 (ClassEntity cls, MemberEntity member) { |
| 280 // All methods on [Object] are shadowed by [Interceptor]. | 274 // All methods on [Object] are shadowed by [Interceptor]. |
| 281 if (classElement == _commonElements.objectClass) return; | 275 if (cls == _commonElements.objectClass) return; |
| 282 Set<Element> set = _interceptedElements.putIfAbsent( | 276 Set<MemberEntity> set = _interceptedElements.putIfAbsent( |
| 283 member.name, () => new Set<Element>()); | 277 member.name, () => new Set<MemberEntity>()); |
| 284 set.add(member); | 278 set.add(member); |
| 285 }, includeSuperAndInjectedMembers: true); | 279 }); |
| 286 } | 280 } |
| 287 _interceptedClasses.add(_helpers.jsInterceptorClass); | 281 _interceptedClasses.add(_helpers.jsInterceptorClass); |
| 288 } | 282 } |
| 289 } | 283 } |
| 290 | 284 |
| 291 class OneShotInterceptorData { | 285 class OneShotInterceptorData { |
| 292 final InterceptorData _interceptorData; | 286 final InterceptorData _interceptorData; |
| 293 final BackendHelpers _helpers; | 287 final BackendHelpers _helpers; |
| 294 | 288 |
| 295 OneShotInterceptorData(this._interceptorData, this._helpers); | 289 OneShotInterceptorData(this._interceptorData, this._helpers); |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 335 jsAst.Name name = namer.nameForGetInterceptor(classes); | 329 jsAst.Name name = namer.nameForGetInterceptor(classes); |
| 336 if (classes.contains(_helpers.jsInterceptorClass)) { | 330 if (classes.contains(_helpers.jsInterceptorClass)) { |
| 337 // We can't use a specialized [getInterceptorMethod], so we make | 331 // We can't use a specialized [getInterceptorMethod], so we make |
| 338 // sure we emit the one with all checks. | 332 // sure we emit the one with all checks. |
| 339 _specializedGetInterceptors[name] = _interceptorData.interceptedClasses; | 333 _specializedGetInterceptors[name] = _interceptorData.interceptedClasses; |
| 340 } else { | 334 } else { |
| 341 _specializedGetInterceptors[name] = classes; | 335 _specializedGetInterceptors[name] = classes; |
| 342 } | 336 } |
| 343 } | 337 } |
| 344 } | 338 } |
| OLD | NEW |