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

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: fix 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/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 devirtualize fields
20 /// when possible by analyzing the class hierarchy and using knowledge of
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) {
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 Set<ClassElement> supertypes = null;
93
94 // Visit accessors in the current class, and see if they override an
95 // otherwise private field.
96 for (var accessor in type.accessors) {
97 // For getter/setter pairs only process them once.
98 if (accessor.correspondingGetter != null) continue;
99 // Ignore abstract or static accessors.
100 if (accessor.isAbstract || accessor.isStatic) continue;
101 // Ignore public accessors in extensible classes.
102 if (accessor.isPublic &&
103 (type.isPublic || _extensiblePrivateClasses.contains(type))) {
104 continue;
105 }
106
107 if (supertypes == null) {
108 supertypes = new Set();
109 var library = type.library;
110 void collectSupertypes(ClassElement cls) {
111 if (!supertypes.add(cls)) return;
112 var s = cls.supertype?.element;
113 if (s != null) collectSupertypes(s);
114 cls.mixins.forEach((m) => collectSupertypes(m.element));
115 }
116
117 collectSupertypes(type);
118 supertypes.remove(type);
119 supertypes.removeWhere((c) => c.library != type.library);
120 }
121
122 // Look in all super classes to see if we're overriding a field in our
123 // library, if so mark that field as overridden.
124 var name = accessor.variable.name;
125 _overriddenPrivateFields.addAll(
126 supertypes.map((c) => allFields[c][name]).where((f) => f != null));
127 }
128 }
129 }
130
131 /// Returns true if a field inside this library is virtual.
132 bool isVirtual(FieldElement field) {
133 // If the field was marked non-virtual, we know for sure.
134 if (!field.isVirtual) return false;
135 if (field.isStatic) return false;
136
137 var type = field.enclosingElement;
138 var library = type.library;
139 if (library.isInSdk && library.source.uri.toString().startsWith('dart:_')) {
140 // There should be no extensible fields in private SDK libraries.
141 return false;
142 }
143
144 if (field.isPublic) {
145 // Public fields in public classes (or extensible private classes)
146 // are always virtual.
147 // They could be overridden by someone using our library.
148 if (type.isPublic) return true;
149 if (_extensiblePrivateClasses.contains(type)) return true;
150 }
151
152 // Otherwise, the field is effectively private and we only need to make it
153 // virtual if it's overridden.
154 return _overriddenPrivateFields.contains(field);
155 }
156 }
157
158 /// Tracks how fields, getters and setters are represented when emitting JS.
159 ///
160 /// Dart classes have implicit features that must be made explicit:
161 ///
162 /// - virtual fields induce a getter and setter pair.
163 /// - getters and setters are independent.
164 /// - getters and setters can be overridden.
165 ///
166 class ClassPropertyModel {
167 /// Fields that are virtual, that is, they must be generated as a property
168 /// pair in JavaScript.
169 ///
170 /// The value property stores the symbol used for the field's storage slot.
171 final virtualFields = <FieldElement, JS.TemporaryId>{};
172
173 /// Static fields that are overridden, this does not matter for Dart but in
174 /// JS we need to take care initializing these because JS classes inherit
175 /// statics.
176 final staticFieldOverrides = new HashSet<FieldElement>();
177
178 /// The set of inherited getters, used because JS getters/setters are paired,
179 /// so if we're generating a setter we may need to emit a getter that calls
180 /// super.
181 final inheritedGetters = new HashSet<String>();
182
183 /// The set of inherited setters, used because JS getters/setters are paired,
184 /// so if we're generating a getter we may need to emit a setter that calls
185 /// super.
186 final inheritedSetters = new HashSet<String>();
187
188 ClassPropertyModel.build(VirtualFieldModel fieldModel, ClassElement classElem,
189 Iterable<ExecutableElement> extensionMembers) {
190 // Visit superclasses to collect information about their fields/accessors.
191 // This is expensive so we try to collect everything in one pass.
192 for (var base in getSuperclasses(classElem)) {
193 for (var accessor in base.accessors) {
194 // For getter/setter pairs only process them once.
195 if (accessor.correspondingGetter != null) continue;
196
197 var field = accessor.variable;
198 var name = field.name;
199 // Ignore private names from other libraries.
200 if (Identifier.isPrivateName(name) &&
201 accessor.library != classElem.library) {
202 continue;
203 }
204
205 if (field.getter?.isAbstract == false) inheritedGetters.add(name);
206 if (field.setter?.isAbstract == false) inheritedSetters.add(name);
207 }
208 }
209
210 var extensionNames =
211 new HashSet<String>.from(extensionMembers.map((e) => e.name));
212
213 // Visit accessors in the current class, and see if they need to be
214 // generated differently based on the inherited fields/accessors.
215 for (var accessor in classElem.accessors) {
216 // For getter/setter pairs only process them once.
217 if (accessor.correspondingGetter != null) continue;
218 // Also ignore abstract fields.
219 if (accessor.isAbstract) continue;
220
221 var field = accessor.variable;
222 var name = field.name;
223 // Is it a field?
224 if (!field.isSynthetic && field is FieldElementImpl) {
225 if (inheritedGetters.contains(name) ||
226 inheritedSetters.contains(name) ||
227 extensionNames.contains(name) ||
228 fieldModel.isVirtual(field)) {
229 if (field.isStatic) {
230 staticFieldOverrides.add(field);
231 } else {
232 virtualFields[field] = new JS.TemporaryId(name);
233 }
234 }
235 }
236 }
237 }
238 }
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