Merge "Register the correct target for a super invoke for generating uses"
diff --git a/build.gradle b/build.gradle
index e469e03..2e70b1e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1199,10 +1199,10 @@
         kotlin.Kotlinc.KotlinTargetVersion.values().each { kotlinTargetVersion ->
             def name = dir.getName()
             def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
-            def outputFile = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}.jar"
-            def javaOutput = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}/java"
+            def outputFile = "build/test/r8KotlinTestResources/${kotlinTargetVersion}/${name}.jar"
+            def javaOutput = "build/test/r8KotlinTestResources/${kotlinTargetVersion}/${name}/java"
             def javaOutputJarName = "${name}.java.jar"
-            def javaOutputJarDir = "build/test/kotlinR8TestResources/${kotlinTargetVersion}"
+            def javaOutputJarDir = "build/test/r8KotlinTestResources/${kotlinTargetVersion}"
             task "${taskName}Kotlin"(type: kotlin.Kotlinc) {
                 source = fileTree(dir: file("${examplesDir}/${name}"),
                         include: ['**/*.kt', '**/*.java'])
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 4288e55..908c4c5 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.19-dev";
+  public static final String LABEL = "1.4.20-dev";
 
   private Version() {
   }
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 648d644..5644ed7 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
@@ -98,10 +98,10 @@
   // If this is the case, which holds for javac code, then we want to ensure that it remains so.
   private boolean allThrowingInstructionsHavePositions;
 
+  // TODO(b/122257895): Update OptimizationInfo to capture instruction kinds of interest.
   public final boolean hasDebugPositions;
-
-  // TODO(jsjeon): maybe making similar to DexEncodedMethod.OptimizationInfo?
   public boolean hasConstString;
+  public final boolean hasMonitorInstruction;
 
   public final InternalOptions options;
 
@@ -113,6 +113,7 @@
       LinkedList<BasicBlock> blocks,
       ValueNumberGenerator valueNumberGenerator,
       boolean hasDebugPositions,
+      boolean hasMonitorInstruction,
       boolean hasConstString,
       Origin origin) {
     assert options != null;
@@ -121,6 +122,7 @@
     this.blocks = blocks;
     this.valueNumberGenerator = valueNumberGenerator;
     this.hasDebugPositions = hasDebugPositions;
+    this.hasMonitorInstruction = hasMonitorInstruction;
     this.hasConstString = hasConstString;
     this.origin = origin;
     // TODO(zerny): Remove or update this property now that all instructions have positions.
@@ -128,6 +130,7 @@
   }
 
   public void copyMetadataFromInlinee(IRCode inlinee) {
+    assert !inlinee.hasMonitorInstruction;
     this.hasConstString |= inlinee.hasConstString;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index c789424..007cac1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -419,6 +419,9 @@
   // Flag indicating if a const string is ever loaded.
   private boolean hasConstString = false;
 
+  // Flag indicating if the code has a monitor instruction.
+  private boolean hasMonitorInstruction = false;
+
   public IRBuilder(
       DexEncodedMethod method,
       AppInfo appInfo,
@@ -613,6 +616,7 @@
             blocks,
             valueNumberGenerator,
             hasDebugPositions,
+            hasMonitorInstruction,
             hasConstString,
             origin);
 
@@ -1133,6 +1137,7 @@
     Value in = readRegister(monitor, ValueTypeConstraint.OBJECT);
     Monitor monitorEnter = new Monitor(type, in);
     add(monitorEnter);
+    hasMonitorInstruction = true;
     return monitorEnter;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7bc04cc..737ab05 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1155,26 +1155,6 @@
 
   private void computeKotlinNonNullParamHints(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
-    DexClass originalHolder = definitionFor(originalSignature.holder);
-    if (!originalHolder.hasKotlinInfo()) {
-      return;
-    }
-
-    // Use non-null parameter hints in Kotlin metadata if available.
-    KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
-    if (kotlinInfo.hasNonNullParameterHints()) {
-      BitSet hintFromMetadata =
-          kotlinInfo.lookupNonNullParameterHint(
-              originalSignature.name.toString(), originalSignature.proto.toDescriptorString());
-      if (hintFromMetadata != null) {
-        if (hintFromMetadata.length() > 0) {
-          feedback.setNonNullParamOrThrow(method, hintFromMetadata);
-        }
-        return;
-      }
-    }
-    // Otherwise, fall back to inspecting the code.
     List<Value> arguments = code.collectArguments(true);
     BitSet paramsCheckedForNull = new BitSet();
     DexMethod checkParameterIsNotNull =
@@ -1194,17 +1174,40 @@
         }
         InvokeMethod invoke = user.asInvokeMethod();
         DexMethod invokedMethod =
-            appView.graphLense().getOriginalMethodSignature(invoke.getInvokedMethod());
+            graphLense().getOriginalMethodSignature(invoke.getInvokedMethod());
+        // TODO(b/121377154): Make sure there is no other side-effect before argument's null check.
+        // E.g., is this the first method invocation inside the method?
         if (invokedMethod == checkParameterIsNotNull && user.inValues().indexOf(argument) == 0) {
           paramsCheckedForNull.set(index);
         }
       }
     }
     if (paramsCheckedForNull.length() > 0) {
+      // Check if collected information conforms to non-null parameter hints in Kotlin metadata.
+      DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
+      DexClass originalHolder = definitionFor(originalSignature.holder);
+      if (originalHolder.hasKotlinInfo()) {
+        KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
+        if (kotlinInfo.hasNonNullParameterHints()) {
+          BitSet hintFromMetadata =
+              kotlinInfo.lookupNonNullParameterHint(
+                  originalSignature.name.toString(), originalSignature.proto.toDescriptorString());
+          if (hintFromMetadata != null && hintFromMetadata.length() > 0) {
+            if (!paramsCheckedForNull.equals(hintFromMetadata) && Log.ENABLED) {
+              Log.debug(getClass(), "Mismatching non-null param hints for %s: %s v.s. %s\n%s",
+                  paramsCheckedForNull.toString(),
+                  hintFromMetadata.toString(),
+                  method.toSourceString(),
+                  logCode(options, method));
+            }
+          }
+        }
+      }
       feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
     }
   }
 
