| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, 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 analyzer2dart.treeShaker; | |
| 6 | |
| 7 import 'dart:collection'; | |
| 8 | |
| 9 import 'package:analyzer/analyzer.dart'; | |
| 10 import 'package:analyzer/src/generated/element.dart'; | |
| 11 import 'package:analyzer/src/generated/source.dart'; | |
| 12 import 'package:analyzer/src/generated/resolver.dart'; | |
| 13 import 'package:compiler/src/universe/universe.dart'; | |
| 14 | |
| 15 import 'closed_world.dart'; | |
| 16 import 'util.dart'; | |
| 17 import 'semantic_visitor.dart'; | |
| 18 import 'identifier_semantics.dart'; | |
| 19 | |
| 20 /** | |
| 21 * The result of performing local reachability analysis on a method. | |
| 22 */ | |
| 23 class MethodAnalysis { | |
| 24 /** | |
| 25 * The AST for the method. | |
| 26 */ | |
| 27 final Declaration declaration; | |
| 28 | |
| 29 /** | |
| 30 * The functions statically called by the method. | |
| 31 */ | |
| 32 final List<ExecutableElement> calls = <ExecutableElement>[]; | |
| 33 | |
| 34 /** | |
| 35 * The fields and top-level variables statically accessed by the method. | |
| 36 */ | |
| 37 // TODO(johnniwinther): Should we split this into reads and writes? | |
| 38 final List<PropertyInducingElement> accesses = <PropertyInducingElement>[]; | |
| 39 | |
| 40 /** | |
| 41 * The selectors used by the method to perform dynamic invocation. | |
| 42 */ | |
| 43 final List<Selector> invokes = <Selector>[]; | |
| 44 | |
| 45 /** | |
| 46 * The classes that are instantiated by the method. | |
| 47 */ | |
| 48 // TODO(johnniwinther,paulberry): Register instantiated types. | |
| 49 // TODO(johnniwinther,paulberry): Register checked types from is/as checks, | |
| 50 // catch clauses and (checked) type annotations. | |
| 51 final List<ClassElement> instantiates = <ClassElement>[]; | |
| 52 | |
| 53 MethodAnalysis(this.declaration); | |
| 54 } | |
| 55 | |
| 56 /** | |
| 57 * The result of performing local reachability analysis on a class. | |
| 58 * | |
| 59 * TODO(paulberry): Do we need to do any other analysis of classes? (For | |
| 60 * example, detect annotations that are relevant to mirrors, detect that a | |
| 61 * class might be used for custom HTML elements, or collect inherited and | |
| 62 * mixed-in classes). | |
| 63 */ | |
| 64 class ClassAnalysis { | |
| 65 /** | |
| 66 * The AST for the class. | |
| 67 */ | |
| 68 final ClassDeclaration declaration; | |
| 69 | |
| 70 ClassAnalysis(this.declaration); | |
| 71 } | |
| 72 | |
| 73 /** | |
| 74 * This class is responsible for performing local analysis of the source code | |
| 75 * to provide the information needed to do tree shaking. | |
| 76 */ | |
| 77 class LocalReachabilityComputer { | |
| 78 /** | |
| 79 * Perform local reachability analysis of [method]. | |
| 80 */ | |
| 81 MethodAnalysis analyzeMethod(ExecutableElement method) { | |
| 82 Declaration declaration = method.node; | |
| 83 MethodAnalysis analysis = new MethodAnalysis(declaration); | |
| 84 if (declaration != null) { | |
| 85 declaration.accept(new TreeShakingVisitor(analysis)); | |
| 86 } else if (method is ConstructorElement) { | |
| 87 // This constructor has no associated declaration in the AST. Either it | |
| 88 // is a default constructor for an ordinary class, or it's a synthetic | |
| 89 // constructor associated with a mixin. For now we assume it's a default | |
| 90 // constructor, in which case all we need to do is record the class as | |
| 91 // being instantiated by this method. TODO(paulberry): handle the | |
| 92 // mixin case. | |
| 93 ClassElement instantiatedClass = method.enclosingElement; | |
| 94 analysis.instantiates.add(instantiatedClass); | |
| 95 if (instantiatedClass.supertype != null) { | |
| 96 ClassElement superClass = instantiatedClass.supertype.element; | |
| 97 ConstructorElement superConstructor = superClass.unnamedConstructor; | |
| 98 if (superConstructor != null) { | |
| 99 // TODO(johnniwinther): Register instantiated type and selector. | |
| 100 analysis.calls.add(superConstructor); | |
| 101 } | |
| 102 } | |
| 103 } else { | |
| 104 // This is an executable element with no associated declaration in the | |
| 105 // AST, and it's not a constructor. TODO(paulberry): can this ever | |
| 106 // happen? | |
| 107 throw new UnimplementedError(); | |
| 108 } | |
| 109 return analysis; | |
| 110 } | |
| 111 | |
| 112 /** | |
| 113 * Perform local reachability analysis of [classElement]. | |
| 114 */ | |
| 115 ClassAnalysis analyzeClass(ClassElement classElement) { | |
| 116 return new ClassAnalysis(classElement.node); | |
| 117 } | |
| 118 | |
| 119 /** | |
| 120 * Determine which members of [classElement] are matched by the given | |
| 121 * [selector]. | |
| 122 * | |
| 123 * [methods] is populated with all the class methods which are matched by the | |
| 124 * selector, [accessors] with all the getters and setters which are matched | |
| 125 * by the selector, and [fields] with all the fields which are matched by the | |
| 126 * selector. | |
| 127 */ | |
| 128 void getMatchingClassMembers(ClassElement classElement, Selector selector, | |
| 129 List<MethodElement> methods, List<PropertyAccessorElement> accessors, | |
| 130 List<PropertyInducingElement> fields) { | |
| 131 // TODO(paulberry): should we walk through superclasses and mixins as well | |
| 132 // here? Or would it be better to make [TreeShaker] responsible for those | |
| 133 // relationships (since they are non-local)? Consider making use of | |
| 134 // InheritanceManager to do this. | |
| 135 for (MethodElement method in classElement.methods) { | |
| 136 // TODO(paulberry): account for arity and named arguments when matching | |
| 137 // the selector against the method. | |
| 138 if (selector.name == method.name) { | |
| 139 methods.add(method); | |
| 140 } | |
| 141 } | |
| 142 if (selector.kind == SelectorKind.GETTER) { | |
| 143 for (PropertyAccessorElement accessor in classElement.accessors) { | |
| 144 if (accessor.isGetter && selector.name == accessor.name) { | |
| 145 if (accessor.isSynthetic) { | |
| 146 // This accessor is implied by the corresponding field declaration. | |
| 147 fields.add(accessor.variable); | |
| 148 } else { | |
| 149 accessors.add(accessor); | |
| 150 } | |
| 151 } | |
| 152 } | |
| 153 } else if (selector.kind == SelectorKind.SETTER) { | |
| 154 // accessor.name uses the convention that setter names end in '='. | |
| 155 String selectorNameWithEquals = '${selector.name}='; | |
| 156 for (PropertyAccessorElement accessor in classElement.accessors) { | |
| 157 if (accessor.isSetter && selectorNameWithEquals == accessor.name) { | |
| 158 if (accessor.isSynthetic) { | |
| 159 // This accessor is implied by the corresponding field declaration. | |
| 160 // TODO(paulberry): should we distinguish reads and writes? | |
| 161 fields.add(accessor.variable); | |
| 162 } else { | |
| 163 accessors.add(accessor); | |
| 164 } | |
| 165 } | |
| 166 } | |
| 167 } | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 /** | |
| 172 * This class is responsible for driving the tree shaking process, and | |
| 173 * and performing the global inferences necessary to determine which methods | |
| 174 * in the source program are reachable. It makes use of | |
| 175 * [LocalReachabilityComputer] to do local analysis of individual classes and | |
| 176 * methods. | |
| 177 */ | |
| 178 class TreeShaker { | |
| 179 List<Element> _queue = <Element>[]; | |
| 180 Set<Element> _alreadyEnqueued = new HashSet<Element>(); | |
| 181 ClosedWorld _world; | |
| 182 Set<Selector> _selectors = new HashSet<Selector>(); | |
| 183 final LocalReachabilityComputer _localComputer = | |
| 184 new LocalReachabilityComputer(); | |
| 185 | |
| 186 TreeShaker(TypeProvider typeProvider, FunctionElement mainFunction) | |
| 187 : _world = new ClosedWorld(typeProvider, mainFunction); | |
| 188 | |
| 189 void _addElement(Element element) { | |
| 190 if (_alreadyEnqueued.add(element)) { | |
| 191 _queue.add(element); | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 void _addSelector(Selector selector) { | |
| 196 if (_selectors.add(selector)) { | |
| 197 // New selector, so match it against all class methods. | |
| 198 _world.instantiatedClasses.forEach((ClassElement element, AstNode node) { | |
| 199 _matchClassToSelector(element, selector); | |
| 200 }); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 void _matchClassToSelector(ClassElement classElement, Selector selector) { | |
| 205 List<MethodElement> methods = <MethodElement>[]; | |
| 206 List<PropertyAccessorElement> accessors = <PropertyAccessorElement>[]; | |
| 207 List<PropertyInducingElement> fields = <PropertyInducingElement>[]; | |
| 208 _localComputer.getMatchingClassMembers( | |
| 209 classElement, | |
| 210 selector, | |
| 211 methods, | |
| 212 accessors, | |
| 213 fields); | |
| 214 methods.forEach(_addElement); | |
| 215 accessors.forEach(_addElement); | |
| 216 fields.forEach(_addElement); | |
| 217 } | |
| 218 | |
| 219 ClosedWorld shake() { | |
| 220 _addElement(_world.mainFunction); | |
| 221 while (_queue.isNotEmpty) { | |
| 222 Element element = _queue.removeLast(); | |
| 223 if (element is ExecutableElement) { | |
| 224 MethodAnalysis analysis = _localComputer.analyzeMethod(element); | |
| 225 _world.executableElements[element] = analysis.declaration; | |
| 226 analysis.calls.forEach(_addElement); | |
| 227 analysis.invokes.forEach(_addSelector); | |
| 228 analysis.instantiates.forEach(_addElement); | |
| 229 analysis.accesses.forEach(_addElement); | |
| 230 } else if (element is ClassElement) { | |
| 231 ClassAnalysis analysis = _localComputer.analyzeClass(element); | |
| 232 _world.instantiatedClasses[element] = analysis.declaration; | |
| 233 for (Selector selector in _selectors) { | |
| 234 _matchClassToSelector(element, selector); | |
| 235 } | |
| 236 } else if (element is FieldElement) { | |
| 237 VariableDeclaration declaration = element.node; | |
| 238 _world.fields[element] = declaration; | |
| 239 } else if (element is TopLevelVariableElement) { | |
| 240 VariableDeclaration declaration = element.node; | |
| 241 _world.variables[element] = declaration; | |
| 242 } else { | |
| 243 throw new Exception( | |
| 244 'Unexpected element type while tree shaking: ' | |
| 245 '$element (${element.runtimeType})'); | |
| 246 } | |
| 247 } | |
| 248 return _world; | |
| 249 } | |
| 250 } | |
| 251 | |
| 252 class TreeShakingVisitor extends SemanticVisitor { | |
| 253 final MethodAnalysis analysis; | |
| 254 | |
| 255 TreeShakingVisitor(this.analysis); | |
| 256 | |
| 257 Source get currentSource => analysis.declaration.element.source; | |
| 258 | |
| 259 @override | |
| 260 void visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| 261 ConstructorElement staticElement = node.staticElement; | |
| 262 if (staticElement != null) { | |
| 263 analysis.calls.add(staticElement); | |
| 264 } else { | |
| 265 // TODO(paulberry): deal with this situation. This can happen, for | |
| 266 // example, in the case "main() => new Unresolved();" (which is a | |
| 267 // warning, not an error). | |
| 268 } | |
| 269 super.visitInstanceCreationExpression(node); | |
| 270 } | |
| 271 | |
| 272 @override | |
| 273 void visitDynamicInvocation(MethodInvocation node, | |
| 274 AccessSemantics semantics) { | |
| 275 analysis.invokes.add(createSelectorFromMethodInvocation( | |
| 276 node.argumentList, node.methodName.name)); | |
| 277 } | |
| 278 | |
| 279 @override | |
| 280 void visitLocalFunctionInvocation(MethodInvocation node, | |
| 281 AccessSemantics semantics) { | |
| 282 // Locals don't need to be tree shaken. | |
| 283 } | |
| 284 | |
| 285 @override | |
| 286 void visitLocalVariableInvocation(MethodInvocation node, | |
| 287 AccessSemantics semantics) { | |
| 288 // Locals don't need to be tree shaken. | |
| 289 } | |
| 290 | |
| 291 @override | |
| 292 void visitParameterInvocation(MethodInvocation node, | |
| 293 AccessSemantics semantics) { | |
| 294 // Locals don't need to be tree shaken. | |
| 295 } | |
| 296 | |
| 297 @override | |
| 298 void visitStaticFieldInvocation(MethodInvocation node, | |
| 299 AccessSemantics semantics) { | |
| 300 // Invocation of a static field. | |
| 301 analysis.accesses.add(semantics.element); | |
| 302 analysis.invokes.add(createSelectorFromMethodInvocation( | |
| 303 node.argumentList, 'call')); | |
| 304 } | |
| 305 | |
| 306 @override | |
| 307 void visitStaticMethodInvocation(MethodInvocation node, | |
| 308 AccessSemantics semantics) { | |
| 309 analysis.calls.add(semantics.element); | |
| 310 } | |
| 311 | |
| 312 @override | |
| 313 void visitStaticPropertyInvocation(MethodInvocation node, | |
| 314 AccessSemantics semantics) { | |
| 315 // Invocation of a property. TODO(paulberry): handle this. | |
| 316 super.visitStaticPropertyInvocation(node, semantics); | |
| 317 } | |
| 318 | |
| 319 void handleDynamicAccess(AccessSemantics semantics) { | |
| 320 if (semantics.isRead) { | |
| 321 analysis.invokes.add( | |
| 322 new Selector.getter(semantics.identifier.name, null)); | |
| 323 } | |
| 324 if (semantics.isWrite) { | |
| 325 // Selector.setter constructor uses the convention that setter names | |
| 326 // don't end in '='. | |
| 327 analysis.invokes.add( | |
| 328 new Selector.setter(semantics.identifier.name, null)); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 @override | |
| 333 void visitDynamicAccess(AstNode node, AccessSemantics semantics) { | |
| 334 handleDynamicAccess(semantics); | |
| 335 } | |
| 336 | |
| 337 @override | |
| 338 void visitLocalFunctionAccess(AstNode node, AccessSemantics semantics) { | |
| 339 // Locals don't need to be tree shaken. | |
| 340 } | |
| 341 | |
| 342 @override | |
| 343 void visitLocalVariableAccess(AstNode node, AccessSemantics semantics) { | |
| 344 // Locals don't need to be tree shaken. | |
| 345 } | |
| 346 | |
| 347 @override | |
| 348 void visitParameterAccess(AstNode node, AccessSemantics semantics) { | |
| 349 // Locals don't need to be tree shaken. | |
| 350 } | |
| 351 | |
| 352 @override | |
| 353 void visitStaticFieldAccess(AstNode node, AccessSemantics semantics) { | |
| 354 analysis.accesses.add(semantics.element); | |
| 355 } | |
| 356 | |
| 357 @override | |
| 358 void visitStaticMethodAccess(AstNode node, AccessSemantics semantics) { | |
| 359 // Method tear-off. TODO(paulberry): implement. | |
| 360 super.visitStaticMethodAccess(node, semantics); | |
| 361 } | |
| 362 | |
| 363 @override | |
| 364 void visitStaticPropertyAccess(AstNode node, AccessSemantics semantics) { | |
| 365 // TODO(paulberry): implement. | |
| 366 super.visitStaticPropertyAccess(node, semantics); | |
| 367 } | |
| 368 | |
| 369 @override | |
| 370 void visitConstructorDeclaration(ConstructorDeclaration node) { | |
| 371 // TODO(paulberry): handle parameter list. | |
| 372 node.initializers.accept(this); | |
| 373 node.body.accept(this); | |
| 374 if (node.factoryKeyword == null) { | |
| 375 // This is a generative constructor. Figure out if it is redirecting. | |
| 376 // If it isn't, then the constructor instantiates the class so we need to | |
| 377 // add the class to analysis.instantiates. (If it is redirecting, then | |
| 378 // we don't need to, because the redirected-to constructor will take care | |
| 379 // of that). | |
| 380 if (node.initializers.length != 1 || node.initializers[0] is! RedirectingC
onstructorInvocation) { | |
| 381 ClassElement classElement = node.element.enclosingElement; | |
| 382 analysis.instantiates.add(node.element.enclosingElement); | |
| 383 if (!node.initializers.any((i) => i is SuperConstructorInvocation)) { | |
| 384 if (classElement.supertype != null) { | |
| 385 ClassElement superClass = classElement.supertype.element; | |
| 386 ConstructorElement superConstructor = superClass.unnamedConstructor; | |
| 387 if (superConstructor != null) { | |
| 388 // TODO(johnniwinther): Register instantiated type and selector. | |
| 389 analysis.calls.add(superConstructor); | |
| 390 } | |
| 391 } | |
| 392 } | |
| 393 } | |
| 394 } else if (node.redirectedConstructor != null) { | |
| 395 if (node.redirectedConstructor.staticElement == null) { | |
| 396 // Factory constructor redirects to a non-existent constructor. | |
| 397 // TODO(paulberry): handle this. | |
| 398 throw new UnimplementedError(); | |
| 399 } else { | |
| 400 analysis.calls.add(node.redirectedConstructor.staticElement); | |
| 401 } | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 @override | |
| 406 void | |
| 407 visitRedirectingConstructorInvocation(RedirectingConstructorInvocation nod
e) { | |
| 408 // Note: we don't have to worry about node.staticElement being | |
| 409 // null, because that would have been detected by the analyzer and | |
| 410 // reported as a compile time error. | |
| 411 analysis.calls.add(node.staticElement); | |
| 412 } | |
| 413 | |
| 414 @override | |
| 415 void handleAssignmentExpression(AssignmentExpression node) { | |
| 416 // Don't special-case assignment expressions. | |
| 417 } | |
| 418 } | |
| OLD | NEW |