[vm/kernel/aot] Fix handling of calls via fields/getters in TFA

Bug: https://github.com/flutter/flutter/issues/16182
Change-Id: Ib2021212fd2dde8b549ca5db9b23287276dc33c9
Reviewed-on: https://dart-review.googlesource.com/50840
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart
index 227ba12..99a885c 100644
--- a/pkg/vm/lib/transformations/type_flow/analysis.dart
+++ b/pkg/vm/lib/transformations/type_flow/analysis.dart
@@ -161,6 +161,12 @@
         // Call via field.
         // TODO(alexmarkov): support function types and use inferred type
         // to get more precise return type.
+        final receiver = fieldValue.getValue(typeFlowAnalysis);
+        if (receiver != const EmptyType()) {
+          typeFlowAnalysis.applyCall(/* callSite = */ null,
+              DynamicSelector.kCall, new Args.withReceiver(args, receiver),
+              isResultUsed: false, processImmediately: false);
+        }
         return new Type.nullableAny();
 
       case CallKind.FieldInitializer:
@@ -212,6 +218,9 @@
             member.isGetter);
         typeFlowAnalysis.addRawCall(
             new DirectSelector(member, callKind: CallKind.PropertyGet));
+        typeFlowAnalysis.applyCall(/* callSite = */ null, DynamicSelector.kCall,
+            new Args.withReceiver(args, new Type.nullableAny()),
+            isResultUsed: false, processImmediately: false);
         return new Type.nullableAny();
       }
     }
@@ -1098,9 +1107,8 @@
   /// ---- Implementation of [CallHandler] interface. ----
 
   @override
