Test path constraint analysis on a small example

Bug: b/302281503
Change-Id: I93456ef564a0cba740d060ca7de5dfd51e18ac14
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
index 73eeb55..550019d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -44,6 +44,10 @@
       this.blockExitStates = blockExitStates;
     }
 
+    public StateType getBlockExitState(Block block) {
+      return blockExitStates.get(block);
+    }
+
     public StateType join(AppView<?> appView) {
       StateType result = null;
       for (StateType blockExitState : blockExitStates.values()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
index 492b2a0..dd5b2c9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
@@ -37,10 +37,11 @@
 public class PathConstraintAnalysis
     extends IntraproceduralDataflowAnalysis<PathConstraintAnalysisState> {
 
-  public PathConstraintAnalysis(
-      AppView<AppInfoWithLiveness> appView,
-      IRCode code,
-      PathConstraintAnalysisTransferFunction transfer) {
-    super(appView, PathConstraintAnalysisState.bottom(), code, transfer);
+  public PathConstraintAnalysis(AppView<AppInfoWithLiveness> appView, IRCode code) {
+    super(
+        appView,
+        PathConstraintAnalysisState.bottom(),
+        code,
+        new PathConstraintAnalysisTransferFunction(appView.abstractValueFactory()));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
index 47eeba1..361ed99 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
@@ -88,6 +88,19 @@
     return newPathConstraints;
   }
 
+  public Set<ComputationTreeNode> getPathConstraints() {
+    return pathConstraints;
+  }
+
+  public Set<ComputationTreeNode> getNegatedPathConstraints() {
+    return negatedPathConstraints;
+  }
+
+  @Override
+  public boolean isConcrete() {
+    return true;
+  }
+
   @Override
   public ConcretePathConstraintAnalysisState asConcreteState() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintAnalysisState.java
index 09f16c2..fea03cc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintAnalysisState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintAnalysisState.java
@@ -25,6 +25,10 @@
     return false;
   }
 
+  public boolean isConcrete() {
+    return false;
+  }
+
   public boolean isUnknown() {
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java b/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java
new file mode 100644
index 0000000..56573a5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2024, 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.ir.analysis.path;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
+import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeUnopCompareNode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 PathConstraintAnalysisUnitTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp app =
+        AndroidApp.builder()
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+            .addLibraryFile(ToolHelper.getMostRecentAndroidJar())
+            .build();
+    AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(app);
+    CodeInspector inspector = new CodeInspector(app);
+    IRCode code =
+        inspector.clazz(Main.class).uniqueMethodWithOriginalName("greet").buildIR(appView);
+    PathConstraintAnalysis analysis = new PathConstraintAnalysis(appView, code);
+    DataflowAnalysisResult result = analysis.run(code.entryBlock());
+    assertTrue(result.isSuccessfulAnalysisResult());
+    SuccessfulDataflowAnalysisResult<BasicBlock, PathConstraintAnalysisState> successfulResult =
+        result.asSuccessfulAnalysisResult();
+
+    // Inspect ENTRY state.
+    PathConstraintAnalysisState entryConstraint =
+        successfulResult.getBlockExitState(code.entryBlock());
+    assertTrue(entryConstraint.isBottom());
+
+    // Inspect THEN state.
+    PathConstraintAnalysisState thenConstraint =
+        successfulResult.getBlockExitState(code.entryBlock().exit().asIf().getTrueTarget());
+    assertTrue(thenConstraint.isConcrete());
+
+    ConcretePathConstraintAnalysisState concreteThenConstraint = thenConstraint.asConcreteState();
+    assertEquals(1, concreteThenConstraint.getPathConstraints().size());
+    assertEquals(0, concreteThenConstraint.getNegatedPathConstraints().size());
+
+    ComputationTreeNode thenPathConstraint =
+        concreteThenConstraint.getPathConstraints().iterator().next();
+    assertTrue(thenPathConstraint instanceof ComputationTreeUnopCompareNode);
+
+    // Inspect ELSE state.
+    PathConstraintAnalysisState elseConstraint =
+        successfulResult.getBlockExitState(code.entryBlock().exit().asIf().fallthroughBlock());
+    assertTrue(elseConstraint.isConcrete());
+
+    ConcretePathConstraintAnalysisState concreteElseConstraint = elseConstraint.asConcreteState();
+    assertEquals(0, concreteElseConstraint.getPathConstraints().size());
+    assertEquals(1, concreteElseConstraint.getNegatedPathConstraints().size());
+
+    ComputationTreeNode elsePathConstraint =
+        concreteElseConstraint.getNegatedPathConstraints().iterator().next();
+    assertEquals(thenPathConstraint, elsePathConstraint);
+
+    // Inspect RETURN state.
+    PathConstraintAnalysisState returnConstraint =
+        successfulResult.getBlockExitState(code.computeNormalExitBlocks().get(0));
+    assertTrue(returnConstraint.isConcrete());
+
+    ConcretePathConstraintAnalysisState concreteReturnConstraint =
+        returnConstraint.asConcreteState();
+    assertEquals(1, concreteReturnConstraint.getPathConstraints().size());
+    assertEquals(
+        concreteReturnConstraint.getPathConstraints(),
+        concreteReturnConstraint.getNegatedPathConstraints());
+  }
+
+  static class Main {
+
+    public static void greet(String greeting, int flags) {
+      if ((flags & 1) != 0) {
+        greeting = "Hello, world!";
+      }
+      System.out.println(greeting);
+    }
+  }
+}