+  // TODO(b/121377154): Consider merging compute(Kotlin)?NonNullParamHints into one.
   private void computeNonNullParamHints(
     OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     List<Value> arguments = code.collectArguments(true);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index fc4c88b..4c437bd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -60,6 +60,12 @@
         if (!current.isConstNumber() && !current.isConstString()) {
           continue;
         }
+        // Do not canonicalize ConstString instructions if there are monitor operations in the code.
+        // That could lead to unbalanced locking and could lead to situations where OOM exceptions
+        // could leave a synchronized method without unlocking the monitor.
+        if (current.isConstString() && code.hasMonitorInstruction) {
+          continue;
+        }
         // Constants with local info must not be canonicalized and must be filtered.
         if (current.outValue().hasLocalInfo()) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index ff38ebd..0251687 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -599,7 +599,12 @@
       isRematerializable = true;
       return;
     }
-    // TODO(ager): rematerialize const string as well.
+
+    // TODO(ager): rematerialize const string as well. However, in that case we have to be very
+    // careful with methods with synchronization. If we rematerialize in a block that has no
+    // other throwing instructions we can end up with lock-level verification issues. The
+    // rematerialized throwing const-string instruction is not covered by the catch range going
+    // to the monitor-exit instruction and we can leave the method without unlocking the monitor.
     if (!value.isConstNumber()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 53e8e66..0f1588f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -92,6 +92,8 @@
   }
 
   public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
+    // TODO(b/122295241): These generated rules should be linked into the graph, eg, the method
+    // using identified reflection should be the source keeping the target alive.
     assert clazz.type == method.method.getHolder();
     ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
     builder.setOrigin(proguardCompatOrigin);
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index 9139583..63dd265 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -9,7 +9,9 @@
 import java.nio.file.Paths;
 
 public abstract class KotlinTestBase extends TestBase {
-  private static final String RSRC = "kotlinR8TestResources";
+  // It is important that Kotlin is capitalized, otherwise the string will be relocated when
+  // building tests for r8lib with relocated dependencies.
+  private static final String RSRC = "r8KotlinTestResources";
 
   protected final KotlinTargetVersion targetVersion;
 
@@ -17,18 +19,21 @@
     this.targetVersion = targetVersion;
   }
 