-  Type applyCall(
-      Call callSite, Selector selector, Args<Type> args, bool isResultUsed,
-      {bool processImmediately: true}) {
+  Type applyCall(Call callSite, Selector selector, Args<Type> args,
+      {bool isResultUsed: true, bool processImmediately: true}) {
     _Invocation invocation = _invocationsCache.getInvocation(selector, args);
 
     // Test if tracing is enabled to avoid expensive message formatting.
@@ -1140,8 +1148,8 @@
     debugPrint("ADD RAW CALL: $selector");
     assertx(selector is! DynamicSelector); // TODO(alexmarkov)
 
-    applyCall(null, selector, summaryCollector.rawArguments(selector), false,
-        processImmediately: false);
+    applyCall(null, selector, summaryCollector.rawArguments(selector),
+        isResultUsed: false, processImmediately: false);
   }
 
   @override
diff --git a/pkg/vm/lib/transformations/type_flow/calls.dart b/pkg/vm/lib/transformations/type_flow/calls.dart
index 02166f9..ac69128 100644
--- a/pkg/vm/lib/transformations/type_flow/calls.dart
+++ b/pkg/vm/lib/transformations/type_flow/calls.dart
@@ -138,6 +138,8 @@
   @override
   final Name name;
 
+  static final kCall = new DynamicSelector(CallKind.Method, new Name('call'));
+
   DynamicSelector(CallKind callKind, this.name) : super(callKind);
 
   @override
diff --git a/pkg/vm/lib/transformations/type_flow/summary.dart b/pkg/vm/lib/transformations/type_flow/summary.dart
index 7c23deb..e918a6d 100644
--- a/pkg/vm/lib/transformations/type_flow/summary.dart
+++ b/pkg/vm/lib/transformations/type_flow/summary.dart
@@ -14,8 +14,8 @@
 import 'utils.dart';
 
 abstract class CallHandler {
-  Type applyCall(
-      Call callSite, Selector selector, Args<Type> args, bool isResultUsed);
+  Type applyCall(Call callSite, Selector selector, Args<Type> args,
+      {bool isResultUsed});
 }
 
 /// Base class for all statements in a summary.
@@ -183,8 +183,9 @@
     if (selector is! DirectSelector) {
       _observeReceiverType(argTypes[0]);
     }
-    final Type result = callHandler.applyCall(this, selector,
-        new Args<Type>(argTypes, names: args.names), isResultUsed);
+    final Type result = callHandler.applyCall(
+        this, selector, new Args<Type>(argTypes, names: args.names),
+        isResultUsed: isResultUsed);
     if (isResultUsed) {
       _observeResultType(result, typeHierarchy);
     }
diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
index 8d48f99..01e6738 100644
--- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart
+++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
@@ -476,7 +476,9 @@
   Call _makeCall(TreeNode node, Selector selector, Args<TypeExpr> args) {
     Call call = new Call(selector, args);
     _summary.add(call);
-    callSites[node] = call;
+    if (node != null) {
+      callSites[node] = call;
+    }
     return call;
   }
 
@@ -714,10 +716,12 @@
     }
     if ((target is Field) || ((target is Procedure) && target.isGetter)) {
       // Call via field.
-      _makeCall(
+      final fieldValue = _makeCall(
           node,
           new InterfaceSelector(target, callKind: CallKind.PropertyGet),
           new Args<TypeExpr>([receiver]));
+      _makeCall(
+          null, DynamicSelector.kCall, new Args.withReceiver(args, fieldValue));
       return _staticType(node);
     } else {
       // TODO(alexmarkov): overloaded arithmetic operators
@@ -777,10 +781,12 @@
         // Call via field.
         // TODO(alexmarkov): Consider cleaning up this code as it duplicates
         // processing in DirectInvocation.
-        _makeCall(
+        final fieldValue = _makeCall(
             node,
             new DirectSelector(target, callKind: CallKind.PropertyGet),
             new Args<TypeExpr>([_receiver]));
+        _makeCall(null, DynamicSelector.kCall,
+            new Args.withReceiver(args, fieldValue));
         return _staticType(node);
       } else {
         return _makeCall(node, new DirectSelector(target), args);
diff --git a/pkg/vm/testcases/transformations/type_flow/summary_collector/calls.dart.expect b/pkg/vm/testcases/transformations/type_flow/summary_collector/calls.dart.expect
index 3b59fa8..4deba6d 100644
--- a/pkg/vm/testcases/transformations/type_flow/summary_collector/calls.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/summary_collector/calls.dart.expect
@@ -39,7 +39,8 @@
 t7* = _Call get [#lib::A::foo2] (%aa)
 t8 = _Narrow (t7 to _T (dart.core::int)+?)
 t9 = _Call set [#lib::A::foo3] (%aa, t8)
-t10 = _Call get [#lib::A::foo2] (%aa)
+t10* = _Call get [#lib::A::foo2] (%aa)
+t11 = _Call dynamic [call] (t10, %a2, %a3, _T ANY?)
 a4 = _Join [dart.core::Object] (%a4, _T ANY?)
 RESULT: a4
 ------------ #lib::C::dynamicCalls ------------
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart
new file mode 100644
index 0000000..81c386e
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2018, 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.
+
+// Regression test for https://github.com/flutter/flutter/issues/16182
+// Verifies that TFA correctly handles calls via fields/getters.
+
+import "package:expect/expect.dart";
+
+bool ok;
+
+class T1 {
+  // Should be reachable.
+  void doTest1() {
+    ok = true;
+  }
+}
+
+class A1 {
+  T1 foo;
+
+  void call([a1, a2, a3, a4, a5]) {
+    foo = a5;
+  }
+}
+
+class B1 {
+  A1 aa1 = new A1();
+}
+
+void test1() {
+  B1 bb = new B1();
+  bb.aa1(1, 2, 3, 4, new T1());
+
+  ok = false;
+  bb.aa1.foo.doTest1();
+  Expect.isTrue(ok);
+}
+
+class T2 {
+  // Should be reachable.
+  void doTest2() {
+    ok = true;
+  }
+}
+
+class A2 {
+  dynamic foo;
+
+  void call([a1, a2, a3, a4, a5, a6]) {
+    foo = a6;
+  }
+}
+
+class B2Base {
+  dynamic _aa = new A2();
+  dynamic get aa2 => _aa;
+}
+
+class B2 extends B2Base {
+  void doSuperCall() {
+    super.aa2(1, 2, 3, 4, 5, new T2());
+  }
+}
+
+void test2() {
+  var bb = new B2();
+  bb.doSuperCall();
+
+  ok = false;
+  bb.aa2.foo.doTest2();
+  Expect.isTrue(ok);
+}
+
+class T3 {
+  // Should be reachable.
+  void doTest3() {
+    ok = true;
+  }
+}
+
+class A3 {
+  dynamic foo;
+
+  void call([a1, a2, a3, a4, a5, a6, a7]) {
+    foo = a7;
+  }
+}
+
+class B3 {
+  A3 aa3 = new A3();
+}
+
+dynamic bb3 = new B3();
+Function unknown3 = () => bb3;
+getDynamic3() => unknown3.call();
+
+void test3() {
+  getDynamic3().aa3(1, 2, 3, 4, 5, 6, new T3());
+
+  ok = false;
+  bb3.aa3.foo.doTest3();
+  Expect.isTrue(ok);
+}
+
+class T4 {
+  // Should be reachable.
+  void doTest4() {
+    ok = true;
+  }
+}
+
+class A4 {
+  dynamic foo;
+
+  void call([a1, a2, a3, a4, a5, a6, a7, a8]) {
+    foo = a8;
+  }
+}
+
+class B4 {
+  dynamic _aa = new A4();
+  dynamic get aa4 => _aa;
+}
+
+dynamic bb4 = new B4();
+Function unknown4 = () => bb4;
+getDynamic4() => unknown4.call();
+
+void test4() {
+  getDynamic4().aa4(1, 2, 3, 4, 5, 6, 7, new T4());
+
+  ok = false;
+  getDynamic4().aa4.foo.doTest4();
+  Expect.isTrue(ok);
+}
+
+void main() {
+  test1();
+  test2();
+  test3();
+  test4();
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart.expect
new file mode 100644
index 0000000..18cbd9d4
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter16182.dart.expect
@@ -0,0 +1,150 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+class T1 extends core::Object {
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method doTest1() → void {
+    self::ok = true;
+  }
+}
+class A1 extends core::Object {
+[@vm.inferred-type.metadata=#lib::T1?]  field self::T1 foo = null;
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method call([dynamic a1 = null, dynamic a2 = null, dynamic a3 = null, dynamic a4 = null, [@vm.inferred-type.metadata=#lib::T1?] dynamic a5 = null]) → void {
+    [@vm.direct-call.metadata=#lib::A1::foo] this.{self::A1::foo} = a5 as{TypeError} self::T1;
+  }
+}
+class B1 extends core::Object {
+[@vm.inferred-type.metadata=#lib::A1]  field self::A1 aa1 = new self::A1::•();
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+}
+class T2 extends core::Object {
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method doTest2() → void {
+    self::ok = true;
+  }
+}
+class A2 extends core::Object {
+[@vm.inferred-type.metadata=#lib::T2?]  field dynamic foo = null;
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method call([dynamic a1 = null, dynamic a2 = null, dynamic a3 = null, dynamic a4 = null, dynamic a5 = null, [@vm.inferred-type.metadata=#lib::T2?] dynamic a6 = null]) → void {
+    [@vm.direct-call.metadata=#lib::A2::foo] this.{self::A2::foo} = a6;
+  }
+}
+abstract class B2Base extends core::Object {
+[@vm.inferred-type.metadata=#lib::A2]  field dynamic _aa = new self::A2::•();
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  get aa2() → dynamic
+    return [@vm.direct-call.metadata=#lib::B2Base::_aa] [@vm.inferred-type.metadata=#lib::A2] this.{self::B2Base::_aa};
+}
+class B2 extends self::B2Base {
+  synthetic constructor •() → void
+    : super self::B2Base::•()
+    ;
+  method doSuperCall() → void {
+    [@vm.inferred-type.metadata=#lib::A2] super.{self::B2Base::aa2}.call(1, 2, 3, 4, 5, new self::T2::•());
+  }
+}
+class T3 extends core::Object {
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method doTest3() → void {
+    self::ok = true;
+  }
+}
+class A3 extends core::Object {
+[@vm.inferred-type.metadata=#lib::T3?]  field dynamic foo = null;
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method call([dynamic a1 = null, dynamic a2 = null, dynamic a3 = null, dynamic a4 = null, dynamic a5 = null, dynamic a6 = null, [@vm.inferred-type.metadata=#lib::T3?] dynamic a7 = null]) → void {
+    [@vm.direct-call.metadata=#lib::A3::foo] this.{self::A3::foo} = a7;
+  }
+}
+class B3 extends core::Object {
+[@vm.inferred-type.metadata=#lib::A3]  field self::A3 aa3 = new self::A3::•();
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+}
+class T4 extends core::Object {
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method doTest4() → void {
+    self::ok = true;
+  }
+}
+class A4 extends core::Object {
+[@vm.inferred-type.metadata=#lib::T4?]  field dynamic foo = null;
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  method call([dynamic a1 = null, dynamic a2 = null, dynamic a3 = null, dynamic a4 = null, dynamic a5 = null, dynamic a6 = null, dynamic a7 = null, [@vm.inferred-type.metadata=#lib::T4?] dynamic a8 = null]) → void {
+    [@vm.direct-call.metadata=#lib::A4::foo] this.{self::A4::foo} = a8;
+  }
+}
+class B4 extends core::Object {
+[@vm.inferred-type.metadata=#lib::A4]  field dynamic _aa = new self::A4::•();
+  synthetic constructor •() → void
+    : super core::Object::•()
+    ;
+  get aa4() → dynamic
+    return [@vm.direct-call.metadata=#lib::B4::_aa] [@vm.inferred-type.metadata=#lib::A4] this.{self::B4::_aa};
+}
+[@vm.inferred-type.metadata=dart.core::bool?]static field core::bool ok;
+[@vm.inferred-type.metadata=#lib::B3?]static field dynamic bb3 = new self::B3::•();
+[@vm.inferred-type.metadata=dart.core::_Closure?]static field core::Function unknown3 = () → dynamic => self::bb3;
+[@vm.inferred-type.metadata=#lib::B4?]static field dynamic bb4 = new self::B4::•();
+[@vm.inferred-type.metadata=dart.core::_Closure?]static field core::Function unknown4 = () → dynamic => self::bb4;
+static method test1() → void {
+  self::B1 bb = new self::B1::•();
+  [@vm.inferred-type.metadata=#lib::A1] bb.{self::B1::aa1}(1, 2, 3, 4, new self::T1::•());
+  self::ok = false;
+  [@vm.direct-call.metadata=#lib::T1::doTest1??] [@vm.direct-call.metadata=#lib::A1::foo] [@vm.inferred-type.metadata=#lib::T1?] [@vm.direct-call.metadata=#lib::B1::aa1] [@vm.inferred-type.metadata=#lib::A1] bb.{self::B1::aa1}.{self::A1::foo}.{self::T1::doTest1}();
+  exp::Expect::isTrue([@vm.inferred-type.metadata=dart.core::bool?] self::ok);
+}
+static method test2() → void {
+  self::B2 bb = new self::B2::•();
+  [@vm.direct-call.metadata=#lib::B2::doSuperCall] bb.{self::B2::doSuperCall}();
+  self::ok = false;
+  [@vm.inferred-type.metadata=#lib::T2?] [@vm.direct-call.metadata=#lib::B2Base::aa2] [@vm.inferred-type.metadata=#lib::A2] bb.{self::B2Base::aa2}.foo.doTest2();
+  exp::Expect::isTrue([@vm.inferred-type.metadata=dart.core::bool?] self::ok);
+}
+static method getDynamic3() → dynamic
+  return self::unknown3.call();
+static method test3() → void {
+  self::getDynamic3().aa3(1, 2, 3, 4, 5, 6, new self::T3::•());
+  self::ok = false;
+  [@vm.inferred-type.metadata=#lib::T3?] [@vm.inferred-type.metadata=#lib::A3] [@vm.inferred-type.metadata=#lib::B3?] self::bb3.aa3.foo.doTest3();
+  exp::Expect::isTrue([@vm.inferred-type.metadata=dart.core::bool?] self::ok);
+}
+static method getDynamic4() → dynamic
+  return self::unknown4.call();
+static method test4() → void {
+  self::getDynamic4().aa4(1, 2, 3, 4, 5, 6, 7, new self::T4::•());
+  self::ok = false;
+  [@vm.inferred-type.metadata=#lib::T4?] [@vm.inferred-type.metadata=#lib::A4] self::getDynamic4().aa4.foo.doTest4();
+  exp::Expect::isTrue([@vm.inferred-type.metadata=dart.core::bool?] self::ok);
+}
+static method main() → void {
+  self::test1();
+  self::test2();
+  self::test3();
+  self::test4();
+}
diff --git a/runtime/tests/vm/dart/regress_flutter16182_test.dart b/runtime/tests/vm/dart/regress_flutter16182_test.dart
new file mode 100644
index 0000000..81c386e
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_flutter16182_test.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2018, 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.
+
+// Regression test for https://github.com/flutter/flutter/issues/16182
+// Verifies that TFA correctly handles calls via fields/getters.
+
+import "package:expect/expect.dart";
+
+bool ok;
+
+class T1 {
+  // Should be reachable.
+  void doTest1() {
+    ok = true;
+  }
+}
+
+class A1 {
+  T1 foo;
+
+  void call([a1, a2, a3, a4, a5]) {
+    foo = a5;
+  }
+}
+
+class B1 {
+  A1 aa1 = new A1();
+}
+
+void test1() {
+  B1 bb = new B1();
+  bb.aa1(1, 2, 3, 4, new T1());
+
+  ok = false;
+  bb.aa1.foo.doTest1();
+  Expect.isTrue(ok);
+}
+
+class T2 {
+  // Should be reachable.
+  void doTest2() {
+    ok = true;
+  }
+}
+
+class A2 {
+  dynamic foo;
+
+  void call([a1, a2, a3, a4, a5, a6]) {
+    foo = a6;
+  }
+}
+
+class B2Base {
+  dynamic _aa = new A2();
+  dynamic get aa2 => _aa;
+}
+
+class B2 extends B2Base {
+  void doSuperCall() {
+    super.aa2(1, 2, 3, 4, 5, new T2());
+  }
+}
+
+void test2() {
+  var bb = new B2();
+  bb.doSuperCall();
+
+  ok = false;
+  bb.aa2.foo.doTest2();
+  Expect.isTrue(ok);
+}
+
+class T3 {
+  // Should be reachable.
+  void doTest3() {
+    ok = true;
+  }
+}
+
+class A3 {
+  dynamic foo;
+
+  void call([a1, a2, a3, a4, a5, a6, a7]) {
+    foo = a7;
+  }
+}
+
+class B3 {
+  A3 aa3 = new A3();
+}
+
+dynamic bb3 = new B3();
+Function unknown3 = () => bb3;
+getDynamic3() => unknown3.call();
+
+void test3() {
+  getDynamic3().aa3(1, 2, 3, 4, 5, 6, new T3());
+
+  ok = false;
+  bb3.aa3.foo.doTest3();
+  Expect.isTrue(ok);
+}
+
+class T4 {
+  // Should be reachable.
+  void doTest4() {
+    ok = true;
+  }
+}
+
+class A4 {
+  dynamic foo;
+
+  void call([a1, a2, a3, a4, a5, a6, a7, a8]) {
+    foo = a8;
+  }
+}
+
+class B4 {
+  dynamic _aa = new A4();
+  dynamic get aa4 => _aa;
+}
+
+dynamic bb4 = new B4();
+Function unknown4 = () => bb4;
+getDynamic4() => unknown4.call();
+
+void test4() {
+  getDynamic4().aa4(1, 2, 3, 4, 5, 6, 7, new T4());
+
+  ok = false;
+  getDynamic4().aa4.foo.doTest4();
+  Expect.isTrue(ok);
+}
+
+void main() {
+  test1();
+  test2();
+  test3();
+  test4();
+}