| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, 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 part of type_graph_inferrer; | |
| 6 | |
| 7 // A set of selectors we know do not escape the elements inside the | |
| 8 // list. | |
| 9 Set<String> doesNotEscapeListSet = new Set<String>.from( | |
| 10 const <String>[ | |
| 11 // From Object. | |
| 12 '==', | |
| 13 'hashCode', | |
| 14 'toString', | |
| 15 'noSuchMethod', | |
| 16 'runtimeType', | |
| 17 | |
| 18 // From Iterable. | |
| 19 'isEmpty', | |
| 20 'isNotEmpty', | |
| 21 'length', | |
| 22 'any', | |
| 23 'contains', | |
| 24 'every', | |
| 25 'join', | |
| 26 | |
| 27 // From List. | |
| 28 'add', | |
| 29 'addAll', | |
| 30 'clear', | |
| 31 'fillRange', | |
| 32 'indexOf', | |
| 33 'insert', | |
| 34 'insertAll', | |
| 35 'lastIndexOf', | |
| 36 'remove', | |
| 37 'removeRange', | |
| 38 'replaceRange', | |
| 39 'setAll', | |
| 40 'setRange', | |
| 41 'shuffle', | |
| 42 '[]=', | |
| 43 | |
| 44 // From JSArray. | |
| 45 'checkMutable', | |
| 46 'checkGrowable', | |
| 47 ]); | |
| 48 | |
| 49 Set<String> doesNotEscapeMapSet = new Set<String>.from( | |
| 50 const <String>[ | |
| 51 // From Object. | |
| 52 '==', | |
| 53 'hashCode', | |
| 54 'toString', | |
| 55 'noSuchMethod', | |
| 56 'runtimeType', | |
| 57 // from Map. | |
| 58 'isEmpty', | |
| 59 'isNotEmpty', | |
| 60 'length', | |
| 61 'clear', | |
| 62 'containsKey', | |
| 63 'containsValue', | |
| 64 '[]=', | |
| 65 // [keys] only allows key values to escape, which we do not track. | |
| 66 'keys' | |
| 67 ]); | |
| 68 | |
| 69 abstract class TracerVisitor<T extends TypeInformation> | |
| 70 implements TypeInformationVisitor { | |
| 71 final T tracedType; | |
| 72 final TypeGraphInferrerEngine inferrer; | |
| 73 final Compiler compiler; | |
| 74 | |
| 75 static const int MAX_ANALYSIS_COUNT = 16; | |
| 76 final Setlet<Element> analyzedElements = new Setlet<Element>(); | |
| 77 | |
| 78 TracerVisitor(this.tracedType, inferrer) | |
| 79 : this.inferrer = inferrer, this.compiler = inferrer.compiler; | |
| 80 | |
| 81 // Work list that gets populated with [TypeInformation] that could | |
| 82 // contain the container. | |
| 83 final List<TypeInformation> workList = <TypeInformation>[]; | |
| 84 | |
| 85 // Work list of lists to analyze after analyzing the users of a | |
| 86 // [TypeInformation]. We know the [tracedType] has been stored in these | |
| 87 // lists and we must check how it escapes from these lists. | |
| 88 final List<ListTypeInformation> listsToAnalyze = | |
| 89 <ListTypeInformation>[]; | |
| 90 // Work list of maps to analyze after analyzing the users of a | |
| 91 // [TypeInformation]. We know the [tracedType] has been stored in these | |
| 92 // maps and we must check how it escapes from these maps. | |
| 93 final List<MapTypeInformation> mapsToAnalyze = <MapTypeInformation>[]; | |
| 94 | |
| 95 final Setlet<TypeInformation> flowsInto = new Setlet<TypeInformation>(); | |
| 96 | |
| 97 // The current [TypeInformation] in the analysis. | |
| 98 TypeInformation currentUser; | |
| 99 bool continueAnalyzing = true; | |
| 100 | |
| 101 void addNewEscapeInformation(TypeInformation info) { | |
| 102 if (flowsInto.contains(info)) return; | |
| 103 flowsInto.add(info); | |
| 104 workList.add(info); | |
| 105 } | |
| 106 | |
| 107 void analyze() { | |
| 108 // Collect the [TypeInformation] where the list can flow in, | |
| 109 // as well as the operations done on all these [TypeInformation]s. | |
| 110 addNewEscapeInformation(tracedType); | |
| 111 while (!workList.isEmpty) { | |
| 112 currentUser = workList.removeLast(); | |
| 113 int expectedWork = analyzedElements.length + currentUser.users.length; | |
| 114 if (expectedWork > MAX_ANALYSIS_COUNT) { | |
| 115 bailout('Too many users'); | |
| 116 break; | |
| 117 } | |
| 118 for (TypeInformation info in currentUser.users) { | |
| 119 analyzedElements.add(info.owner); | |
| 120 info.accept(this); | |
| 121 } | |
| 122 while (!listsToAnalyze.isEmpty) { | |
| 123 analyzeStoredIntoList(listsToAnalyze.removeLast()); | |
| 124 } | |
| 125 while (!mapsToAnalyze.isEmpty) { | |
| 126 analyzeStoredIntoMap(mapsToAnalyze.removeLast()); | |
| 127 } | |
| 128 if (!continueAnalyzing) break; | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 void bailout(String reason) { | |
| 133 if (_VERBOSE) { | |
| 134 print('Bailing out on $tracedType because: $reason'); | |
| 135 } | |
| 136 continueAnalyzing = false; | |
| 137 } | |
| 138 | |
| 139 void visitNarrowTypeInformation(NarrowTypeInformation info) { | |
| 140 addNewEscapeInformation(info); | |
| 141 } | |
| 142 | |
| 143 void visitPhiElementTypeInformation(PhiElementTypeInformation info) { | |
| 144 addNewEscapeInformation(info); | |
| 145 } | |
| 146 | |
| 147 void visitElementInContainerTypeInformation( | |
| 148 ElementInContainerTypeInformation info) { | |
| 149 addNewEscapeInformation(info); | |
| 150 } | |
| 151 | |
| 152 void visitKeyInMapTypeInformation(KeyInMapTypeInformation info) { | |
| 153 // We do not track the use of keys from a map, so we have to bail. | |
| 154 bailout('Used as key in Map'); | |
| 155 } | |
| 156 | |
| 157 void visitValueInMapTypeInformation(ValueInMapTypeInformation info) { | |
| 158 addNewEscapeInformation(info); | |
| 159 } | |
| 160 | |
| 161 void visitListTypeInformation(ListTypeInformation info) { | |
| 162 listsToAnalyze.add(info); | |
| 163 } | |
| 164 | |
| 165 void visitMapTypeInformation(MapTypeInformation info) { | |
| 166 mapsToAnalyze.add(info); | |
| 167 } | |
| 168 void visitConcreteTypeInformation(ConcreteTypeInformation info) {} | |
| 169 | |
| 170 void visitStringLiteralTypeInformation(StringLiteralTypeInformation info) {} | |
| 171 | |
| 172 void visitClosureTypeInformation(ClosureTypeInformation info) {} | |
| 173 | |
| 174 void visitClosureCallSiteTypeInformation( | |
| 175 ClosureCallSiteTypeInformation info) {} | |
| 176 | |
| 177 visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { | |
| 178 Element called = info.calledElement; | |
| 179 if (inferrer.types.getInferredTypeOf(called) == currentUser) { | |
| 180 addNewEscapeInformation(info); | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 void analyzeStoredIntoList(ListTypeInformation list) { | |
| 185 inferrer.analyzeListAndEnqueue(list); | |
| 186 if (list.bailedOut) { | |
| 187 bailout('Stored in a list that bailed out'); | |
| 188 } else { | |
| 189 list.flowsInto.forEach((flow) { | |
| 190 flow.users.forEach((user) { | |
| 191 if (user is !DynamicCallSiteTypeInformation) return; | |
| 192 if (user.receiver != flow) return; | |
| 193 if (inferrer._returnsListElementTypeSet.contains(user.selector)) { | |
| 194 addNewEscapeInformation(user); | |
| 195 } else if (!doesNotEscapeListSet.contains(user.selector.name)) { | |
| 196 bailout('Escape from a list via [${user.selector.name}]'); | |
| 197 } | |
| 198 }); | |
| 199 }); | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 void analyzeStoredIntoMap(MapTypeInformation map) { | |
| 204 inferrer.analyzeMapAndEnqueue(map); | |
| 205 if (map.bailedOut) { | |
| 206 bailout('Stored in a map that bailed out'); | |
| 207 } else { | |
| 208 map.flowsInto.forEach((flow) { | |
| 209 flow.users.forEach((user) { | |
| 210 if (user is !DynamicCallSiteTypeInformation) return; | |
| 211 if (user.receiver != flow) return; | |
| 212 if (user.selector.isIndex) { | |
| 213 addNewEscapeInformation(user); | |
| 214 } else if (!doesNotEscapeMapSet.contains(user.selector.name)) { | |
| 215 bailout('Escape from a map via [${user.selector.name}]'); | |
| 216 } | |
| 217 }); | |
| 218 }); | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 /** | |
| 223 * Checks whether this is a call to a list adding method. The definition | |
| 224 * of what list adding means has to stay in sync with | |
| 225 * [isParameterOfListAddingMethod]. | |
| 226 */ | |
| 227 bool isAddedToContainer(DynamicCallSiteTypeInformation info) { | |
| 228 if (info.arguments == null) return false; | |
| 229 var receiverType = info.receiver.type; | |
| 230 if (!receiverType.isContainer) return false; | |
| 231 String selectorName = info.selector.name; | |
| 232 List<TypeInformation> arguments = info.arguments.positional; | |
| 233 return (selectorName == '[]=' && currentUser == arguments[1]) | |
| 234 || (selectorName == 'insert' && currentUser == arguments[1]) | |
| 235 || (selectorName == 'add' && currentUser == arguments[0]); | |
| 236 } | |
| 237 | |
| 238 bool isIndexSetOnMap(DynamicCallSiteTypeInformation info) { | |
| 239 if (info.arguments == null) return false; | |
| 240 var receiverType = info.receiver.type; | |
| 241 if (!receiverType.isMap) return false; | |
| 242 return info.selector.name == '[]='; | |
| 243 } | |
| 244 | |
| 245 /** | |
| 246 * Checks whether this is a call to a map adding method for values. The | |
| 247 * definition of map adding method has to stay in sync with | |
| 248 * [isParameterOfMapAddingMethod]. | |
| 249 */ | |
| 250 bool isValueAddedToMap(DynamicCallSiteTypeInformation info) { | |
| 251 return isIndexSetOnMap(info) && | |
| 252 currentUser == info.arguments.positional[1]; | |
| 253 } | |
| 254 | |
| 255 /** | |
| 256 * Checks whether this is a call to a map adding method for keys. The | |
| 257 * definition of map adding method has to stay in sync with | |
| 258 * [isParameterOfMapAddingMethod]. | |
| 259 */ | |
| 260 bool isKeyAddedToMap(DynamicCallSiteTypeInformation info) { | |
| 261 return isIndexSetOnMap(info) && | |
| 262 currentUser == info.arguments.positional[0]; | |
| 263 } | |
| 264 | |
| 265 void visitDynamicCallSiteTypeInformation( | |
| 266 DynamicCallSiteTypeInformation info) { | |
| 267 if (isAddedToContainer(info)) { | |
| 268 ContainerTypeMask mask = info.receiver.type; | |
| 269 | |
| 270 if (mask.allocationNode != null) { | |
| 271 ListTypeInformation list = | |
| 272 inferrer.types.allocatedLists[mask.allocationNode]; | |
| 273 listsToAnalyze.add(list); | |
| 274 } else { | |
| 275 // The [ContainerTypeMask] is a union of two containers, and | |
| 276 // we lose track of where these containers have been allocated | |
| 277 // at this point. | |
| 278 bailout('Stored in too many containers'); | |
| 279 } | |
| 280 } else if (isValueAddedToMap(info)) { | |
| 281 MapTypeMask mask = info.receiver.type; | |
| 282 if (mask.allocationNode != null) { | |
| 283 MapTypeInformation map = | |
| 284 inferrer.types.allocatedMaps[mask.allocationNode]; | |
| 285 mapsToAnalyze.add(map); | |
| 286 } else { | |
| 287 // The [MapTypeMask] is a union. See comment for | |
| 288 // [ContainerTypeMask] above. | |
| 289 bailout('Stored in too many maps'); | |
| 290 } | |
| 291 } else if (isKeyAddedToMap(info)) { | |
| 292 // We do not track the use of keys from a map, so we have to bail. | |
| 293 bailout('Used as key in Map'); | |
| 294 } | |
| 295 | |
| 296 if (info.targetsIncludeNoSuchMethod && | |
| 297 info.arguments != null && | |
| 298 info.arguments.contains(currentUser)) { | |
| 299 bailout('Passed to noSuchMethod'); | |
| 300 } | |
| 301 | |
| 302 Iterable<Element> inferredTargetTypes = info.targets.map((element) { | |
| 303 return inferrer.types.getInferredTypeOf(element); | |
| 304 }); | |
| 305 if (inferredTargetTypes.any((user) => user == currentUser)) { | |
| 306 addNewEscapeInformation(info); | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 /** | |
| 311 * Check whether element is the parameter of a list adding method. | |
| 312 * The definition of what a list adding method is has to stay in sync with | |
| 313 * [isAddedToContainer]. | |
| 314 */ | |
| 315 bool isParameterOfListAddingMethod(Element element) { | |
| 316 if (!element.isParameter) return false; | |
| 317 if (element.enclosingClass != compiler.backend.listImplementation) { | |
| 318 return false; | |
| 319 } | |
| 320 Element method = element.enclosingElement; | |
| 321 return (method.name == '[]=') | |
| 322 || (method.name == 'add') | |
| 323 || (method.name == 'insert'); | |
| 324 } | |
| 325 | |
| 326 /** | |
| 327 * Check whether element is the parameter of a list adding method. | |
| 328 * The definition of what a list adding method is has to stay in sync with | |
| 329 * [isValueAddedToMap] and [isKeyAddedToMap]. | |
| 330 */ | |
| 331 bool isParameterOfMapAddingMethod(Element element) { | |
| 332 if (!element.isParameter) return false; | |
| 333 if (element.enclosingClass != compiler.backend.mapImplementation) { | |
| 334 return false; | |
| 335 } | |
| 336 Element method = element.enclosingElement; | |
| 337 return (method.name == '[]='); | |
| 338 } | |
| 339 | |
| 340 bool isClosure(Element element) { | |
| 341 if (!element.isFunction) return false; | |
| 342 /// Creating an instance of a class that implements [Function] also | |
| 343 /// closurizes the corresponding [call] member. We do not currently | |
| 344 /// track these, thus the check for [isClosurized] on such a method will | |
| 345 /// return false. Instead we catch that case here for now. | |
| 346 // TODO(herhut): Handle creation of closures from instances of Function. | |
| 347 if (element.isInstanceMember && | |
| 348 element.name == Compiler.CALL_OPERATOR_NAME) { | |
| 349 return true; | |
| 350 } | |
| 351 Element outermost = element.outermostEnclosingMemberOrTopLevel; | |
| 352 return outermost.declaration != element.declaration; | |
| 353 } | |
| 354 | |
| 355 void visitMemberTypeInformation(MemberTypeInformation info) { | |
| 356 Element element = info.element; | |
| 357 if (info.isClosurized) { | |
| 358 bailout('Returned from a closurized method'); | |
| 359 } | |
| 360 if (isClosure(info.element)) { | |
| 361 bailout('Returned from a closure'); | |
| 362 } | |
| 363 if (!inferrer.compiler.backend | |
| 364 .canBeUsedForGlobalOptimizations(info.element)) { | |
| 365 bailout('Escape to code that has special backend treatment'); | |
| 366 } | |
| 367 addNewEscapeInformation(info); | |
| 368 } | |
| 369 | |
| 370 void visitParameterTypeInformation(ParameterTypeInformation info) { | |
| 371 ParameterElement element = info.element; | |
| 372 if (inferrer.isNativeElement(element.functionDeclaration)) { | |
| 373 bailout('Passed to a native method'); | |
| 374 } | |
| 375 if (!inferrer.compiler.backend | |
| 376 .canBeUsedForGlobalOptimizations(info.element)) { | |
| 377 bailout('Escape to code that has special backend treatment'); | |
| 378 } | |
| 379 if (isParameterOfListAddingMethod(info.element) || | |
| 380 isParameterOfMapAddingMethod(info.element)) { | |
| 381 // These elements are being handled in | |
| 382 // [visitDynamicCallSiteTypeInformation]. | |
| 383 return; | |
| 384 } | |
| 385 addNewEscapeInformation(info); | |
| 386 } | |
| 387 } | |
| OLD | NEW |