-  protected Path getKotlinJarFile(String folder) {
+  protected static Path getKotlinJarFile(String folder, KotlinTargetVersion targetVersion) {
     return Paths.get(ToolHelper.TESTS_BUILD_DIR, RSRC,
         targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
   }
 
-  protected Path getJavaJarFile(String folder) {
+  protected Path getKotlinJarFile(String folder) {
+    return getKotlinJarFile(folder, targetVersion);
+  }
+
+  protected static Path getJavaJarFile(String folder, KotlinTargetVersion targetVersion) {
     return Paths.get(ToolHelper.TESTS_BUILD_DIR, RSRC,
         targetVersion.getFolderName(), folder + ".java" + FileUtils.JAR_EXTENSION);
   }
 
-  protected Path getMappingfile(String folder, String mappingFileName) {
-    return Paths.get(ToolHelper.TESTS_DIR, RSRC, folder, mappingFileName);
+  protected Path getJavaJarFile(String folder) {
+    return getJavaJarFile(folder, targetVersion);
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 475f764..8f9eab9 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -40,6 +40,7 @@
   private boolean enableClassInliningAnnotations = false;
   private boolean enableMergeAnnotations = false;
   private CollectingGraphConsumer graphConsumer = null;
+  private List<String> keepRules = new ArrayList<>();
 
   @Override
   R8TestBuilder self() {
@@ -53,6 +54,9 @@
     if (enableInliningAnnotations || enableClassInliningAnnotations || enableMergeAnnotations) {
       ToolHelper.allowTestProguardOptions(builder);
     }
+    if (!keepRules.isEmpty()) {
+      builder.addProguardConfiguration(keepRules, Origin.unknown());
+    }
     StringBuilder proguardMapBuilder = new StringBuilder();
     builder.setDisableTreeShaking(!enableTreeShaking);
     builder.setDisableMinification(!enableMinification);
@@ -75,7 +79,9 @@
 
   @Override
   public R8TestBuilder addKeepRules(Collection<String> rules) {
-    builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
+    // Delay adding the actual rules so that we only associate a single origin and unique lines to
+    // each actual rule.
+    keepRules.addAll(rules);
     return self();
   }
 
@@ -104,7 +110,7 @@
   public R8TestBuilder enableInliningAnnotations() {
     if (!enableInliningAnnotations) {
       enableInliningAnnotations = true;
-      addKeepRules(
+      addInternalKeepRules(
           "-forceinline class * { @com.android.tools.r8.ForceInline *; }",
           "-neverinline class * { @com.android.tools.r8.NeverInline *; }");
     }
@@ -114,7 +120,7 @@
   public R8TestBuilder enableClassInliningAnnotations() {
     if (!enableClassInliningAnnotations) {
       enableClassInliningAnnotations = true;
-      addKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
+      addInternalKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
     }
     return self();
   }
@@ -122,8 +128,7 @@
   public R8TestBuilder enableMergeAnnotations() {
     if (!enableMergeAnnotations) {
       enableMergeAnnotations = true;
-      addKeepRules(
-          "-nevermerge @com.android.tools.r8.NeverMerge class *");
+      addInternalKeepRules("-nevermerge @com.android.tools.r8.NeverMerge class *");
     }
     return self();
   }
@@ -150,4 +155,9 @@
     builder.setMainDexKeptGraphConsumer(graphConsumer);
     return self();
   }
+
+  private void addInternalKeepRules(String... rules) {
+    // We don't add these to the keep-rule set for other test provided rules.
+    builder.addProguardConfiguration(Arrays.asList(rules), Origin.unknown());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 211c297..81f0407 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
@@ -87,10 +86,7 @@
 
   public T addKeepMainRule(String mainClass) {
     return addKeepRules(
-        StringUtils.joinLines(
-            "-keep class " + mainClass + " {",
-            "  public static void main(java.lang.String[]);",
-            "}"));
+        "-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
   }
 
   public T addKeepMethodRules(MethodReference... methods) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 55c64ee..927bff2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -132,7 +132,14 @@
     AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
     IRCode code =
         new IRCode(
-            options, null, blocks, new ValueNumberGenerator(), false, false, Origin.unknown());
+            options,
+            null,
+            blocks,
+            new ValueNumberGenerator(),
+            false,
+            false,
+            false,
+            Origin.unknown());
     PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appInfo, code, options));
 
     // Check that all four constant number instructions remain.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 8db3a73..6f22e21 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -79,6 +79,7 @@
             new ValueNumberGenerator(),
             false,
             false,
+            false,
             Origin.unknown());
     CodeRewriter.collapseTrivialGotos(null, code);
     assertTrue(code.blocks.get(0).isTrivialGoto());
@@ -162,6 +163,7 @@
             new ValueNumberGenerator(),
             false,
             false,
+            false,
             Origin.unknown());
     CodeRewriter.collapseTrivialGotos(null, code);
     assertTrue(block0.getInstructions().get(1).isIf());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
new file mode 100644
index 0000000..0b049e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
@@ -0,0 +1,204 @@
+// Copyright (c) 2019, 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.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.RangeSubject;
+import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class StringInMonitorTestMain {
+  static final Object lock = new Object();
+
+  private static int foo = 0;
+
+  @NeverInline
+  private static synchronized void sync() throws OutOfMemoryError {
+    String bar = foo == 0 ? "bar" : "";
+    if (bar == "") {
+      System.out.println("bar");
+      throw new OutOfMemoryError();
+    }
+  }
+
+  @NeverInline
+  private static void oom() throws OutOfMemoryError {
+    System.out.println("oom");
+    if (System.currentTimeMillis() > 0) {
+      throw new OutOfMemoryError();
+    }
+    System.out.println("oom");
+    System.out.println("this-string-will-not-be-loaded.");
+    System.out.println("this-string-will-not-be-loaded.");
+  }
+
+  public static void main(String[] args) {
+    try {
+      synchronized (lock) {
+        System.out.println("1st sync");
+        sync();
+        oom();
+        System.out.println("1st sync");
+      }
+    } catch (OutOfMemoryError oom) {
+      // Pretend to recover from OOM
+      synchronized (lock) {
+        System.out.println("2nd sync");
+      }
+    }
+  }
+}
+
+@RunWith(Parameterized.class)
+public class StringInMonitorTest extends TestBase {
+  private final Backend backend;
+  private static final Class<?> MAIN = StringInMonitorTestMain.class;
+  private static final List<Class<?>> CLASSES = ImmutableList.of(NeverInline.class, MAIN);
+  private static final String JAVA_OUTPUT = StringUtils.lines(
+      "1st sync",
+      "oom",
+      "2nd sync"
+  );
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public StringInMonitorTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void testJVMoutput() throws Exception {
+    assumeTrue("Only run JVM reference once (for CF backend)", backend == Backend.CF);
+    testForJvm().addTestClasspath().run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+  }
+
+  private void test(
+      TestRunResult result,
+      int expectedConstStringCount1,
+      int expectedConstStringCount2,
+      int expectedConstStringCount3) throws Exception {
+    CodeInspector codeInspector = result.inspector();
+    ClassSubject mainClass = codeInspector.clazz(MAIN);
+    MethodSubject mainMethod = mainClass.mainMethod();
+    assertThat(mainMethod, isPresent());
+
+    long count = Streams.stream(mainMethod.iterateInstructions(
+        i -> i.isConstString("1st sync", JumboStringMode.ALLOW))).count();
+    assertEquals(expectedConstStringCount1, count);
+
+    // TODO(b/122302789): CfInstruction#getOffset()
+    if (backend == Backend.DEX) {
+      Iterator<InstructionSubject> constStringIterator =
+          mainMethod.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW));
+      // All const-string's in main(...) should be covered by try (or synthetic catch-all) region.
+      while (constStringIterator.hasNext()) {
+        InstructionSubject constString = constStringIterator.next();
+        InstructionOffsetSubject offsetSubject = constString.getOffset(mainMethod);
+        // const-string of interest is indirectly covered. See b/122285813 for reference.
+        Iterator<TryCatchSubject> catchAllIterator =
+            mainMethod.iterateTryCatches(TryCatchSubject::hasCatchAll);
+        boolean covered = false;
+        while (catchAllIterator.hasNext()) {
+          RangeSubject tryRange = catchAllIterator.next().getRange();
+          covered |= tryRange.includes(offsetSubject);
+          if (covered) {
+            break;
+          }
+        }
+        assertTrue(covered);
+      }
+    }
+
+    MethodSubject sync = mainClass.uniqueMethodWithName("sync");
+    assertThat(sync, isPresent());
+    count = Streams.stream(sync.iterateInstructions(
+        i -> i.isConstString("", JumboStringMode.ALLOW))).count();
+    assertEquals(expectedConstStringCount2, count);
+
+    // In CF, we don't explicitly add monitor-{enter|exit} and catch-all for synchronized methods.
+    if (backend == Backend.DEX) {
+      Iterator<InstructionSubject> constStringIterator =
+          sync.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW));
+      // All const-string's in sync() should be covered by the synthetic catch-all regions.
+      while (constStringIterator.hasNext()) {
+        InstructionSubject constString = constStringIterator.next();
+        InstructionOffsetSubject offsetSubject = constString.getOffset(mainMethod);
+        Iterator<TryCatchSubject> catchAllIterator =
+            sync.iterateTryCatches(TryCatchSubject::hasCatchAll);
+        boolean covered = false;
+        while (catchAllIterator.hasNext()) {
+          RangeSubject tryRange = catchAllIterator.next().getRange();
+          covered |= tryRange.includes(offsetSubject);
+          if (covered) {
+            break;
+          }
+        }
+        assertTrue(covered);
+      }
+    }
+
+    MethodSubject oom = mainClass.uniqueMethodWithName("oom");
+    assertThat(oom, isPresent());
+    count = Streams.stream(oom.iterateInstructions(
+        i -> i.isConstString("this-string-will-not-be-loaded.", JumboStringMode.ALLOW))).count();
+    assertEquals(expectedConstStringCount3, count);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue("Only run D8 for Dex backend", backend == Backend.DEX);
+    TestRunResult result = testForD8()
+        .debug()
+        .addProgramClasses(CLASSES)
+        .run(MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 2, 2, 1);
+
+    result = testForD8()
+        .release()
+        .addProgramClasses(CLASSES)
+        .run(MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 2, 2, 1);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(CLASSES)
+        .enableInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .run(MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+    // Due to the different behavior regarding constant canonicalization.
+    int expectedConstStringCount3 = backend == Backend.CF ? 2 : 1;
+    test(result, 2, 2, expectedConstStringCount3);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 9380115..4229213 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -10,9 +10,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.Code;
@@ -24,7 +24,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -32,7 +31,6 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -46,7 +44,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public abstract class AbstractR8KotlinTestBase extends TestBase {
+public abstract class AbstractR8KotlinTestBase extends KotlinTestBase {
 
   // This is the name of the Jasmin-generated class which contains the "main" method which will
   // invoke the tested method.
@@ -63,6 +61,10 @@
     return buildParameters(BooleanUtils.values(), KotlinTargetVersion.values());
   }
 
+  public AbstractR8KotlinTestBase() {
+    super(KotlinTargetVersion.JAVA_6);
+  }
+
   protected void addExtraClasspath(Path path) {
     extraClasspath.add(path);
   }
@@ -230,8 +232,8 @@
 
     // Build classpath for compilation (and java execution)
     classpath.clear();
-    classpath.add(getKotlinJarFile(folder));
-    classpath.add(getJavaJarFile(folder));
+    classpath.add(getKotlinJarFile(folder, targetVersion));
+    classpath.add(getJavaJarFile(folder, targetVersion));
     classpath.addAll(extraClasspath);
 
     // Build with R8
@@ -296,16 +298,6 @@
     }
   }
 
-  private Path getKotlinJarFile(String folder) {
-    return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
-        targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
-  }
-
-  private Path getJavaJarFile(String folder) {
-    return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
-        targetVersion.getFolderName(), folder + ".java" + FileUtils.JAR_EXTENSION);
-  }
-
   @FunctionalInterface
   interface AndroidAppInspector {
 
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 4248497..6906b76 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.ProguardTestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
@@ -279,8 +281,21 @@
       } else if (compiler == Compiler.PROGUARD) {
         return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
       } else if (compiler == Compiler.DX || compiler == Compiler.D8) {
-        return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
+        if (ToolHelper.getDexVm().getVersion() == Version.V4_0_4
+            || ToolHelper.getDexVm().getVersion() == Version.V4_4_4) {
+          return StringUtils.joinLines("Hello!", "Goodbye!", "");
+        } else if (ToolHelper.getDexVm().getVersion() == Version.V7_0_0) {
+          return StringUtils.joinLines(
+              "Hello!",
+              "Unexpected outcome of checkcast",
+              "Unexpected outcome of instanceof",
+              "Goodbye!",
+              "");
+        } else {
+          return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
+        }
       } else {
+        assert compiler == Compiler.JAVAC;
         return StringUtils.joinLines("Hello!", "Goodbye!", "");
       }
     } else {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTest.java
new file mode 100644
index 0000000..baeacd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTest.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2019, 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.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+
+public class KeptByAnnotatedMethodTest {
+
+  static class Inner {
+
+    @Keep
+    void foo() {
+      bar();
+    }
+
+    @NeverInline
+    static void bar() {
+      System.out.println("called bar");
+    }
+
+    @NeverInline
+    static void baz() {
+      System.out.println("called baz");
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    // Make inner class undecidable to avoid generating reflective rules.
+    Class<?> clazz = getInner(args.length);
+    Object instance = clazz.newInstance();
+    clazz.getDeclaredMethod("foo").invoke(instance);
+  }
+
+  private static Class<?> getInner(int i) {
+    return i == 0 ? Inner.class : null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
new file mode 100644
index 0000000..19cdf04
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2019, 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.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByAnnotatedMethodTestRunner extends TestBase {
+
+  private static final Class<?> CLASS = KeptByAnnotatedMethodTest.class;
+  private static final Class<?> INNER = KeptByAnnotatedMethodTest.Inner.class;
+  private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS, INNER);
+
+  private final String EXPECTED = StringUtils.lines("called bar");
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public KeptByAnnotatedMethodTestRunner(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void testKeptMethod() throws Exception {
+    MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+    MethodReference fooMethod = methodFromMethod(INNER.getDeclaredMethod("foo"));
+    MethodReference barMethod = methodFromMethod(INNER.getDeclaredMethod("bar"));
+    MethodReference bazMethod = methodFromMethod(INNER.getDeclaredMethod("baz"));
+
+    if (backend == Backend.CF) {
+      testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED);
+    }
+
+    Origin ruleOrigin = Origin.unknown();
+
+    String keepAnnotatedMethodsRule = "-keepclassmembers class * { @com.android.tools.r8.Keep *; }";
+    String keepClassesOfAnnotatedMethodsRule =
+        "-keep,allowobfuscation class * { <init>(); @com.android.tools.r8.Keep *; }";
+    GraphInspector inspector =
+        testForR8(backend)
+            .enableGraphInspector()
+            .enableInliningAnnotations()
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(CLASS)
+            .addKeepRules(keepAnnotatedMethodsRule, keepClassesOfAnnotatedMethodsRule)
+            .run(CLASS)
+            .assertSuccessWithOutput(EXPECTED)
+            .graphInspector();
+
+    assertEquals(3, inspector.getRoots().size());
+    QueryNode keepMain = inspector.rule(ruleOrigin, 1, 1).assertRoot();
+    QueryNode keepAnnotatedMethods = inspector.rule(keepAnnotatedMethodsRule).assertRoot();
+    QueryNode keepClassesOfAnnotatedMethods =
+        inspector.rule(keepClassesOfAnnotatedMethodsRule).assertRoot();
+
+    inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+    // Check that Inner is allowed and is actually renamed.
+    inspector.clazz(Reference.classFromClass(INNER)).assertRenamed();
+
+    // Check bar is called from foo.
+    inspector.method(barMethod).assertRenamed().assertInvokedFrom(fooMethod);
+
+    // Check foo *is not* called from main (it is reflectively accessed) and check that it is kept.
+    inspector
+        .method(fooMethod)
+        .assertNotRenamed()
+        .assertNotInvokedFrom(mainMethod)
+        // TODO(b/122297131): keepAnnotatedMethods should also be keeping foo alive!
+        .assertKeptBy(keepClassesOfAnnotatedMethods);
+
+    // Check baz is removed.
+    inspector.method(bazMethod).assertAbsent();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index ffc64cf..924ec2d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstClass;
@@ -20,6 +22,7 @@
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfMonitor;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.cf.code.CfPosition;
@@ -28,8 +31,10 @@
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Monitor.Type;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.Opcodes;
 
@@ -254,6 +259,36 @@
   }
 
   @Override
+  public boolean isMonitorEnter() {
+    if (!(instruction instanceof CfMonitor)) {
+      return false;
+    }
+    CfMonitor monitor = (CfMonitor) instruction;
+    return monitor.getType() == Type.ENTER;
+  }
+
+  @Override
+  public boolean isMonitorExit() {
+    if (!(instruction instanceof CfMonitor)) {
+      return false;
+    }
+    CfMonitor monitor = (CfMonitor) instruction;
+    return monitor.getType() == Type.EXIT;
+  }
+
+  @Override
+  public int size() {
+    // TODO(b/122302789): CfInstruction#getSize()
+    throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
+  }
+
+  @Override
+  public InstructionOffsetSubject getOffset(MethodSubject methodSubject) {
+    // TODO(b/122302789): CfInstruction#getOffset()
+    throw new UnsupportedOperationException("CfInstruction doesn't have offset yet.");
+  }
+
+  @Override
   public boolean equals(Object other) {
     return other instanceof CfInstructionSubject
         && instruction.equals(((CfInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchIterator.java
new file mode 100644
index 0000000..965bffc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchIterator.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import java.util.Iterator;
+
+class CfTryCatchIterator implements TryCatchIterator {
+  private final CodeInspector codeInspector;
+  private final CfCode cfCode;
+  private final Iterator<CfTryCatch> iterator;
+
+  CfTryCatchIterator(CodeInspector codeInspector, MethodSubject methodSubject) {
+    this.codeInspector = codeInspector;
+    assert methodSubject.isPresent();
+    Code code = methodSubject.getMethod().getCode();
+    assert code != null && code.isCfCode();
+    cfCode = code.asCfCode();
+    iterator = cfCode.getTryCatchRanges().iterator();
+  }
+
+  @Override
+  public boolean hasNext() {
+    return iterator.hasNext();
+  }
+
+  @Override
+  public TryCatchSubject next() {
+    return codeInspector.createTryCatchSubject(cfCode, iterator.next());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java
new file mode 100644
index 0000000..2d7aec8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2019, 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.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+
+class CfTryCatchSubject implements TryCatchSubject {
+  private final CfCode cfCode;
+  private final CfTryCatch tryCatch;
+
+  CfTryCatchSubject(CfCode cfCode, CfTryCatch tryCatch) {
+    this.cfCode = cfCode;
+    this.tryCatch = tryCatch;
+  }
+
+  @Override
+  public RangeSubject getRange() {
+    int index = 0;
+    int startIndex = -1;
+    int endIndex = -1;
+    for (CfInstruction instruction : cfCode.instructions) {
+      if (startIndex < 0 && instruction.equals(tryCatch.start)) {
+        startIndex = index;
+      }
+      if (endIndex < 0 && instruction.equals(tryCatch.end)) {
+        // To be inclusive, increase the index so that the range includes the current instruction.
+        assertNotEquals(-1, startIndex);
+        index++;
+        endIndex = index;
+        break;
+      }
+      index++;
+    }
+    assertNotEquals(-1, startIndex);
+    assertNotEquals(-1, endIndex);
+    assertTrue(startIndex < endIndex);
+    return new RangeSubject(startIndex, endIndex);
+  }
+
+  @Override
+  public boolean isCatching(String exceptionType) {
+    for (DexType guardType : tryCatch.guards) {
+      if (guardType.toString().equals(exceptionType)
+        || guardType.toDescriptorString().equals(exceptionType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public boolean hasCatchAll() {
+    return isCatching(DexItemFactory.catchAllType.toDescriptorString());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 8c59c98..3f53530 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -7,15 +7,20 @@
 
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
@@ -346,6 +351,26 @@
     }
   }
 
+  TryCatchSubject createTryCatchSubject(DexCode code, Try tryElement, TryHandler tryHandler) {
+    return new DexTryCatchSubject(code, tryElement, tryHandler);
+  }
+
+  TryCatchSubject createTryCatchSubject(CfCode code, CfTryCatch tryCatch) {
+    return new CfTryCatchSubject(code, tryCatch);
+  }
+
+  TryCatchIterator createTryCatchIterator(MethodSubject method) {
+    Code code = method.getMethod().getCode();
+    assert code != null;
+    if (code.isDexCode()) {
+      return new DexTryCatchIterator(this, method);
+    } else if (code.isCfCode()) {
+      return new CfTryCatchIterator(this, method);
+    } else {
+      throw new Unimplemented("TryCatchIterator is implemented for DexCode and CfCode only.");
+    }
+  }
+
   // Build the generic signature using the current mapping if any.
   class GenericSignatureGenerator implements GenericSignatureAction<String> {
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 9e3f563..6d463da 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -55,6 +55,8 @@
 import com.android.tools.r8.code.IputObject;
 import com.android.tools.r8.code.IputShort;
 import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.MonitorEnter;
+import com.android.tools.r8.code.MonitorExit;
 import com.android.tools.r8.code.MulDouble;
 import com.android.tools.r8.code.MulDouble2Addr;
 import com.android.tools.r8.code.MulFloat;
@@ -352,6 +354,26 @@
   }
 
   @Override
+  public boolean isMonitorEnter() {
+    return instruction instanceof MonitorEnter;
+  }
+
+  @Override
+  public boolean isMonitorExit() {
+    return instruction instanceof MonitorExit;
+  }
+
+  @Override
+  public int size() {
+    return instruction.getSize();
+  }
+
+  @Override
+  public InstructionOffsetSubject getOffset(MethodSubject methodSubject) {
+    return new InstructionOffsetSubject(instruction.getOffset());
+  }
+
+  @Override
   public boolean equals(Object other) {
     return other instanceof DexInstructionSubject
         && instruction.equals(((DexInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchIterator.java
new file mode 100644
index 0000000..dff1cbe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchIterator.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import java.util.NoSuchElementException;
+
+class DexTryCatchIterator implements TryCatchIterator {
+  private final CodeInspector codeInspector;
+  private final DexCode code;
+  private int index;
+
+  DexTryCatchIterator(CodeInspector codeInspector, MethodSubject methodSubject) {
+    this.codeInspector = codeInspector;
+    assert methodSubject.isPresent();
+    Code code = methodSubject.getMethod().getCode();
+    assert code != null && code.isDexCode();
+    this.code = code.asDexCode();
+    this.index = 0;
+  }
+
+  @Override
+  public boolean hasNext() {
+    return index < code.tries.length;
+  }
+
+  @Override
+  public TryCatchSubject next() {
+    if (index == code.tries.length) {
+      throw new NoSuchElementException();
+    }
+    int current = index++;
+    return codeInspector.createTryCatchSubject(
+        code, code.tries[current], code.handlers[code.tries[current].handlerIndex]);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
new file mode 100644
index 0000000..ed46590
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.graph.DexCode.TryHandler.NO_HANDLER;
+
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+
+class DexTryCatchSubject implements TryCatchSubject {
+  private final DexCode dexCode;
+  private final Try tryElement;
+  private final TryHandler tryHandler;
+
+  DexTryCatchSubject(DexCode dexCode, Try tryElement, TryHandler tryHandler) {
+    this.dexCode = dexCode;
+    this.tryElement = tryElement;
+    this.tryHandler = tryHandler;
+  }
+
+  @Override
+  public RangeSubject getRange() {
+    return new RangeSubject(
+        tryElement.startAddress,
+        tryElement.startAddress + tryElement.instructionCount - 1);
+  }
+
+  @Override
+  public boolean isCatching(String exceptionType) {
+    for (TypeAddrPair pair : tryHandler.pairs) {
+      if (pair.type.toString().equals(exceptionType)
+        || pair.type.toDescriptorString().equals(exceptionType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public boolean hasCatchAll() {
+    return tryHandler.catchAllAddr != NO_HANDLER;
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
index 870953e..598f0be 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
@@ -18,7 +18,6 @@
       CodeInspector codeInspector, MethodSubject method, Predicate<InstructionSubject> predicate) {
     this.iterator = codeInspector.createInstructionIterator(method);
     this.predicate = predicate;
-    hasNext();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredTryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredTryCatchIterator.java
new file mode 100644
index 0000000..14fcb21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredTryCatchIterator.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, 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 java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Predicate;
+
+class FilteredTryCatchIterator<T extends TryCatchSubject> implements Iterator<T> {
+
+  private final TryCatchIterator iterator;
+  private final Predicate<TryCatchSubject> predicate;
+  private TryCatchSubject pendingNext = null;
+
+  FilteredTryCatchIterator(
+      CodeInspector codeInspector,
+      MethodSubject methodSubject,
+      Predicate<TryCatchSubject> predicate) {
+    this.iterator = codeInspector.createTryCatchIterator(methodSubject);
+    this.predicate = predicate;
+  }
+
+  @Override
+  public boolean hasNext() {
+    if (pendingNext == null) {
+      while (iterator.hasNext()) {
+        pendingNext = iterator.next();
+        if (predicate.test(pendingNext)) {
+          break;
+        }
+        pendingNext = null;
+      }
+    }
+    return pendingNext != null;
+  }
+
+  @Override
+  public T next() {
+    hasNext();
+    if (pendingNext == null) {
+      throw new NoSuchElementException();
+    }
+    // We cannot tell if the provided predicate will only match instruction subjects of type T.
+    @SuppressWarnings("unchecked")
+    T result = (T) pendingNext;
+    pendingNext = null;
+    return result;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 6ce0496..2b4825e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.code.Instruction;
@@ -165,6 +168,17 @@
   }
 
   @Override
+  public Iterator<TryCatchSubject> iterateTryCatches() {
+    return codeInspector.createTryCatchIterator(this);
+  }
+
+  @Override
+  public <T extends TryCatchSubject> Iterator<T> iterateTryCatches(
+      Predicate<TryCatchSubject> filter) {
+    return new FilteredTryCatchIterator<>(codeInspector, this, filter);
+  }
+
+  @Override
   public boolean hasLocalVariableTable() {
     Code code = getMethod().getCode();
     if (code.isDexCode()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java
new file mode 100644
index 0000000..232884b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, 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;
+
+public class InstructionOffsetSubject {
+  // For Dex backend, this is bytecode offset.
+  // For CF backend, this is the index in the list of instruction.
+  final int offset;
+
+  InstructionOffsetSubject(int offset) {
+    this.offset = offset;
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index a8a3225..6abeacf 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -79,4 +79,12 @@
   boolean isSparseSwitch();
 
   boolean isMultiplication();
+
+  boolean isMonitorEnter();
+
+  boolean isMonitorExit();
+
+  int size();
+
+  InstructionOffsetSubject getOffset(MethodSubject methodSubject);
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index adcbf31..9cecb1d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -35,6 +35,15 @@
     return null;
   }
 
+  public Iterator<TryCatchSubject> iterateTryCatches() {
+    return null;
+  }
+
+  public <T extends TryCatchSubject> Iterator<T> iterateTryCatches(
+      Predicate<TryCatchSubject> filter) {
+    return null;
+  }
+
   public boolean hasLineNumberTable() {
     return getLineNumberTable() != null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java
new file mode 100644
index 0000000..12e3a4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2019, 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;
+
+public class RangeSubject {
+  // For Dex backend, these are bytecode offset.
+  // For CF backend, these are indices in the list of instructions.
+  final int start;
+  final int end;
+
+  RangeSubject(int start, int end) {
+    this.start = start;
+    this.end = end;
+  }
+
+  // Returns true if the given instruction is within the current range.
+  public boolean includes(InstructionOffsetSubject offsetSubject) {
+    return this.start <= offsetSubject.offset && offsetSubject.offset <= this.end;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchIterator.java
new file mode 100644
index 0000000..c37839d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchIterator.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, 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 java.util.Iterator;
+
+interface TryCatchIterator extends Iterator<TryCatchSubject> {}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java
new file mode 100644
index 0000000..838f7a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, 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;
+
+public interface TryCatchSubject {
+  RangeSubject getRange();
+  boolean isCatching(String exceptionType);
+  boolean hasCatchAll();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index b8ce79a..107978b 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -108,14 +108,21 @@
 
     public QueryNode assertInvokedFrom(MethodReference method) {
       assertTrue(
-          errorMessage("invokation from " + method.toString(), "none"), isInvokedFrom(method));
+          errorMessage("invocation from " + method.toString(), "none"), isInvokedFrom(method));
+      return this;
+    }
+
+    public QueryNode assertNotInvokedFrom(MethodReference method) {
+      assertTrue(
+          errorMessage("no invocation from " + method.toString(), "invoke"),
+          !isInvokedFrom(method));
       return this;
     }
 
     public QueryNode assertKeptBy(QueryNode node) {
       assertTrue("Invalid call to assertKeptBy with: " + node.getNodeDescription(),
           node.isPresent());
-      assertTrue(errorMessage("kept by " + getNodeDescription(), "none"), isKeptBy(node));
+      assertTrue(errorMessage("kept by " + node.getNodeDescription(), "none"), isKeptBy(node));
       return this;
     }
   }
@@ -287,6 +294,19 @@
     return Collections.unmodifiableSet(roots);
   }
 
+  public QueryNode rule(String ruleContent) {
+    KeepRuleGraphNode found = null;
+    for (KeepRuleGraphNode rule : rules) {
+      if (rule.getContent().equals(ruleContent)) {
+        if (found != null) {
+          fail("Found two matching rules matching " + ruleContent + ": " + found + " and " + rule);
+        }
+        found = rule;
+      }
+    }
+    return getQueryNode(found, ruleContent);
+  }
+
   public QueryNode rule(Origin origin, int line, int column) {
     String ruleReferenceString = getReferenceStringForRule(origin, line, column);
     KeepRuleGraphNode found = null;
@@ -317,6 +337,10 @@
     return "rule@" + origin + ":" + new TextPosition(0, line, column);
   }
 
+  public QueryNode clazz(ClassReference clazz) {
+    return getQueryNode(classes.get(clazz), clazz.toString());
+  }
+
   public QueryNode method(MethodReference method) {
     return getQueryNode(methods.get(method), method.toString());
   }