Bail out of control-flow analysis on error states

This also changes the worklist usage to avoid pushing duplicates.

Bug: b/295349278
Change-Id: I924e91f169a15eaf196647044a183917d2e57a83
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
index ad58a5c..6c77bb9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.FailedDataflowAnalysisResult;
 
 /**
  * A transfer function that defines the abstract semantics of the instructions in the program
@@ -57,4 +58,9 @@
       StateType throwState) {
     return throwState;
   }
+
+  default FailedDataflowAnalysisResult createFailedAnalysisResult(
+      Instruction instruction, TransferFunctionResult<StateType> transferResult) {
+    return new FailedDataflowAnalysisResult();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
index 3a8b75c..f81a516 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
@@ -73,7 +73,7 @@
 
   private DataflowAnalysisResult run(WorkList<Block> worklist, Timing timing) {
     while (worklist.hasNext()) {
-      Block initialBlock = worklist.next();
+      Block initialBlock = worklist.removeSeen();
       Block block = initialBlock;
       Block end = null;
       // Compute the abstract state upon entry to the basic block, by joining all the predecessor
@@ -83,7 +83,7 @@
 
       TransferFunctionResult<StateType> blockResult = transfer.applyBlock(initialBlock, state);
       if (blockResult.isFailedTransferResult()) {
-        return new FailedDataflowAnalysisResult();
+        return transfer.createFailedAnalysisResult(null, state);
       }
       state = blockResult.asAbstractState();
 
@@ -105,14 +105,15 @@
                   TransferFunctionResult<StateType> transferResult =
                       transfer.apply(instruction, previousState);
                   if (transferResult.isFailedTransferResult()) {
-                    timing.end();
-                    return TraversalContinuation.doBreak(new FailedDataflowAnalysisResult());
+                    return TraversalContinuation.doBreak(
+                        transfer.createFailedAnalysisResult(instruction, transferResult));
                   }
                   assert transferResult.isAbstractState();
                   return TraversalContinuation.doContinue(transferResult.asAbstractState());
                 },
                 state);
         if (traversalContinuation.isBreak()) {
+          timing.end();
           return traversalContinuation.asBreak().getValue();
         }
         state = traversalContinuation.asContinue().getValue();
@@ -128,7 +129,7 @@
       // Update the block exit state, and re-enqueue all successor blocks if the abstract state
       // changed.
       if (setBlockExitState(end, state)) {
-        cfg.forEachSuccessor(end, worklist::addIgnoringSeenSet);
+        cfg.forEachSuccessor(end, worklist::addIfNotSeen);
       }
 
       // Add the computed exit state to the entry state of each normal successor that satisfies the
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
index 437d01d..b15a35f 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -100,6 +100,11 @@
     return false;
   }
 
+  @Override
+  public boolean isFailedTransferResult() {
+    return isError();
+  }
+
   public ErroneousCfFrameState asError() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
index 7d65c1e..5d149ed 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.FailedDataflowAnalysisResult;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfBlock;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfControlFlowGraph;
@@ -174,6 +175,15 @@
     CfIntraproceduralDataflowAnalysis<CfFrameState> analysis =
         new CfIntraproceduralDataflowAnalysis<>(appView, CfFrameState.bottom(), cfg, transfer);
     DataflowAnalysisResult result = analysis.run(cfg.getEntryBlock());
+    if (result.isFailedAnalysisResult()) {
+      FailedCfAnalysisResult failedResult = (FailedCfAnalysisResult) result;
+      int index =
+          failedResult.instruction == null
+              ? 0
+              : code.getInstructions().indexOf(failedResult.instruction);
+      helper.registerUnverifiableCode(method, index, failedResult.errorState);
+      return;
+    }
     assert result.isSuccessfulAnalysisResult();
     for (CfBlock block : cfg.getBlocks()) {
       if (analysis.isIntermediateBlock(block)) {
@@ -181,6 +191,8 @@
       }
       CfFrameState state = analysis.computeBlockEntryState(block);
       if (state.isError()) {
+        // Any error should terminate with a "FailedAnalysisResult" above.
+        assert false;
         helper.registerUnverifiableCode(method, 0, state.asError());
         return;
       }
@@ -192,6 +204,8 @@
           helper.processInstruction(instruction, state);
           state = transfer.apply(instruction, state).asAbstractState();
           if (state.isError()) {
+            // Any error should terminate with a "FailedAnalysisResult" above.
+            assert false;
             helper.registerUnverifiableCode(method, instructionIndex, state.asError());
             return;
           }
@@ -250,6 +264,17 @@
     methods.forEach(method -> reporter.warning(unverifiableCodeDiagnostics.get(method)));
   }
 
+  private static class FailedCfAnalysisResult extends FailedDataflowAnalysisResult {
+
+    private final CfInstruction instruction;
+    private final ErroneousCfFrameState errorState;
+
+    public FailedCfAnalysisResult(CfInstruction instruction, ErroneousCfFrameState errorState) {
+      this.instruction = instruction;
+      this.errorState = errorState;
+    }
+  }
+
   private class TransferFunction
       implements AbstractTransferFunction<CfBlock, CfInstruction, CfFrameState> {
 
@@ -262,6 +287,13 @@
     }
 
     @Override
+    public FailedDataflowAnalysisResult createFailedAnalysisResult(
+        CfInstruction instruction, TransferFunctionResult<CfFrameState> transferResult) {
+      ErroneousCfFrameState errorState = (ErroneousCfFrameState) transferResult;
+      return new FailedCfAnalysisResult(instruction, errorState);
+    }
+
+    @Override
     public TransferFunctionResult<CfFrameState> apply(
         CfInstruction instruction, CfFrameState state) {
       return instruction.evaluate(state, appView, config);