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