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

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

Powered by Google App Engine
This is Rietveld 408576698