Add argument propagator test inspector

Change-Id: I974341af6709379ba0f29f200269ea878d8ab333
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 354cc9a..ad6e869 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -122,6 +122,7 @@
     // Unset the scanner since all code objects have been scanned at this point.
     assert appView.isAllCodeProcessed();
     MethodStateCollectionByReference codeScannerResult = codeScanner.getMethodStates();
+    appView.testing().argumentPropagatorEventConsumer.acceptCodeScannerResult(codeScannerResult);
     codeScanner = null;
 
     timing.begin("Compute optimization info");
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java
new file mode 100644
index 0000000..c02d1ad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, the R8 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.
+
+package com.android.tools.r8.optimize.argumentpropagation;
+
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+
+public interface ArgumentPropagatorEventConsumer {
+
+  static ArgumentPropagatorEventConsumer emptyConsumer() {
+    return new ArgumentPropagatorEventConsumer() {
+      @Override
+      public void acceptCodeScannerResult(MethodStateCollectionByReference methodStates) {
+        // Intentionally empty.
+      }
+    };
+  }
+
+  void acceptCodeScannerResult(MethodStateCollectionByReference methodStates);
+
+  default ArgumentPropagatorEventConsumer andThen(
+      ArgumentPropagatorEventConsumer nextEventConsumer) {
+    ArgumentPropagatorEventConsumer self = this;
+    return new ArgumentPropagatorEventConsumer() {
+      @Override
+      public void acceptCodeScannerResult(MethodStateCollectionByReference methodStates) {
+        self.acceptCodeScannerResult(methodStates);
+        nextEventConsumer.acceptCodeScannerResult(methodStates);
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
index cbc73da..3a54fe8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
@@ -86,7 +86,11 @@
   }
 
   public MethodState get(ProgramMethod method) {
-    return methodStates.getOrDefault(getKey(method), MethodState.bottom());
+    return get(getKey(method));
+  }
+
+  public MethodState get(K method) {
+    return methodStates.getOrDefault(method, MethodState.bottom());
   }
 
   public boolean isEmpty() {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 5ee21c1..f391fb1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
 import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.ClassReference;
@@ -1484,6 +1485,9 @@
 
     public static int NO_LIMIT = -1;
 
+    public ArgumentPropagatorEventConsumer argumentPropagatorEventConsumer =
+        ArgumentPropagatorEventConsumer.emptyConsumer();
+
     // Force writing the specified bytes as the DEX version content.
     public byte[] forceDexVersionBytes = null;
 
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 37deccc..9818d3e 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -7,6 +7,8 @@
 import static com.android.tools.r8.utils.ClassReferenceUtils.getClassReferenceComparator;
 import static com.android.tools.r8.utils.TypeReferenceUtils.getTypeReferenceComparator;
 
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.references.ArrayReference;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
@@ -87,6 +89,15 @@
     }
   }
 
+  public static DexMethod toDexMethod(
+      MethodReference methodReference, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createMethod(
+        ClassReferenceUtils.toDexType(methodReference.getHolderClass(), dexItemFactory),
+        TypeReferenceUtils.toDexProto(
+            methodReference.getFormalTypes(), methodReference.getReturnType(), dexItemFactory),
+        methodReference.getMethodName());
+  }
+
   public static String toSourceStringWithoutHolderAndReturnType(MethodReference methodReference) {
     return toSourceString(methodReference, false, false);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
index 9d322e6..a5145f4 100644
--- a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
@@ -37,6 +37,15 @@
     return COMPARATOR;
   }
 
+  public static DexProto toDexProto(
+      List<TypeReference> formalTypes, TypeReference returnType, DexItemFactory dexItemFactory) {
+    return toDexProto(
+        formalTypes,
+        returnType,
+        dexItemFactory,
+        classReference -> ClassReferenceUtils.toDexType(classReference, dexItemFactory));
+  }
+
   /**
    * Converts the given {@param formalTypes} and {@param returnType} to a {@link DexProto}.
    *
@@ -55,6 +64,13 @@
             formalType -> toDexType(formalType, dexItemFactory, classReferenceConverter)));
   }
 
+  public static DexType toDexType(TypeReference typeReference, DexItemFactory dexItemFactory) {
+    return toDexType(
+        typeReference,
+        dexItemFactory,
+        classReference -> ClassReferenceUtils.toDexType(classReference, dexItemFactory));
+  }
+
   /**
    * Converts the given {@param typeReference} to a {@link DexType}.
    *
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 1e1b55f..3702430 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,6 +10,8 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -18,6 +20,7 @@
 import com.android.tools.r8.utils.ForwardingOutputStream;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingOutputStream;
+import com.android.tools.r8.utils.codeinspector.ArgumentPropagatorCodeScannerResultInspector;
 import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
 import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
@@ -103,6 +106,23 @@
       B builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException;
 
+  public T addArgumentPropagatorCodeScannerResultInspector(
+      ThrowableConsumer<ArgumentPropagatorCodeScannerResultInspector> inspector) {
+    return addOptionsModification(
+        options ->
+            options.testing.argumentPropagatorEventConsumer =
+                options.testing.argumentPropagatorEventConsumer.andThen(
+                    new ArgumentPropagatorEventConsumer() {
+                      @Override
+                      public void acceptCodeScannerResult(
+                          MethodStateCollectionByReference methodStates) {
+                        inspector.acceptWithRuntimeException(
+                            new ArgumentPropagatorCodeScannerResultInspector(
+                                options.dexItemFactory(), methodStates));
+                      }
+                    }));
+  }
+
   public T addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
     if (optionsConsumer != null) {
       this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer);
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java
new file mode 100644
index 0000000..0359d25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, the R8 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.
+
+package com.android.tools.r8.optimize.argumentpropagation;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  // TODO(b/190154391): The state for A.test() should be unknown.
+  @Test(expected = CompilationFailedException.class)
+  public void test() throws Exception {
+    BooleanBox inspected = new BooleanBox();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addArgumentPropagatorCodeScannerResultInspector(
+            inspector ->
+                inspector
+                    .assertHasUnknownMethodState(
+                        Reference.methodFromMethod(A.class.getDeclaredMethod("test")))
+                    .assertHasBottomMethodState(
+                        Reference.methodFromMethod(B.class.getDeclaredMethod("test")))
+                    .apply(ignore -> inspected.set()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(true))
+        .addVerticallyMergedClassesInspector(
+            VerticallyMergedClassesInspector::assertNoClassesMerged)
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+    assertTrue(inspected.isTrue());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A aOrB = System.currentTimeMillis() >= 0 ? new A() : new B();
+      aOrB.test();
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class A {
+
+    void test() {
+      System.out.println("A");
+    }
+  }
+
+  static class B extends A {
+
+    @Override
+    void test() {
+      System.out.println("B");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java
new file mode 100644
index 0000000..2b87e0b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, the R8 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.
+
+package com.android.tools.r8.optimize.argumentpropagation;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ParameterWithUnknownArgumentInformationWidenedToUnknownTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  // TODO(b/190154391): The state for Main.test() should be unknown.
+  @Test(expected = CompilationFailedException.class)
+  public void test() throws Exception {
+    BooleanBox inspected = new BooleanBox();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addArgumentPropagatorCodeScannerResultInspector(
+            inspector ->
+                inspector
+                    .assertHasUnknownMethodState(
+                        Reference.methodFromMethod(Main.class.getDeclaredMethod("test", A.class)))
+                    .apply(ignore -> inspected.set()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(true))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(null, "A");
+    assertTrue(inspected.isTrue());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A alwaysNull = null;
+      A neverNull = new A();
+      test(alwaysNull);
+      test(neverNull);
+    }
+
+    @NeverInline
+    static void test(A a) {
+      System.out.println(a);
+    }
+  }
+
+  static class A {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java
new file mode 100644
index 0000000..b427284
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2021, the R8 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.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.util.function.Predicate;
+
+public class ArgumentPropagatorCodeScannerResultInspector {
+
+  private final DexItemFactory dexItemFactory;
+  private final MethodStateCollectionByReference methodStates;
+
+  public ArgumentPropagatorCodeScannerResultInspector(
+      DexItemFactory dexItemFactory, MethodStateCollectionByReference methodStates) {
+    this.dexItemFactory = dexItemFactory;
+    this.methodStates = methodStates;
+  }
+
+  public ArgumentPropagatorCodeScannerResultInspector apply(
+      ThrowableConsumer<ArgumentPropagatorCodeScannerResultInspector> consumer) {
+    consumer.acceptWithRuntimeException(this);
+    return this;
+  }
+
+  public ArgumentPropagatorCodeScannerResultInspector assertHasBottomMethodState(
+      MethodReference methodReference) {
+    return assertHasMethodStateThatMatches(
+        "Expected method state for "
+            + MethodReferenceUtils.toSourceString(methodReference)
+            + " to be bottom",
+        methodReference,
+        MethodState::isBottom);
+  }
+
+  public ArgumentPropagatorCodeScannerResultInspector assertHasUnknownMethodState(
+      MethodReference methodReference) {
+    return assertHasMethodStateThatMatches(
+        "Expected method state for "
+            + MethodReferenceUtils.toSourceString(methodReference)
+            + " to be unknown",
+        methodReference,
+        MethodState::isUnknown);
+  }
+
+  public ArgumentPropagatorCodeScannerResultInspector assertHasMethodStateThatMatches(
+      String message, MethodReference methodReference, Predicate<MethodState> predicate) {
+    DexMethod method = MethodReferenceUtils.toDexMethod(methodReference, dexItemFactory);
+    MethodState methodState = methodStates.get(method);
+    assertTrue(message + ", was: " + methodState, predicate.test(methodState));
+    return this;
+  }
+}