Extend cf analysis to cover exceptional control flow

Bug: b/236240410
Change-Id: I5c715a71a34c87c1178105c5422172adf255e43d
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
index a0a7ba0..51e185c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.TraversalUtils;
 import com.android.tools.r8.utils.TriFunction;
+import java.util.Collection;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -15,6 +16,8 @@
 
 public interface ControlFlowGraph<Block, Instruction> {
 
+  Collection<? extends Block> getBlocks();
+
   Block getEntryBlock();
 
   default Block getUniquePredecessor(Block block) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
index 7986623..dc0c539 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -22,6 +23,9 @@
   // The CfCode instruction index of the block's first instruction.
   int firstInstructionIndex = -1;
 
+  // The CfCode instruction index of the block's first throwing instruction.
+  int firstThrowingInstructionIndex = -1;
+
   // The CfCode instruction index of the block's last instruction.
   int lastInstructionIndex = -1;
 
@@ -46,6 +50,14 @@
     return firstInstructionIndex;
   }
 
+  public boolean hasThrowingInstruction() {
+    return firstThrowingInstructionIndex >= 0;
+  }
+
+  public int getFirstThrowingInstructionIndex() {
+    return firstThrowingInstructionIndex;
+  }
+
   public CfInstruction getLastInstruction(CfCode code) {
     return code.getInstructions().get(lastInstructionIndex);
   }
@@ -66,6 +78,22 @@
     return exceptionalSuccessors;
   }
 
+  @Override
+  public String toString() {
+    List<String> predecessorStrings = new ArrayList<>();
+    predecessors.forEach(p -> predecessorStrings.add(p.getRangeString()));
+    exceptionalPredecessors.forEach(p -> predecessorStrings.add("*" + p.getRangeString()));
+    return "CfBlock(range="
+        + getRangeString()
+        + ", predecessors="
+        + StringUtils.join(", ", predecessorStrings)
+        + ")";
+  }
+
+  private String getRangeString() {
+    return firstInstructionIndex + "->" + lastInstructionIndex;
+  }
+
   // A mutable interface for block construction.
   static class MutableCfBlock extends CfBlock {
 
@@ -86,6 +114,10 @@
       this.firstInstructionIndex = firstInstructionIndex;
     }
 
+    void setFirstThrowingInstructionIndex(int firstThrowingInstructionIndex) {
+      this.firstThrowingInstructionIndex = firstThrowingInstructionIndex;
+    }
+
     void setLastInstructionIndex(int lastInstructionIndex) {
       this.lastInstructionIndex = lastInstructionIndex;
     }
@@ -93,6 +125,10 @@
     boolean validate(CfControlFlowGraph cfg, InternalOptions options) {
       assert 0 <= firstInstructionIndex;
       assert firstInstructionIndex <= lastInstructionIndex;
+      assert firstThrowingInstructionIndex < 0
+          || firstInstructionIndex <= firstThrowingInstructionIndex;
+      assert firstThrowingInstructionIndex < 0
+          || firstThrowingInstructionIndex <= lastInstructionIndex;
       assert SetUtils.newIdentityHashSet(predecessors).size() == predecessors.size();
       assert this == cfg.getEntryBlock()
           || !predecessors.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
index 2b58157..7069adb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
@@ -22,6 +22,7 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -64,6 +65,11 @@
   }
 
   @Override
+  public Collection<? extends CfBlock> getBlocks() {
+    return blocks.values();
+  }
+
+  @Override
   public CfBlock getEntryBlock() {
     return getBlock(code.getInstruction(0));
   }
@@ -270,6 +276,9 @@
         assert !instruction.isLabel()
             || verifyCatchHandlersUnchanged(
                 instruction.asLabel(), activeCatchHandlers, inactiveUntilCatchHandlers);
+        if (instruction.instructionTypeCanThrow() && !block.hasThrowingInstruction()) {
+          block.setFirstThrowingInstructionIndex(instructionIndex);
+        }
         if (isBlockExit(instructionIndex)) {
           break;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 409d050..dbcd9be 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1364,6 +1364,7 @@
     }
   }
 
+  @Override
   public LinkedList<BasicBlock> getBlocks() {
     return blocks;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
index 6ffdfef..98e576b 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -100,6 +100,11 @@
   }
 
   @Override
