[vm/aot/tfa] Correctly tree shake initializers of write-only fields
Fixes https://github.com/dart-lang/sdk/issues/35632
Change-Id: I9736268087a90fd409509f35bf2fc0637443a9fb
Reviewed-on: https://dart-review.googlesource.com/c/89161
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Auto-Submit: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart
index 9f12b93..f403b43 100644
--- a/pkg/vm/lib/transformations/type_flow/analysis.dart
+++ b/pkg/vm/lib/transformations/type_flow/analysis.dart
@@ -220,6 +220,7 @@
}
fieldValue.setValue(initializerResult, typeFlowAnalysis,
field.isStatic ? null : args.receiver);
+ fieldValue.isInitialized = true;
return const EmptyType();
}
@@ -732,6 +733,9 @@
final Summary typeGuardSummary;
Type value;
+ /// Flag indicating if field initializer was executed.
+ bool isInitialized = false;
+
_FieldValue(this.field, this.typeGuardSummary)
: staticType = new Type.fromStatic(field.type) {
if (field.initializer == null && _isDefaultValueOfFieldObservable()) {
@@ -1385,6 +1389,8 @@
hierarchyCache.seal();
}
+ /// Returns true if analysis found that given member
+ /// could be executed / field could be accessed.
bool isMemberUsed(Member member) {
if (member is Field) {
return _fieldValues.containsKey(member);
@@ -1393,6 +1399,16 @@
}
}
+ /// Returns true if analysis found that initializer of the given [field]
+ /// could be executed.
+ bool isFieldInitializerUsed(Field field) {
+ final fieldValue = _fieldValues[field];
+ if (fieldValue != null) {
+ return fieldValue.isInitialized;
+ }
+ return false;
+ }
+
bool isClassAllocated(Class c) => hierarchyCache.allocatedClasses.contains(c);
Call callSite(TreeNode node) => summaryCollector.callSites[node];
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index 9cb6d8d..4e3103d 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -367,6 +367,8 @@
bool isClassAllocated(Class c) => typeFlowAnalysis.isClassAllocated(c);
bool isMemberUsed(Member m) => _usedMembers.contains(m);
bool isMemberBodyReachable(Member m) => typeFlowAnalysis.isMemberUsed(m);
+ bool isFieldInitializerReachable(Field f) =>
+ typeFlowAnalysis.isFieldInitializerUsed(f);
bool isMemberReferencedFromNativeCode(Member m) =>
typeFlowAnalysis.nativeCodeOracle.isMemberReferencedFromNativeCode(m);
bool isTypedefUsed(Typedef t) => _usedTypedefs.contains(t);
@@ -603,6 +605,30 @@
}
@override
+ TreeNode visitField(Field node) {
+ if (shaker.isMemberBodyReachable(node)) {
+ if (kPrintTrace) {
+ tracePrint("Visiting $node");
+ }
+ shaker.addUsedMember(node);
+ if (node.initializer != null) {
+ if (shaker.isFieldInitializerReachable(node)) {
+ node.transformChildren(this);
+ } else {
+ node.initializer = _makeUnreachableCall([]);
+ }
+ }
+ } else if (shaker.isMemberReferencedFromNativeCode(node)) {
+ // Preserve members referenced from native code to satisfy lookups, even
+ // if they are not reachable. An instance member could be added via
+ // native code entry point but still unreachable if no instances of
+ // its enclosing class are allocated.
+ shaker.addUsedMember(node);
+ }
+ return node;
+ }
+
+ @override
TreeNode visitMethodInvocation(MethodInvocation node) {
node.transformChildren(this);
if (_isUnreachable(node)) {
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart
new file mode 100644
index 0000000..6abe2d5
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests tree shaking of field initializer for a write-only field.
+// Regression test for https://github.com/dart-lang/sdk/issues/35632.
+
+class A {
+ A() {
+ print('A');
+ }
+}
+
+var field = A();
+
+class B {
+ B() {
+ print('B');
+ }
+}
+
+class C {
+ var instanceField = new B();
+}
+
+void main() {
+ field = null;
+ new C().instanceField = null;
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect
new file mode 100644
index 0000000..d8d8757
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect
@@ -0,0 +1,23 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+
+abstract class A extends core::Object {
+}
+class B extends core::Object {
+ constructor •() → self::B
+ : super core::Object::•() {
+ core::print("B");
+ }
+}
+class C extends core::Object {
+[@vm.inferred-type.metadata=#lib::B?] [@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] field self::B instanceField = new self::B::•();
+ synthetic constructor •() → self::C
+ : super core::Object::•()
+ ;
+}
+[@vm.inferred-type.metadata=dart.core::Null?]static field self::A field = throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
+static method main() → void {
+ self::field = null;
+ [@vm.direct-call.metadata=#lib::C::instanceField] new self::C::•().{self::C::instanceField} = null;
+}