Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2016, 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 import 'dart:collection' show HashMap, HashSet, Queue; | |
| 6 | |
| 7 import 'package:analyzer/dart/ast/ast.dart' show Identifier; | |
| 8 import 'package:analyzer/dart/element/element.dart'; | |
| 9 import 'package:analyzer/dart/element/type.dart'; | |
| 10 import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl; | |
| 11 | |
| 12 import '../js_ast/js_ast.dart' as JS; | |
| 13 import 'element_helpers.dart'; | |
| 14 import 'js_names.dart' as JS; | |
| 15 | |
| 16 /// Dart allows all fields to be overridden. | |
| 17 /// | |
| 18 /// To prevent a performance/code size penalty for allowing this, we analyze | |
| 19 /// private classes within each library that is being compiled to determine | |
| 20 /// if those fields should be virtual or not. In effect, we devirtualize fields | |
| 21 /// when possible by analyzing the class hierarchy and using knowledge of | |
| 22 /// which members are private and thus, could not be overridden outside of the | |
| 23 /// current library. | |
| 24 class VirtualFieldModel { | |
| 25 final _modelForLibrary = | |
| 26 new HashMap<LibraryElement, _LibraryVirtualFieldModel>(); | |
| 27 | |
| 28 _LibraryVirtualFieldModel _getModel(LibraryElement library) => | |
| 29 _modelForLibrary.putIfAbsent( | |
| 30 library, () => new _LibraryVirtualFieldModel.build(library)); | |
| 31 | |
| 32 /// Returns true if a field is virtual. | |
| 33 bool isVirtual(FieldElement field) => | |
| 34 _getModel(field.library).isVirtual(field); | |
| 35 } | |
| 36 | |
| 37 /// This is a building block of [VirtualFieldModel], used to track information | |
| 38 /// about a single library that has been analyzed. | |
| 39 class _LibraryVirtualFieldModel { | |
| 40 /// Fields that are private (or public fields of a private class) and | |
| 41 /// overridden in this library. | |
| 42 /// | |
| 43 /// This means we must generate them as virtual fields using a property pair | |
| 44 /// in JavaScript. | |
| 45 final _overriddenPrivateFields = new HashSet<FieldElement>(); | |
| 46 | |
| 47 /// Private classes that can be extended outside of this library. | |
| 48 /// | |
| 49 /// Normally private classes cannot be accessed outside this library, however, | |
| 50 /// this can happen if they are extended by a public class, for example: | |
| 51 /// | |
| 52 /// class _A { int x = 42; } | |
| 53 /// class _B { int x = 42; } | |
| 54 /// | |
| 55 /// // _A is now effectively public for the purpose of overrides. | |
| 56 /// class C extends _A {} | |
| 57 /// | |
| 58 /// The class _A must treat is "x" as virtual, however _B does not. | |
| 59 final _extensiblePrivateClasses = new HashSet<ClassElement>(); | |
| 60 | |
| 61 _LibraryVirtualFieldModel.build(LibraryElement library) { | |
| 62 var allTypes = library.units.expand((u) => u.types).toList(); | |
| 63 | |
| 64 // The set of public types is our initial extensible type set. | |
| 65 // From there, visit all immediate private types in this library, and so on | |
| 66 // from those private types, marking them as extensible. | |
| 67 var typesToVisit = | |
| 68 new Queue<ClassElement>.from(allTypes.where((t) => t.isPublic)); | |
| 69 while (typesToVisit.isNotEmpty) { | |
| 70 var extensibleType = typesToVisit.removeFirst(); | |
| 71 | |
| 72 // For each supertype of a public type in this library, | |
| 73 // if we encounter a private class, we mark it as being extended, and | |
| 74 // add it to our work set if this is the first time we've visited it. | |
| 75 for (var type in getImmediateSuperclasses(extensibleType)) { | |
| 76 if (!type.isPublic && type.library == library) { | |
| 77 if (_extensiblePrivateClasses.add(type)) typesToVisit.add(type); | |
| 78 } | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 // ClassElement can only look up inherited members with an O(N) scan through | |
| 83 // the class, so we build up a mapping of all fields in the library ahead of | |
| 84 // time. | |
| 85 var allFields = | |
| 86 new HashMap<ClassElement, HashMap<String, FieldElement>>.fromIterable( | |
| 87 allTypes, | |
| 88 value: (t) => new HashMap.fromIterable( | |
| 89 t.fields.where((f) => !f.isStatic), | |
| 90 key: (f) => f.name)); | |
| 91 | |
| 92 for (var type in allTypes) { | |
| 93 Set<ClassElement> supertypes = null; | |
| 94 | |
| 95 // Visit accessors in the current class, and see if they override an | |
| 96 // otherwise private field. | |
| 97 for (var accessor in type.accessors) { | |
| 98 // For getter/setter pairs only process them once. | |
| 99 if (accessor.correspondingGetter != null) continue; | |
| 100 // Ignore abstract or static accessors. | |
| 101 if (accessor.isAbstract || accessor.isStatic) continue; | |
| 102 // Ignore public accessors in extensible classes. | |
| 103 if (accessor.isPublic && | |
| 104 (type.isPublic || _extensiblePrivateClasses.contains(type))) { | |
| 105 continue; | |
| 106 } | |
| 107 | |
| 108 if (supertypes == null) { | |
| 109 supertypes = new Set(); | |
| 110 var library = type.library; | |
| 111 void collectSupertypes(ClassElement cls) { | |
| 112 // Invariant: we never need to leave the current library. | |
| 113 // This works because we are only checking for field overrides for | |
| 114 // those we know cannot be overridden outside the current library, | |
| 115 // such as private fields and public fields of non-extensible | |
| 116 // private classes. | |
| 117 if (cls.library != library) return; | |
|
vsm
2017/03/28 13:28:32
I don't think this is quite right. If I patch you
Jennifer Messerly
2017/03/28 16:48:22
I was trying really hard to keep the search cheap
vsm
2017/03/28 17:12:33
You might get most of the benefit from checking if
| |
| 118 if (!identical(type, cls) && !supertypes.add(cls)) return; | |
| 119 | |
| 120 var s = cls.supertype?.element; | |
| 121 if (s != null) collectSupertypes(s); | |
| 122 cls.mixins.forEach((m) => collectSupertypes(m.element)); | |
| 123 } | |
| 124 | |
| 125 collectSupertypes(type); | |
| 126 } | |
| 127 | |
| 128 // Look in all super classes to see if we're overriding a field in our | |
| 129 // library, if so mark that field as overridden. | |
| 130 var name = accessor.variable.name; | |
| 131 _overriddenPrivateFields.addAll( | |
| 132 supertypes.map((c) => allFields[c][name]).where((f) => f != null)); | |
| 133 } | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 /// Returns true if a field inside this library is virtual. | |
| 138 bool isVirtual(FieldElement field) { | |
| 139 // If the field was marked non-virtual, we know for sure. | |
| 140 if (!field.isVirtual) return false; | |
| 141 if (field.isStatic) return false; | |
| 142 | |
| 143 var type = field.enclosingElement; | |
| 144 var library = type.library; | |
| 145 if (library.isInSdk && library.source.uri.toString().startsWith('dart:_')) { | |
| 146 // There should be no extensible fields in private SDK libraries. | |
| 147 return false; | |
| 148 } | |
| 149 | |
| 150 if (field.isPublic) { | |
| 151 // Public fields in public classes (or extensible private classes) | |
| 152 // are always virtual. | |
| 153 // They could be overridden by someone using our library. | |
| 154 if (type.isPublic) return true; | |
| 155 if (_extensiblePrivateClasses.contains(type)) return true; | |
| 156 } | |
| 157 | |
| 158 // Otherwise, the field is effectively private and we only need to make it | |
| 159 // virtual if it's overridden. | |
| 160 return _overriddenPrivateFields.contains(field); | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 /// Tracks how fields, getters and setters are represented when emitting JS. | |
| 165 /// | |
| 166 /// Dart classes have implicit features that must be made explicit: | |
| 167 /// | |
| 168 /// - virtual fields induce a getter and setter pair. | |
| 169 /// - getters and setters are independent. | |
| 170 /// - getters and setters can be overridden. | |
| 171 /// | |
| 172 class ClassPropertyModel { | |
| 173 /// Fields that are virtual, that is, they must be generated as a property | |
| 174 /// pair in JavaScript. | |
| 175 /// | |
| 176 /// The value property stores the symbol used for the field's storage slot. | |
| 177 final virtualFields = <FieldElement, JS.TemporaryId>{}; | |
| 178 | |
| 179 /// Static fields that are overridden, this does not matter for Dart but in | |
| 180 /// JS we need to take care initializing these because JS classes inherit | |
| 181 /// statics. | |
| 182 final staticFieldOverrides = new HashSet<FieldElement>(); | |
| 183 | |
| 184 /// The set of inherited getters, used because JS getters/setters are paired, | |
| 185 /// so if we're generating a setter we may need to emit a getter that calls | |
| 186 /// super. | |
| 187 final inheritedGetters = new HashSet<String>(); | |
| 188 | |
| 189 /// The set of inherited setters, used because JS getters/setters are paired, | |
| 190 /// so if we're generating a getter we may need to emit a setter that calls | |
| 191 /// super. | |
| 192 final inheritedSetters = new HashSet<String>(); | |
| 193 | |
| 194 ClassPropertyModel.build(VirtualFieldModel fieldModel, ClassElement classElem, | |
| 195 Iterable<ExecutableElement> extensionMembers) { | |
| 196 // Visit superclasses to collect information about their fields/accessors. | |
| 197 // This is expensive so we try to collect everything in one pass. | |
| 198 for (var base in getSuperclasses(classElem)) { | |
| 199 for (var accessor in base.accessors) { | |
| 200 // For getter/setter pairs only process them once. | |
| 201 if (accessor.correspondingGetter != null) continue; | |
| 202 | |
| 203 var field = accessor.variable; | |
| 204 var name = field.name; | |
| 205 // Ignore private names from other libraries. | |
| 206 if (Identifier.isPrivateName(name) && | |
| 207 accessor.library != classElem.library) { | |
| 208 continue; | |
| 209 } | |
| 210 | |
| 211 if (field.getter?.isAbstract == false) inheritedGetters.add(name); | |
| 212 if (field.setter?.isAbstract == false) inheritedSetters.add(name); | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 var extensionNames = | |
| 217 new HashSet<String>.from(extensionMembers.map((e) => e.name)); | |
| 218 | |
| 219 // Visit accessors in the current class, and see if they need to be | |
| 220 // generated differently based on the inherited fields/accessors. | |
| 221 for (var accessor in classElem.accessors) { | |
| 222 // For getter/setter pairs only process them once. | |
| 223 if (accessor.correspondingGetter != null) continue; | |
| 224 // Also ignore abstract fields. | |
| 225 if (accessor.isAbstract) continue; | |
| 226 | |
| 227 var field = accessor.variable; | |
| 228 var name = field.name; | |
| 229 // Is it a field? | |
| 230 if (!field.isSynthetic && field is FieldElementImpl) { | |
| 231 if (inheritedGetters.contains(name) || | |
| 232 inheritedSetters.contains(name) || | |
| 233 extensionNames.contains(name) || | |
| 234 fieldModel.isVirtual(field)) { | |
| 235 if (field.isStatic) { | |
| 236 staticFieldOverrides.add(field); | |
| 237 } else { | |
| 238 virtualFields[field] = new JS.TemporaryId(name); | |
| 239 } | |
| 240 } | |
| 241 } | |
| 242 } | |
| 243 } | |
| 244 } | |
| OLD | NEW |