+  public CfFrameState pushException(CfAnalysisConfig config, DexType guard) {
+    return this;
+  }
+
+  @Override
   public CfFrameState readLocal(
       AppView<?> appView,
       int localIndex,
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 239cb8e..c14a235 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
@@ -291,6 +291,8 @@
     return push(config, valueType.toDexType(appView.dexItemFactory()));
   }
 
+  public abstract CfFrameState pushException(CfAnalysisConfig config, DexType guard);
+
   public abstract CfFrameState readLocal(
       AppView<?> appView,
       int localIndex,
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 b21c294..a6d436c 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
@@ -5,7 +5,7 @@
 package com.android.tools.r8.optimize.interfaces.analysis;
 
 import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
@@ -56,12 +56,15 @@
   private class TransferFunction
       implements AbstractTransferFunction<CfBlock, CfInstruction, CfFrameState> {
 
+    private final CfCode code;
     private final CfAnalysisConfig config;
+    private final ProgramMethod context;
 
     TransferFunction(ProgramMethod context) {
       CfCode code = context.getDefinition().getCode().asCfCode();
       int maxLocals = code.getMaxLocals();
       int maxStack = code.getMaxStack();
+      this.code = code;
       this.config =
           new CfAnalysisConfig() {
 
@@ -85,6 +88,7 @@
               return type == context.getHolder().getSuperType();
             }
           };
+      this.context = context;
     }
 
     @Override
@@ -94,20 +98,51 @@
     }
 
     @Override
+    public CfFrameState computeInitialState(CfBlock entryBlock, CfFrameState bottom) {
+      CfFrameState initialState = new ConcreteCfFrameState();
+      int localIndex = 0;
+      if (context.getDefinition().isInstance()) {
+        if (context.getDefinition().isInstanceInitializer()) {
+          initialState = initialState.storeLocal(localIndex, FrameType.uninitializedThis(), config);
+        } else {
+          initialState =
+              initialState.storeLocal(
+                  localIndex, FrameType.initialized(context.getHolderType()), config);
+        }
+        localIndex++;
+      }
+      for (DexType parameter : context.getParameters()) {
+        initialState =
+            initialState.storeLocal(localIndex, FrameType.initialized(parameter), config);
+        localIndex += parameter.getRequiredRegisters();
+      }
+      return initialState;
+    }
+
+    @Override
     public CfFrameState computeBlockEntryState(
         CfBlock block, CfBlock predecessor, CfFrameState predecessorExitState) {
       return predecessorExitState;
     }
 
     @Override
+    public boolean shouldTransferExceptionalControlFlowFromInstruction(
+        CfBlock throwBlock, CfInstruction throwInstruction) {
+      // All locals defined after the first throwing instruction cannot be accessed in the catch
+      // handler. Therefore, we only propagate the state from the first throwing instruction to the
+      // catch handler.
+      assert throwBlock.hasThrowingInstruction();
+      return throwInstruction == code.getInstruction(throwBlock.getFirstThrowingInstructionIndex());
+    }
+
+    @Override
     public CfFrameState computeExceptionalBlockEntryState(
         CfBlock block,
         DexType guard,
         CfBlock throwBlock,
         CfInstruction throwInstruction,
         CfFrameState throwState) {
-      // TODO(b/214496607): Handle exceptional control flow.
-      throw new Unimplemented();
+      return throwState.pushException(config, guard);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
index b5df6e6..acc1f3d 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -262,6 +262,15 @@
   }
 
   @Override
+  public CfFrameState pushException(CfAnalysisConfig config, DexType guard) {
+    Int2ObjectAVLTreeMap<FrameType> newLocals = new Int2ObjectAVLTreeMap<>(locals);
+    ArrayDeque<PreciseFrameType> newStack = new ArrayDeque<>();
+    int newStackHeight = 0;
+    return new ConcreteCfFrameState(newLocals, newStack, newStackHeight)
+        .push(config, FrameType.initialized(guard));
+  }
+
+  @Override
   public CfFrameState readLocal(
       AppView<?> appView,
       int localIndex,
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
index 892d57b..eda488a 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -185,6 +185,11 @@
   }
 
   @Override
+  public CfFrameState pushException(CfAnalysisConfig config, DexType guard) {
+    return this;
+  }
+
+  @Override
   public CfFrameState readLocal(
       AppView<?> appView,
       int localIndex,