Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(292)

Side by Side Diff: pkg/dev_compiler/lib/src/compiler/property_model.dart

Issue 2781443003: Fix #28120, strong mode allows field overrides (Closed)
Patch Set: refactor Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/dev_compiler/lib/src/compiler/nullable_type_inference.dart ('k') | pkg/dev_compiler/test/browser/language_tests.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698