Merge "Revert "Change location of kotlin r8 test resources for relocating""
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 035847d..b12e26f 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -115,7 +115,12 @@
 
     @Override
     public boolean registerInvokeSuper(DexMethod method) {
-      addMethod(method);
+      DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method, method.holder);
+      if (superTarget != null) {
+        addMethod(superTarget.method);
+      } else {
+        addMethod(method);
+      }
       return false;
     }
 
@@ -201,7 +206,7 @@
     private void registerMethod(DexEncodedMethod method) {
       DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method.method, method.method.holder);
       if (superTarget != null) {
-        registerInvokeSuper(superTarget.method);
+        addMethod(superTarget.method);
       }
       for (DexType type : method.method.proto.parameters.values) {
         registerTypeReference(type);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index c1664a6..3cf4664 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -418,7 +418,8 @@
         if (options.enableArgumentRemoval) {
           if (options.enableUnusedArgumentRemoval) {
             timing.begin("UnusedArgumentRemoval");
-            appView.setGraphLense(new UnusedArgumentsCollector(appViewWithLiveness).run());
+            appView.setGraphLense(
+                new UnusedArgumentsCollector(appViewWithLiveness).run(executorService));
             application = application.asDirect().rewrittenWithLense(appView.graphLense());
             timing.end();
             appViewWithLiveness.setAppInfo(
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/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 3b858b0..74e9c6a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -257,7 +257,8 @@
 
   private RewrittenPrototypeDescription getPrototypeChanges(
       DexEncodedMethod encodedMethod, Strategy strategy) {
-    if (ArgumentRemovalUtils.isPinned(encodedMethod, appView)) {
+    if (ArgumentRemovalUtils.isPinned(encodedMethod, appView)
+        || appView.appInfo().keepConstantArguments.contains(encodedMethod.method)) {
       return RewrittenPrototypeDescription.none();
     }
     return new RewrittenPrototypeDescription(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index e06cdbef..47b9289 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -20,11 +20,13 @@
 import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Streams;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashSet;
@@ -32,6 +34,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public class UnusedArgumentsCollector {
 
@@ -80,9 +84,12 @@
     this.appView = appView;
   }
 
-  public GraphLense run() {
-    // TODO(65810338): Do this is parallel.
-    appView.appInfo().classes().forEach(this::processClass);
+  public GraphLense run(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.awaitFutures(
+        Streams.stream(appView.appInfo().classes())
+            .map(this::runnableForClass)
+            .map(executorService::submit)
+            .iterator());
 
     if (!methodMapping.isEmpty()) {
       return new UnusedArgumentsGraphLense(
@@ -104,14 +111,22 @@
     private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
     private final Set<Wrapper<DexMethod>> usedSignatures = new HashSet<>();
 
-    private DexProto protoWithRemovedArguments(DexMethod method, RemovedArgumentsInfo unused) {
-      DexType[] parameters =
-          new DexType[method.proto.parameters.size() - unused.numberOfRemovedArguments()];
-      if (parameters.length > 0) {
+    private DexProto protoWithRemovedArguments(
+        DexEncodedMethod encodedMethod, RemovedArgumentsInfo unused) {
+      DexMethod method = encodedMethod.method;
+
+      int firstArgumentIndex = encodedMethod.isStatic() ? 0 : 1;
+      int numberOfParameters = method.proto.parameters.size() - unused.numberOfRemovedArguments();
+      if (!encodedMethod.isStatic() && unused.isArgumentRemoved(0)) {
+        numberOfParameters++;
+      }
+
+      DexType[] parameters = new DexType[numberOfParameters];
+      if (numberOfParameters > 0) {
         int newIndex = 0;
-        for (int j = 0; j < method.proto.parameters.size(); j++) {
-          if (!unused.isArgumentRemoved(j)) {
-            parameters[newIndex++] = method.proto.parameters.values[j];
+        for (int oldIndex = 0; oldIndex < method.proto.parameters.size(); oldIndex++) {
+          if (!unused.isArgumentRemoved(oldIndex + firstArgumentIndex)) {
+            parameters[newIndex++] = method.proto.parameters.values[oldIndex];
           }
         }
         assert newIndex == parameters.length;
@@ -128,19 +143,26 @@
     }
 
     DexEncodedMethod removeArguments(DexEncodedMethod method, RemovedArgumentsInfo unused) {
+      if (unused == null) {
+        return null;
+      }
+
       boolean removed = usedSignatures.remove(equivalence.wrap(method.method));
       assert removed;
-      DexProto newProto = protoWithRemovedArguments(method.method, unused);
+      DexProto newProto = protoWithRemovedArguments(method, unused);
       DexMethod newSignature;
       int count = 0;
       DexString newName = null;
       do {
-        newName =
-            newName == null
-                ? method.method.name
-                : appView
-                    .dexItemFactory()
-                    .createString(method.method.name.toSourceString() + count);
+        if (newName == null) {
+          newName = method.method.name;
+        } else if (method.method.name != appView.dexItemFactory().initMethodName) {
+          newName =
+              appView.dexItemFactory().createString(method.method.name.toSourceString() + count);
+        } else {
+          // Constructors must be named `<init>`.
+          return null;
+        }
         newSignature =
             appView.dexItemFactory().createMethod(method.method.holder, newProto, newName);
         count++;
@@ -150,6 +172,10 @@
     }
   }
 
+  private Runnable runnableForClass(DexProgramClass clazz) {
+    return () -> this.processClass(clazz);
+  }
+
   private void processClass(DexProgramClass clazz) {
     UsedSignatures signatures = new UsedSignatures();
     for (DexEncodedMethod method : clazz.methods()) {
@@ -158,17 +184,20 @@
     for (int i = 0; i < clazz.directMethods().length; i++) {
       DexEncodedMethod method = clazz.directMethods()[i];
       RemovedArgumentsInfo unused = collectUnusedArguments(method);
-      if (unused != null) {
-        DexEncodedMethod newMethod = signatures.removeArguments(method, unused);
+      DexEncodedMethod newMethod = signatures.removeArguments(method, unused);
+      if (newMethod != null) {
         clazz.directMethods()[i] = newMethod;
-        methodMapping.put(method.method, newMethod.method);
-        removedArguments.put(newMethod.method, unused);
+        synchronized (this) {
+          methodMapping.put(method.method, newMethod.method);
+          removedArguments.put(newMethod.method, unused);
+        }
       }
     }
   }
 
   private RemovedArgumentsInfo collectUnusedArguments(DexEncodedMethod method) {
-    if (ArgumentRemovalUtils.isPinned(method, appView)) {
+    if (ArgumentRemovalUtils.isPinned(method, appView)
+        || appView.appInfo().keepUnusedArguments.contains(method.method)) {
       return null;
     }
     // Only process JAR code.
@@ -178,21 +207,25 @@
     assert method.getCode().getOwner() == method;
     int argumentCount =
         method.method.proto.parameters.size() + (method.accessFlags.isStatic() ? 0 : 1);
-    // TODO(65810338): Implement for private and virtual as well.
-    if (!method.accessFlags.isStatic()) {
-      return null;
-    }
-    CollectUsedArguments collector = new CollectUsedArguments();
-    method.getCode().registerArgumentReferences(collector);
-    BitSet used = collector.getUsedArguments();
-    if (used.cardinality() < argumentCount) {
-      List<RemovedArgumentInfo> unused = new ArrayList<>();
-      for (int i = 0; i < argumentCount; i++) {
-        if (!used.get(i)) {
-          unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
-        }
+    // TODO(65810338): Implement for virtual methods as well.
+    if (method.accessFlags.isPrivate() || method.accessFlags.isStatic()) {
+      CollectUsedArguments collector = new CollectUsedArguments();
+      if (!method.accessFlags.isStatic()) {
+        // TODO(65810338): The receiver cannot be removed without transforming the method to being
+        // static.
+        collector.register(0);
       }
-      return new RemovedArgumentsInfo(unused);
+      method.getCode().registerArgumentReferences(collector);
+      BitSet used = collector.getUsedArguments();
+      if (used.cardinality() < argumentCount) {
+        List<RemovedArgumentInfo> unused = new ArrayList<>();
+        for (int i = 0; i < argumentCount; i++) {
+          if (!used.get(i)) {
+            unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
+          }
+        }
+        return new RemovedArgumentsInfo(unused);
+      }
     }
     return null;
   }
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/ConstantArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
new file mode 100644
index 0000000..6d6f424
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
@@ -0,0 +1,81 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ConstantArgumentRule extends ProguardConfigurationRule {
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ConstantArgumentRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public ConstantArgumentRule build() {
+      return new ConstantArgumentRule(
+          origin,
+          getPosition(),
+          source,
+          classAnnotation,
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          inheritanceAnnotation,
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  private ConstantArgumentRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotation,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotation,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "keepconstantarguments";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 95f077d..279d381 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1941,6 +1941,10 @@
      * All methods that *must* never be inlined due to a configuration directive (testing only).
      */
     public final Set<DexMethod> neverInline;
+    /** All methods that may not have any parameters with a constant value removed. */
+    public final Set<DexMethod> keepConstantArguments;
+    /** All methods that may not have any unused arguments removed. */
+    public final Set<DexMethod> keepUnusedArguments;
     /**
      * All types that *must* never be inlined due to a configuration directive (testing only).
      */
@@ -2015,6 +2019,8 @@
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
       this.forceInline = enqueuer.rootSet.forceInline;
       this.neverInline = enqueuer.rootSet.neverInline;
+      this.keepConstantArguments = enqueuer.rootSet.keepConstantArguments;
+      this.keepUnusedArguments = enqueuer.rootSet.keepUnusedArguments;
       this.neverClassInline = enqueuer.rootSet.neverClassInline;
       this.neverMerge = enqueuer.rootSet.neverMerge;
       this.identifierNameStrings = joinIdentifierNameStrings(
@@ -2062,6 +2068,8 @@
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = previous.forceInline;
       this.neverInline = previous.neverInline;
+      this.keepConstantArguments = previous.keepConstantArguments;
+      this.keepUnusedArguments = previous.keepUnusedArguments;
       this.neverClassInline = previous.neverClassInline;
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings = previous.identifierNameStrings;
@@ -2123,6 +2131,10 @@
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
       this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
+      this.keepConstantArguments =
+          lense.rewriteMethodsWithRenamedSignature(previous.keepConstantArguments);
+      this.keepUnusedArguments =
+          lense.rewriteMethodsWithRenamedSignature(previous.keepUnusedArguments);
       assert lense.assertDefinitionsNotModified(
           previous.neverMerge.stream()
               .map(this::definitionFor)
@@ -2178,6 +2190,8 @@
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = previous.forceInline;
       this.neverInline = previous.neverInline;
+      this.keepConstantArguments = previous.keepConstantArguments;
+      this.keepUnusedArguments = previous.keepUnusedArguments;
       this.neverClassInline = previous.neverClassInline;
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings = previous.identifierNameStrings;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8d2c1b9..47d3743 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -242,6 +242,12 @@
       } else if (acceptString("keepdirectories")) {
         configurationBuilder.enableKeepDirectories();
         parsePathFilter(configurationBuilder::addKeepDirectories);
+      } else if (allowTestOptions && acceptString("keepconstantarguments")) {
+        ConstantArgumentRule rule = parseConstantArgumentRule(optionStart);
+        configurationBuilder.addRule(rule);
+      } else if (allowTestOptions && acceptString("keepunusedarguments")) {
+        UnusedArgumentRule rule = parseUnusedArgumentRule(optionStart);
+        configurationBuilder.addRule(rule);
       } else if (acceptString("keep")) {
         ProguardKeepRule rule = parseKeepRule(optionStart);
         configurationBuilder.addRule(rule);
@@ -683,6 +689,28 @@
           "Expecting '-keep' option after '-if' option.", origin, getPosition(optionStart)));
     }
 
+    private ConstantArgumentRule parseConstantArgumentRule(Position start)
+        throws ProguardRuleParserException {
+      ConstantArgumentRule.Builder keepRuleBuilder =
+          ConstantArgumentRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
+      return keepRuleBuilder.build();
+    }
+
+    private UnusedArgumentRule parseUnusedArgumentRule(Position start)
+        throws ProguardRuleParserException {
+      UnusedArgumentRule.Builder keepRuleBuilder =
+          UnusedArgumentRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
+      return keepRuleBuilder.build();
+    }
+
     void verifyAndLinkBackReferences(Iterable<ProguardWildcard> wildcards) {
       List<Pattern> patterns = new ArrayList<>();
       boolean backReferenceStarted = false;
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/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 69d2ee1..2f7f54a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -72,6 +72,8 @@
   private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+  private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
+  private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
   private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
   private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking =
@@ -185,7 +187,9 @@
         if (allRulesSatisfied(memberKeepRules, clazz)) {
           markClass(clazz, rule);
         }
-      } else if (rule instanceof InlineRule) {
+      } else if (rule instanceof InlineRule
+          || rule instanceof ConstantArgumentRule
+          || rule instanceof UnusedArgumentRule) {
         markMatchingMethods(clazz, memberKeepRules, rule, null);
       } else if (rule instanceof ClassInlineRule) {
         if (allRulesSatisfied(memberKeepRules, clazz)) {
@@ -261,6 +265,8 @@
         alwaysInline,
         forceInline,
         neverInline,
+        keepParametersWithConstantValue,
+        keepUnusedArguments,
         neverClassInline,
         neverMerge,
         noSideEffects,
@@ -951,6 +957,14 @@
       } else if (item.isDexEncodedMethod()) {
         identifierNameStrings.add(item.asDexEncodedMethod().method);
       }
+    } else if (context instanceof ConstantArgumentRule) {
+      if (item.isDexEncodedMethod()) {
+        keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
+      }
+    } else if (context instanceof UnusedArgumentRule) {
+      if (item.isDexEncodedMethod()) {
+        keepUnusedArguments.add(item.asDexEncodedMethod().method);
+      }
     }
   }
 
@@ -965,6 +979,8 @@
     public final Set<DexMethod> alwaysInline;
     public final Set<DexMethod> forceInline;
     public final Set<DexMethod> neverInline;
+    public final Set<DexMethod> keepConstantArguments;
+    public final Set<DexMethod> keepUnusedArguments;
     public final Set<DexType> neverClassInline;
     public final Set<DexType> neverMerge;
     public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
@@ -983,6 +999,8 @@
         Set<DexMethod> alwaysInline,
         Set<DexMethod> forceInline,
         Set<DexMethod> neverInline,
+        Set<DexMethod> keepConstantArguments,
+        Set<DexMethod> keepUnusedArguments,
         Set<DexType> neverClassInline,
         Set<DexType> neverMerge,
         Map<DexDefinition, ProguardMemberRule> noSideEffects,
@@ -999,6 +1017,8 @@
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
       this.forceInline = Collections.unmodifiableSet(forceInline);
       this.neverInline = neverInline;
+      this.keepConstantArguments = keepConstantArguments;
+      this.keepUnusedArguments = keepUnusedArguments;
       this.neverClassInline = neverClassInline;
       this.neverMerge = Collections.unmodifiableSet(neverMerge);
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
diff --git a/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
new file mode 100644
index 0000000..1098bcd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
@@ -0,0 +1,81 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class UnusedArgumentRule extends ProguardConfigurationRule {
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<UnusedArgumentRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public UnusedArgumentRule build() {
+      return new UnusedArgumentRule(
+          origin,
+          getPosition(),
+          source,
+          classAnnotation,
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          inheritanceAnnotation,
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  private UnusedArgumentRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotation,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotation,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "keepunusedarguments";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 40143af..91bb786 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -146,12 +146,6 @@
     ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : application.classes()) {
-
-      // TODO(tamaskenez) fix b/69356670 and remove the conditional skipping.
-      if (!clazz.getSynthesizedFrom().isEmpty()) {
-        continue;
-      }
-
       IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
           groupMethodsByRenamedName(namingLens, clazz);
 
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 1586cd4..bde7016 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -15,10 +15,14 @@
 
   public static void awaitFutures(Iterable<? extends Future<?>> futures)
       throws ExecutionException {
-    Iterator<? extends Future<?>> it = futures.iterator();
+    awaitFutures(futures.iterator());
+  }
+
+  public static void awaitFutures(Iterator<? extends Future<?>> futureIterator)
+      throws ExecutionException {
     try {
-      while (it.hasNext()) {
-        it.next().get();
+      while (futureIterator.hasNext()) {
+        futureIterator.next().get();
       }
     } catch (InterruptedException e) {
       throw new RuntimeException("Interrupted while waiting for future.", e);
@@ -26,9 +30,9 @@
       // In case we get interrupted or one of the threads throws an exception, still wait for all
       // further work to make sure synchronization guarantees are met. Calling cancel unfortunately
       // does not guarantee that the task at hand actually terminates before cancel returns.
-      while (it.hasNext()) {
+      while (futureIterator.hasNext()) {
         try {
-          it.next().get();
+          futureIterator.next().get();
         } catch (Throwable t) {
           // Ignore any new Exception.
         }
diff --git a/src/test/java/com/android/tools/r8/KeepConstantArguments.java b/src/test/java/com/android/tools/r8/KeepConstantArguments.java
new file mode 100644
index 0000000..87edb4f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KeepConstantArguments.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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface KeepConstantArguments {}
diff --git a/src/test/java/com/android/tools/r8/KeepUnusedArguments.java b/src/test/java/com/android/tools/r8/KeepUnusedArguments.java
new file mode 100644
index 0000000..9a5e3e9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KeepUnusedArguments.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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface KeepUnusedArguments {}
diff --git a/src/test/java/com/android/tools/r8/NeverInline.java b/src/test/java/com/android/tools/r8/NeverInline.java
index cf92f18f..082d446 100644
--- a/src/test/java/com/android/tools/r8/NeverInline.java
+++ b/src/test/java/com/android/tools/r8/NeverInline.java
@@ -3,5 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-public @interface NeverInline {
-}
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 475f764..1c39971 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -39,7 +39,10 @@
   private boolean enableInliningAnnotations = false;
   private boolean enableClassInliningAnnotations = false;
   private boolean enableMergeAnnotations = false;
+  private boolean enableConstantArgumentAnnotations = false;
+  private boolean enableUnusedArgumentAnnotations = false;
   private CollectingGraphConsumer graphConsumer = null;
+  private List<String> keepRules = new ArrayList<>();
 
   @Override
   R8TestBuilder self() {
@@ -50,9 +53,16 @@
   R8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
-    if (enableInliningAnnotations || enableClassInliningAnnotations || enableMergeAnnotations) {
+    if (enableInliningAnnotations
+        || enableClassInliningAnnotations
+        || enableMergeAnnotations
+        || enableConstantArgumentAnnotations
+        || enableUnusedArgumentAnnotations) {
       ToolHelper.allowTestProguardOptions(builder);
     }
+    if (!keepRules.isEmpty()) {
+      builder.addProguardConfiguration(keepRules, Origin.unknown());
+    }
     StringBuilder proguardMapBuilder = new StringBuilder();
     builder.setDisableTreeShaking(!enableTreeShaking);
     builder.setDisableMinification(!enableMinification);
@@ -75,7 +85,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 +116,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 +126,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 +134,25 @@
   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();
+  }
+
+  public R8TestBuilder enableConstantArgumentAnnotations() {
+    if (!enableConstantArgumentAnnotations) {
+      enableConstantArgumentAnnotations = true;
+      addInternalKeepRules(
+          "-keepconstantarguments class * { @com.android.tools.r8.KeepConstantArguments *; }");
+    }
+    return self();
+  }
+
+  public R8TestBuilder enableUnusedArgumentAnnotations() {
+    if (!enableUnusedArgumentAnnotations) {
+      enableUnusedArgumentAnnotations = true;
+      addInternalKeepRules(
+          "-keepunusedarguments class * { @com.android.tools.r8.KeepUnusedArguments *; }");
     }
     return self();
   }
@@ -150,4 +179,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/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index c750ded..5a17455 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -24,6 +24,7 @@
 import org.hamcrest.Matcher;
 
 public abstract class TestCompileResult<RR extends TestRunResult> {
+
   final TestState state;
   public final AndroidApp app;
 
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/classinliner/ClassInliningOracleTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
index 6982656..cb02879 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.KeepUnusedArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
@@ -26,6 +27,7 @@
             .enableInliningAnnotations()
             .enableClassInliningAnnotations()
             .enableMergeAnnotations()
+            .enableUnusedArgumentAnnotations()
             .compile()
             .inspector();
     assertThat(inspector.clazz(Builder.class), isPresent());
@@ -55,12 +57,10 @@
   @NeverClassInline
   static class Helper extends HelperBase {
 
+    @KeepUnusedArguments
     @NeverInline
     public void help(Builder builder) {
-      // TODO(b/120959040): To avoid unused argument removal; should be replaced by a testing rule).
-      if (builder != null) {
-        super.hello();
-      }
+      super.hello();
     }
   }
 
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/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
index 10bf5df..2f7e437 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableList;
@@ -40,6 +41,7 @@
     }
   }
 
+  @NeverClassInline
   static class TestClass {
 
     @NeverInline
@@ -57,10 +59,29 @@
       return a;
     }
 
+    @NeverInline
+    private Object b(Object a) {
+      return a;
+    }
+
+    @NeverInline
+    private Object b(Object a, Object b) {
+      return a;
+    }
+
+    @NeverInline
+    private Object b(Object a, Object b, Object c) {
+      return a;
+    }
+
     public static void main(String[] args) {
+      TestClass instance = new TestClass();
       System.out.print(a(new TestObject("1")));
       System.out.print(a(new TestObject("2"), new TestObject("3")));
       System.out.print(a(new TestObject("4"), new TestObject("5"), new TestObject("6")));
+      System.out.print(instance.b(new TestObject("1")));
+      System.out.print(instance.b(new TestObject("2"), new TestObject("3")));
+      System.out.print(instance.b(new TestObject("4"), new TestObject("5"), new TestObject("6")));
     }
   }
 
@@ -76,18 +97,18 @@
 
   @Override
   public String getExpectedResult() {
-    return "124";
+    return "124124";
   }
 
   @Override
   public void inspectTestClass(ClassSubject clazz) {
-    assertEquals(4, clazz.allMethods().size());
+    assertEquals(8, clazz.allMethods().size());
     clazz.forAllMethods(
-        method -> {
-          Assert.assertTrue(
-              method.getFinalName().equals("main")
-                  || (method.getFinalSignature().parameters.length == 1
-                      && method.getFinalSignature().parameters[0].equals("java.lang.Object")));
-        });
+        method ->
+            Assert.assertTrue(
+                method.getFinalName().equals("main")
+                    || method.getFinalName().equals("<init>")
+                    || (method.getFinalSignature().parameters.length == 1
+                        && method.getFinalSignature().parameters[0].equals("java.lang.Object"))));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
index d20de91..0a5d8e9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
@@ -55,8 +55,9 @@
         .addProgramClasses(getTestClass())
         .addProgramClasses(getAdditionalClasses())
         .addKeepMainRule(getTestClass())
-        .addKeepRules(minification ? "" : "-dontobfuscate")
+        .minification(minification)
         .enableInliningAnnotations()
+        .enableClassInliningAnnotations()
         .compile()
         .run(getTestClass())
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
index 44bd3db..314acdc 100644
--- a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
@@ -9,17 +9,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.StringConsumer.FileConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import java.io.File;
 import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -40,18 +37,6 @@
     JasminBuilder appBuilder = new JasminBuilder();
     ClassBuilder classBuilder = appBuilder.addClass("package.TestClass");
 
-    // TODO(b/120959040): Use a testing rule to disable relevant optimizations instead.
-    // Add a main method that instantiates A and B to make sure that the return type of the methods
-    // below are not changed to void by the uninstantiated type optimization.
-    classBuilder.addMainMethod(
-        ".limit stack 1",
-        ".limit locals 1",
-        "new package/A",
-        "invokespecial package/A/<init>()V",
-        "new package/B",
-        "invokespecial package/B/<init>()V",
-        "return");
-
     classBuilder.addVirtualMethod("method1", "Ljava/lang/Object;", returnNullByteCode);
     classBuilder.addVirtualMethod("method1", "Lpackage/A;", returnNullByteCode);
     classBuilder.addVirtualMethod("method1", "Lpackage/B;", returnNullByteCode);
@@ -70,17 +55,18 @@
     classBuilder = appBuilder.addClass("package.B");
     classBuilder.addDefaultConstructor();
 
-    Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
-    AndroidApp output =
-        compileWithR8(
-            appBuilder.build(),
-            keepMainProguardConfiguration("package.TestClass"),
-            options -> {
-              options.enableTreeShaking = false;
-              options.proguardMapConsumer = new FileConsumer(proguardMapPath);
-            });
+    Path inputJar = writeToJar(appBuilder);
 
-    CodeInspector inspector = new CodeInspector(output, proguardMapPath);
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addProgramFiles(inputJar)
+            .addKeepMainRule("package.TestClass")
+            .addKeepRules("-keepconstantarguments class * { *; }")
+            .enableConstantArgumentAnnotations()
+            .treeShaking(false)
+            .compile()
+            .inspector();
+
     ClassSubject clazz = inspector.clazz("package.TestClass");
     assertThat(clazz, isPresent());
 
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
new file mode 100644
index 0000000..fa0a393
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -0,0 +1,100 @@
+// 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.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileName;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.google.common.collect.ImmutableList;
+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 DesugarLambdaRetraceTest extends RetraceTestBase {
+
+  @Parameters(name = "Backend: {0}, mode: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(Backend.values(), CompilationMode.values());
+  }
+
+  public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode) {
+    super(backend, mode);
+  }
+
+  @Override
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(getMainClass(), ConsumerDesugarLambdaRetraceTest.class);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return MainDesugarLambdaRetraceTest.class;
+  }
+
+  private int expectedActualStackTraceHeight() {
+    return mode == CompilationMode.RELEASE ? 2 : (backend == Backend.CF ? 4 : 5);
+  }
+
+  private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
+    return line.className.contains("-$$Lambda$");
+  }
+
+  @Test
+  public void testSourceFileAndLineNumberTable() throws Exception {
+    runTest(
+        "-keepattributes SourceFile,LineNumberTable\n-printmapping",
+        (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
+          // Even when SourceFile is present retrace replaces the file name in the stack trace.
+          if (backend == Backend.CF) {
+            assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
+          } else {
+            // With the frame from the lambda class filtered out the stack trace is teh same.
+            assertThat(
+                retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
+                isSameExceptForFileName(expectedStackTrace));
+            // Check the frame from the lambda class.
+            StackTrace lambdaFrames = retracedStackTrace.filter(this::isSynthesizedLambdaFrame);
+            assertEquals(1, lambdaFrames.size());
+            assertEquals(mode == CompilationMode.RELEASE ? 0 : 2, lambdaFrames.get(0).lineNumber);
+            // Proguard retrace will take the class name until the first $ to construct the file
+            // name, so for "-$$Lambda$...", the file name becomes "-.java".
+            assertEquals("-.java", lambdaFrames.get(0).fileName);
+          }
+          assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+        });
+  }
+}
+
+// Custom consumer functional interface, as java.util.function.Consumer is not present on Android.
+@FunctionalInterface
+interface ConsumerDesugarLambdaRetraceTest<T> {
+  void accept(T value);
+}
+
+class MainDesugarLambdaRetraceTest {
+
+  public static void method2(long j) {
+    System.out.println("In method2");
+    if (j == 1) throw null;
+  }
+
+  public static void method1(String s, ConsumerDesugarLambdaRetraceTest<String> consumer) {
+    System.out.println("In method1");
+    for (int i = 0; i < 10; i++) {
+      consumer.accept(s);
+    }
+  }
+
+  public static void main(String[] args) {
+    System.out.println("In main");
+    method1("1", s -> method2(Long.parseLong(s)));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
index 0d0c21b..6243e26 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
 import java.util.function.BiConsumer;
 import org.junit.Before;
 
@@ -21,6 +23,10 @@
 
   public StackTrace expectedStackTrace;
 
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(getMainClass());
+  }
+
   public abstract Class<?> getMainClass();
 
   @Before
@@ -42,7 +48,7 @@
             .setMode(mode)
             .enableProguardTestOptions()
             .enableInliningAnnotations()
-            .addProgramClasses(getMainClass())
+            .addProgramClasses(getClasses())
             .addKeepMainRule(getMainClass())
             .addKeepRules(keepRule)
             .run(getMainClass())
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 3270e48..13115f4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -18,6 +18,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -183,6 +184,10 @@
     return StackTrace.extractFromJvm(ToolHelper.runRetrace(mapFile, stackTraceFile));
   }
 
+  public StackTrace filter(Predicate<StackTraceLine> filter) {
+    return new StackTrace(stackTraceLines.stream().filter(filter).collect(Collectors.toList()));
+  }
+
   @Override
   public int hashCode() {
     return stackTraceLines.hashCode();
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());
   }