[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;
+}