Add initial support for -addconfigurationdebugging

Bug: 73707846
Change-Id: Icc7b82e1fdb69ee7dae074f4e9f2618fd77b992c
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 036c874..458972d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -659,7 +659,13 @@
 
       // Perform minification.
       NamingLens namingLens;
-      if (options.getProguardConfiguration().hasApplyMappingFile()) {
+      if (options.configurationDebugging) {
+        if (options.getProguardConfiguration().hasApplyMappingFile() || options.isMinifying()) {
+          options.reporter.info(new StringDiagnostic(
+              "Build is not being obfuscated due to the use of -addconfigurationdebugging"));
+        }
+        namingLens = NamingLens.getIdentityLens();
+      } else if (options.getProguardConfiguration().hasApplyMappingFile()) {
         SeedMapper seedMapper =
             SeedMapper.seedMapperFromFile(
                 options.reporter, options.getProguardConfiguration().getApplyMappingFile());
@@ -697,7 +703,7 @@
         return;
       }
 
-      // Remove unneeeded visibility bridges that have been inserted for member rebinding.
+      // Remove unneeded visibility bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
       if (appView.appInfo().hasLiveness()) {
         ImmutableSet.Builder<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.builder();
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 6fede89..6dafe55 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,11 +10,17 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 
 import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.code.Const;
 import com.android.tools.r8.code.ConstString;
-import com.android.tools.r8.code.ConstStringJumbo;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeDirect;
 import com.android.tools.r8.code.InvokeStatic;
@@ -47,6 +53,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -54,8 +61,10 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
+  public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
 
   /**
    * Encodes the processing state of a method.
@@ -525,16 +534,11 @@
 
   /**
    * Generates a {@link DexCode} object for the given instructions.
-   * <p>
-   * As the code object is produced outside of the normal compilation cycle, it has to use {@link
-   * ConstStringJumbo} to reference string constants. Hence, code produced form these templates
-   * might incur a size overhead.
    */
   private DexCode generateCodeFromTemplate(
       int numberOfRegisters, int outRegisters, Instruction... instructions) {
     int offset = 0;
     for (Instruction instruction : instructions) {
-      assert !(instruction instanceof ConstString);
       instruction.setOffset(offset);
       offset += instruction.getSize();
     }
@@ -591,47 +595,93 @@
     return builder.build();
   }
 
-  public DexEncodedMethod toMethodThatLogsError(DexItemFactory itemFactory) {
+  public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
+    if (appView.options().isGeneratingDex()) {
+      return toMethodThatLogsErrorDexCode(appView.dexItemFactory());
+    } else {
+      return toMethodThatLogsErrorCfCode(appView.dexItemFactory());
+    }
+  }
+
+  private DexEncodedMethod toMethodThatLogsErrorDexCode(DexItemFactory itemFactory) {
     checkIfObsolete();
     Signature signature = MethodSignature.fromDexMethod(method);
-    // TODO(herhut): Construct this out of parts to enable reuse, maybe even using descriptors.
     DexString message = itemFactory.createString(
-        "Shaking error: Missing method in " + method.holder.toSourceString() + ": "
-            + signature);
-    DexString tag = itemFactory.createString("TOIGHTNESS");
+        CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
+    DexString tag = itemFactory.createString("[R8]");
     DexType[] args = {itemFactory.stringType, itemFactory.stringType};
     DexProto proto = itemFactory.createProto(itemFactory.intType, args);
-    DexMethod logMethod = itemFactory
-        .createMethod(itemFactory.createType("Landroid/util/Log;"), proto,
-            itemFactory.createString("e"));
+    DexMethod logMethod =
+        itemFactory.createMethod(
+            itemFactory.createType("Landroid/util/Log;"), proto, itemFactory.createString("e"));
     DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;");
-    DexMethod exceptionInitMethod = itemFactory
-        .createMethod(exceptionType, itemFactory.createProto(itemFactory.voidType,
-            itemFactory.stringType),
+    DexMethod exceptionInitMethod =
+        itemFactory.createMethod(
+            exceptionType,
+            itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
             itemFactory.constructorMethodName);
-    DexCode code;
-    if (isInstanceInitializer()) {
-      // The Java VM Spec requires that a constructor calls an initializer from the super class
-      // or another constructor from the current class. For simplicity we do the latter by just
-      // calling ourself. This is ok, as the constructor always throws before the recursive call.
-      code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag),
-          new ConstStringJumbo(1, message),
-          new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
-          new NewInstance(0, exceptionType),
-          new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
-          new Throw(0),
-          new InvokeDirect(1, method, 2, 0, 0, 0, 0));
+    DexCode code =
+        generateCodeFromTemplate(2, 2,
+            new ConstString(0, tag),
+            new ConstString(1, message),
+            new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
+            new NewInstance(0, exceptionType),
+            new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
+            new Throw(0));
+    Builder builder = builder(this);
+    builder.setCode(code);
+    setObsolete();
+    return builder.build();
+  }
 
-    } else {
-      // These methods might not get registered for jumbo string processing, therefore we always
-      // use the jumbo string encoding for the const string instruction.
-      code = generateCodeFromTemplate(2, 2, new ConstStringJumbo(0, tag),
-          new ConstStringJumbo(1, message),
-          new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
-          new NewInstance(0, exceptionType),
-          new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
-          new Throw(0));
+  private DexEncodedMethod toMethodThatLogsErrorCfCode(DexItemFactory itemFactory) {
+    checkIfObsolete();
+    Signature signature = MethodSignature.fromDexMethod(method);
+    DexString message = itemFactory.createString(
+        CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
+    DexString tag = itemFactory.createString("[R8]");
+    DexType logger = itemFactory.createType("Ljava/util/logging/Logger;");
+    DexMethod getLogger =
+        itemFactory.createMethod(
+            logger,
+            itemFactory.createProto(logger, itemFactory.stringType),
+            itemFactory.createString("getLogger"));
+    DexMethod severe =
+        itemFactory.createMethod(
+            logger,
+            itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
+            itemFactory.createString("severe"));
+    DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;");
+    DexMethod exceptionInitMethod =
+        itemFactory.createMethod(
+            exceptionType,
+            itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
+            itemFactory.constructorMethodName);
+    int locals = method.proto.parameters.size() + 1;
+    if (!isStaticMember()) {
+      // Consider `this` pointer
+      locals++;
     }
+    ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
+    instructionBuilder
+        .add(new CfConstString(tag))
+        .add(new CfInvoke(Opcodes.INVOKESTATIC, getLogger, false))
+        .add(new CfStore(ValueType.OBJECT, locals - 1))
+        .add(new CfLoad(ValueType.OBJECT, locals - 1))
+        .add(new CfConstString(message))
+        .add(new CfInvoke(Opcodes.INVOKEVIRTUAL, severe, false))
+        .add(new CfNew(exceptionType))
+        .add(new CfStackInstruction(Opcode.Dup))
+        .add(new CfConstString(message))
+        .add(new CfInvoke(Opcodes.INVOKESPECIAL, exceptionInitMethod, false))
+        .add(new CfThrow());
+    CfCode code = new CfCode(
+        method,
+        3,
+        locals,
+        instructionBuilder.build(),
+        Collections.emptyList(),
+        Collections.emptyList());
     Builder builder = builder(this);
     builder.setCode(code);
     setObsolete();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 360a11a..d46a976 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -68,6 +68,7 @@
     private boolean forceProguardCompatibility = false;
     private boolean overloadAggressively;
     private boolean keepRuleSynthesisForRecompilation = false;
+    private boolean configurationDebugging = false;
 
     private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
       this.dexItemFactory = dexItemFactory;
@@ -270,6 +271,10 @@
       this.keepRuleSynthesisForRecompilation = true;
     }
 
+    public void setConfigurationDebugging(boolean configurationDebugging) {
+      this.configurationDebugging = configurationDebugging;
+    }
+
     /**
      * This synthesizes a set of keep rules that are necessary in order to be able to successfully
      * recompile the generated dex files with the same keep rules.
@@ -326,7 +331,8 @@
               adaptClassStrings.build(),
               adaptResourceFilenames.build(),
               adaptResourceFileContents.build(),
-              keepDirectories.build());
+              keepDirectories.build(),
+              configurationDebugging);
 
       reporter.failIfPendingErrors();
 
@@ -393,6 +399,7 @@
   private final ProguardPathFilter adaptResourceFilenames;
   private final ProguardPathFilter adaptResourceFileContents;
   private final ProguardPathFilter keepDirectories;
+  private final boolean configurationDebugging;
 
   private ProguardConfiguration(
       String parsedConfiguration,
@@ -430,7 +437,8 @@
       ProguardClassFilter adaptClassStrings,
       ProguardPathFilter adaptResourceFilenames,
       ProguardPathFilter adaptResourceFileContents,
-      ProguardPathFilter keepDirectories) {
+      ProguardPathFilter keepDirectories,
+      boolean configurationDebugging) {
     this.parsedConfiguration = parsedConfiguration;
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
@@ -467,6 +475,7 @@
     this.adaptResourceFilenames = adaptResourceFilenames;
     this.adaptResourceFileContents = adaptResourceFileContents;
     this.keepDirectories = keepDirectories;
+    this.configurationDebugging = configurationDebugging;
   }
 
   /**
@@ -625,6 +634,10 @@
     return seedFile;
   }
 
+  public boolean isConfigurationDebugging() {
+    return configurationDebugging;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
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 15534bd..c816928 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -87,9 +87,7 @@
       "dump");
 
   private static final List<String> WARNED_FLAG_OPTIONS =
-      ImmutableList.of(
-          // TODO(b/73707846): add support -addconfigurationdebugging
-          "addconfigurationdebugging", "useuniqueclassmembernames");
+      ImmutableList.of("useuniqueclassmembernames");
 
   private static final List<String> WARNED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList.of(
       // TODO(b/73708157): add support -assumenoexternalsideeffects <class_spec>
@@ -398,6 +396,8 @@
         configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
       } else if (acceptString("if")) {
         configurationBuilder.addRule(parseIfRule(optionStart));
+      } else if (acceptString("addconfigurationdebugging")) {
+        configurationBuilder.setConfigurationDebugging(true);
       } else {
         String unknownOption = acceptString();
         String devMessage = "";
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index ba63b85..5b7b182 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -47,14 +46,6 @@
 
   public DexApplication run() {
     application.timing.begin("Pruning application...");
-    InternalOptions options = appView.options();
-    if (options.debugKeepRules && options.isShrinking()) {
-      options.reporter.info(
-          new StringDiagnostic(
-              "Debugging keep rules on a minified build might yield broken builds, as "
-                  + "minification also depends on the used keep rules. We recommend using "
-                  + "--skip-minification."));
-    }
     DexApplication result;
     try {
       result = removeUnused(application).appendDeadCode(usagePrinter.toStringContent()).build();
@@ -74,6 +65,11 @@
     InternalOptions options = appView.options();
     List<DexProgramClass> newClasses = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
+      if (options.configurationDebugging) {
+        newClasses.add(clazz);
+        pruneMembersAndAttributes(clazz);
+        continue;
+      }
       if (!appInfo.liveTypes.contains(clazz.type)) {
         // The class is completely unused and we can remove it.
         if (Log.ENABLED) {
@@ -84,8 +80,7 @@
       } else {
         newClasses.add(clazz);
         if (!appInfo.instantiatedTypes.contains(clazz.type)
-            && !options.forceProguardCompatibility
-            && (!options.debugKeepRules || !clazz.hasDefaultInitializer())) {
+            && !options.forceProguardCompatibility) {
           // The class is only needed as a type but never instantiated. Make it abstract to reflect
           // this.
           if (clazz.accessFlags.isFinal()) {
@@ -98,46 +93,49 @@
           }
           clazz.accessFlags.setAbstract();
         }
-        // The class is used and must be kept. Remove the unused fields and methods from
-        // the class.
-        usagePrinter.visiting(clazz);
-        DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
-        if (reachableDirectMethods != null) {
-          clazz.setDirectMethods(reachableDirectMethods);
-        }
-        DexEncodedMethod[] reachableVirtualMethods =
-            reachableMethods(clazz.virtualMethods(), clazz);
-        if (reachableVirtualMethods != null) {
-          clazz.setVirtualMethods(reachableVirtualMethods);
-        }
-        DexEncodedField[] reachableInstanceFields = reachableFields(clazz.instanceFields());
-        if (reachableInstanceFields != null) {
-          clazz.setInstanceFields(reachableInstanceFields);
-        }
-        DexEncodedField[] reachableStaticFields = reachableFields(clazz.staticFields());
-        if (reachableStaticFields != null) {
-          clazz.setStaticFields(reachableStaticFields);
-        }
-        // If the class is a local class, it'll become an ordinary class by renaming.
-        // Invalidate its inner-class / enclosing-method attributes early.
-        if (appView.options().isMinifying()
-            && appView.rootSet().mayBeMinified(clazz.type, appView)
-            && clazz.isLocalClass()) {
-          assert clazz.getEnclosingMethod() != null;
-          assert clazz.getInnerClassAttributeForThisClass() != null;
-          clazz.removeEnclosingMethod(Predicates.alwaysTrue());
-          InnerClassAttribute innerClassAttribute =
-              clazz.getInnerClassAttributeForThisClass();
-          clazz.removeInnerClasses(attr -> attr == innerClassAttribute);
-        }
-        clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
-        clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
-        usagePrinter.visited();
+        // The class is used and must be kept. Remove the unused fields and methods from the class.
+        pruneMembersAndAttributes(clazz);
       }
     }
     return newClasses;
   }
 
+  private void pruneMembersAndAttributes(DexProgramClass clazz) {
+    usagePrinter.visiting(clazz);
+    DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
+    if (reachableDirectMethods != null) {
+      clazz.setDirectMethods(reachableDirectMethods);
+    }
+    DexEncodedMethod[] reachableVirtualMethods =
+        reachableMethods(clazz.virtualMethods(), clazz);
+    if (reachableVirtualMethods != null) {
+      clazz.setVirtualMethods(reachableVirtualMethods);
+    }
+    DexEncodedField[] reachableInstanceFields = reachableFields(clazz.instanceFields());
+    if (reachableInstanceFields != null) {
+      clazz.setInstanceFields(reachableInstanceFields);
+    }
+    DexEncodedField[] reachableStaticFields = reachableFields(clazz.staticFields());
+    if (reachableStaticFields != null) {
+      clazz.setStaticFields(reachableStaticFields);
+    }
+    // If the class is a local class, it'll become an ordinary class by renaming.
+    // Invalidate its inner-class / enclosing-method attributes early.
+    if (appView.options().isMinifying()
+        && appView.rootSet().mayBeMinified(clazz.type, appView)
+        && clazz.isLocalClass()) {
+      assert clazz.getEnclosingMethod() != null;
+      assert clazz.getInnerClassAttributeForThisClass() != null;
+      clazz.removeEnclosingMethod(Predicates.alwaysTrue());
+      InnerClassAttribute innerClassAttribute =
+          clazz.getInnerClassAttributeForThisClass();
+      clazz.removeInnerClasses(attr -> attr == innerClassAttribute);
+    }
+    clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
+    clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
+    usagePrinter.visited();
+  }
+
   private boolean isAttributeReferencingPrunedItem(EnclosingMethodAttribute attr) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     return
@@ -166,11 +164,6 @@
     return -1;
   }
 
-  private boolean isDefaultConstructor(DexEncodedMethod method) {
-    return method.isInstanceInitializer()
-        && method.method.proto.parameters.isEmpty();
-  }
-
   private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     InternalOptions options = appView.options();
@@ -187,12 +180,12 @@
       DexEncodedMethod method = methods.get(i);
       if (appInfo.liveMethods.contains(method.getKey())) {
         reachableMethods.add(method);
-      } else if (options.debugKeepRules && isDefaultConstructor(method)) {
+      } else if (options.configurationDebugging) {
         // Keep the method but rewrite its body, if it has one.
         reachableMethods.add(
             method.shouldNotHaveCode() && !method.hasCode()
                 ? method
-                : method.toMethodThatLogsError(application.dexItemFactory));
+                : method.toMethodThatLogsError(appView));
       } else if (appInfo.targetedMethods.contains(method.getKey())) {
         // If the method is already abstract, and doesn't have code, let it be.
         if (method.shouldNotHaveCode() && !method.hasCode()) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c00f327..d0538c0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -103,6 +103,7 @@
     if (!proguardConfiguration.isOptimizing()) {
       disableAllOptimizations();
     }
+    configurationDebugging = proguardConfiguration.isConfigurationDebugging();
   }
 
   void disableAllOptimizations() {
@@ -303,7 +304,7 @@
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
   public boolean disableAssertions = true;
-  public boolean debugKeepRules = false;
+  public boolean configurationDebugging = false;
   // Read input classes into CfCode format (instead of JarCode).
   public boolean enableCfFrontend = false;
   // Don't convert Code objects to IRCode.
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index edd1492..2e1f053 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 63bed21..001e3201 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2032,8 +2032,8 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
-        "Ignoring", "-addconfigurationdebugging");
+    verifyParserEndsCleanly();
+    assertTrue(parser.getConfig().isConfigurationDebugging());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java
new file mode 100644
index 0000000..7cb0d57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java
@@ -0,0 +1,182 @@
+// 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.addconfigurationdebugging;
+
+import static com.android.tools.r8.graph.DexEncodedMethod.CONFIGURATION_DEBUGGING_PREFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class BaseClass {
+  Object field;
+  BaseClass(Object usedArg) {
+    field = usedArg;
+  }
+}
+
+class UninstantiatedClass extends BaseClass {
+  UninstantiatedClass() {
+    super(null);
+    System.out.println("UninstantiatedClass#<init>");
+  }
+
+  UninstantiatedClass(String arg) {
+    super(arg);
+    System.out.println("UninstantiatedClass#<init>(String)");
+  }
+}
+
+class TestClass {
+  BaseClass b;
+
+  TestClass() {
+    b = new BaseClass(this);
+    System.out.println(b);
+  }
+
+  void foo(int i, long l) {
+    System.out.println("void TestClass#foo(IJ)");
+  }
+
+  static void bar(TestClass arg) {
+    System.out.println("void TestClass#bar(TestClass)");
+  }
+}
+
+class Caller {
+  public static void main(String[] args) {
+    try {
+      new UninstantiatedClass();
+    } catch (RuntimeException e) {
+    }
+    try {
+      new UninstantiatedClass("aaarrrrrrhhhhhh");
+    } catch (RuntimeException e) {
+    }
+
+    TestClass instance = new TestClass();
+    try {
+      instance.foo(4, 2L);
+    } catch (RuntimeException e) {
+    }
+    try {
+      TestClass.bar(instance);
+    } catch (RuntimeException e) {
+    }
+
+    throw new RuntimeException("Reaching the end");
+  }
+}
+
+@RunWith(Parameterized.class)
+public class ConfigurationDebuggingTest extends TestBase {
+  private static final String PACKAGE_NAME =
+      ConfigurationDebuggingTest.class.getPackage().getName();
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public ConfigurationDebuggingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path firstRunArchive =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class)
+            .addKeepRules("-addconfigurationdebugging")
+            .addKeepRules("-keep class **.TestClass { <init>(); }")
+            .noMinification()
+            .setMinApi(parameters.getRuntime())
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addLibraryClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class)
+            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+            .addProgramClasses(Caller.class)
+            .addKeepMainRule(Caller.class)
+            .setMinApi(parameters.getRuntime());
+    R8TestRunResult result =
+        builder
+            .compile()
+            .addRunClasspathFiles(firstRunArchive)
+            .run(parameters.getRuntime(), Caller.class);
+    // TODO(b/117302947): Dex runtime should be able to find that framework class.
+    if (parameters.isDexRuntime()) {
+      result.assertFailureWithErrorThatMatches(containsString("NoClassDefFoundError"));
+      result.assertFailureWithErrorThatMatches(containsString("android.util.Log"));
+      return;
+    }
+    result
+        .assertFailureWithErrorThatMatches(
+            containsString(createExpectedMessage(UninstantiatedClass.class)))
+        .assertFailureWithErrorThatMatches(containsString("void <init>()"))
+        .assertFailureWithErrorThatMatches(containsString("void <init>(java.lang.String)"))
+        .assertFailureWithErrorThatMatches(
+            containsString(createExpectedMessage(TestClass.class)))
+        .assertFailureWithErrorThatMatches(containsString("void foo(int,long)"))
+        .assertFailureWithErrorThatMatches(
+            containsString("void bar(" + PACKAGE_NAME + ".TestClass" +")"))
+        .assertFailureWithErrorThatMatches(containsString("Reaching the end"));
+  }
+
+  private String createExpectedMessage(Class<?> clazz) {
+    return CONFIGURATION_DEBUGGING_PREFIX + clazz.getName();
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject baseClass = inspector.clazz(BaseClass.class);
+    assertThat(baseClass, isPresent());
+    baseClass.allMethods().forEach(methodSubject -> {
+      assertTrue(methodSubject.isInstanceInitializer());
+      assertFalse(hasThrow(methodSubject));
+    });
+
+    ClassSubject uninstantiatedClass = inspector.clazz(UninstantiatedClass.class);
+    assertThat(uninstantiatedClass, isPresent());
+    uninstantiatedClass.allMethods().forEach(methodSubject -> {
+      assertTrue(methodSubject.isInstanceInitializer());
+      assertTrue(hasThrow(methodSubject));
+    });
+
+    ClassSubject testClass = inspector.clazz(TestClass.class);
+    assertThat(testClass, isPresent());
+    MethodSubject foo = testClass.uniqueMethodWithName("foo");
+    assertThat(foo, isPresent());
+    assertTrue(hasThrow(foo));
+    MethodSubject bar = testClass.uniqueMethodWithName("bar");
+    assertThat(bar, isPresent());
+    assertTrue(hasThrow(bar));
+  }
+
+  private static boolean hasThrow(MethodSubject methodSubject) {
+    return methodSubject.iterateInstructions(InstructionSubject::isThrow).hasNext();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 0395177..20fda04 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -4,46 +4,70 @@
 
 package com.android.tools.r8.smali;
 
-import static org.junit.Assert.assertEquals;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class JumboStringTest extends SmaliTestBase {
+  private static Pair<StringBuilder, StringBuilder> builders;
+  private final TestParameters parameters;
 
-  @Test
-  public void test() throws Exception {
+  @BeforeClass
+  public static void createBuilders() {
     StringBuilder builder = new StringBuilder();
     StringBuilder expectedBuilder = new StringBuilder();
-    builder.append(StringUtils.lines("    new-instance         v0, Ljava/lang/StringBuilder;"));
-    builder.append(StringUtils.lines("    invoke-direct        { v0 }, Ljava/lang/StringBuilder;"
-        + "-><init>()V"));
+    builder.append("    new-instance         v0, Ljava/lang/StringBuilder;");
+    builder.append("    invoke-direct        { v0 }, Ljava/lang/StringBuilder;-><init>()V");
     for (int i = 0; i <= 0xffff + 2; i++) {
       String prefixed = StringUtils.zeroPrefix(i, 5);
       expectedBuilder.append(prefixed);
       expectedBuilder.append(StringUtils.lines(""));
-      builder.append(StringUtils.lines("  const-string         v1, \"" + prefixed + "\\n\""));
+      builder.append("  const-string         v1, \"" + prefixed + "\\n\"");
       builder.append(
-          StringUtils.lines("  invoke-virtual       { v0, v1 }, Ljava/lang/StringBuilder;"
-              + "->append(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
+          "  invoke-virtual       { v0, v1 }, Ljava/lang/StringBuilder;"
+              + "->append(Ljava/lang/String;)Ljava/lang/StringBuilder;");
     }
     builder.append(
-        StringUtils.lines("    invoke-virtual       { v0 }, Ljava/lang/StringBuilder;"
-            + "->toString()Ljava/lang/String;"));
+        "    invoke-virtual       { v0 }, Ljava/lang/StringBuilder;"
+            + "->toString()Ljava/lang/String;");
     builder.append(StringUtils.lines("    move-result-object   v0"));
-    builder.append(StringUtils.lines("    return-object               v0"));
+    builder.append(StringUtils.lines("    return-object        v0"));
+    builders = new Pair<>(builder, expectedBuilder);
+  }
 
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  public JumboStringTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
     SmaliBuilder smaliBuilder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
-    MethodSignature signature = smaliBuilder.addStaticMethod(
+    smaliBuilder.addStaticMethod(
         "java.lang.String",
         DEFAULT_METHOD_NAME,
         ImmutableList.of(),
         2,
-        builder.toString()
+        builders.getFirst().toString()
     );
 
     smaliBuilder.addMainMethod(
@@ -55,10 +79,67 @@
         "    return-void"
     );
 
-    AndroidApp originalApplication = buildApplication(smaliBuilder);
-    AndroidApp processedApplication = processApplication(originalApplication);
-    String result = runArt(processedApplication);
+    testForR8(parameters.getBackend())
+        .addProgramDexFileData(smaliBuilder.compile())
+        .addKeepMainRule(DEFAULT_CLASS_NAME)
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), DEFAULT_CLASS_NAME)
+        .assertSuccessWithOutput(builders.getSecond().toString())
+        .inspect(inspector -> {
+          ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
+          assertThat(main, isPresent());
+          MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
+          assertThat(method, isPresent());
+          assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+        });
+  }
 
-    assertEquals(expectedBuilder.toString(), result);
+  @Test
+  public void test_addconfigurationdebugging() throws Exception {
+    SmaliBuilder smaliBuilder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    smaliBuilder.addStaticMethod(
+        "java.lang.String",
+        DEFAULT_METHOD_NAME,
+        ImmutableList.of(),
+        2,
+        builders.getFirst().toString()
+    );
+
+    // Intentionally dead code, but will be kept due to -addconfigurationdebugging.
+    smaliBuilder.addStaticMethod(
+        "java.lang.String",
+        DEFAULT_METHOD_NAME + "2",
+        ImmutableList.of(),
+        2,
+        builders.getFirst().toString()
+    );
+
+    smaliBuilder.addMainMethod(
+        2,
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    invoke-static       {}, LTest;->method()Ljava/lang/String;",
+        "    move-result-object  v1",
+        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
+        "    return-void"
+    );
+
+    testForR8(parameters.getBackend())
+        .addProgramDexFileData(smaliBuilder.compile())
+        .addKeepMainRule(DEFAULT_CLASS_NAME)
+        .addKeepRules("-addconfigurationdebugging")
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), DEFAULT_CLASS_NAME)
+        .assertSuccessWithOutput(builders.getSecond().toString())
+        .inspect(inspector -> {
+          ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
+          assertThat(main, isPresent());
+          MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
+          assertThat(method, isPresent());
+          assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+          MethodSubject method2 = main.uniqueMethodWithName(DEFAULT_METHOD_NAME + "2");
+          assertThat(method2, isPresent());
+          assertTrue(method2.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+        });
   }
 }
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 1223b61..b9f72c4 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
@@ -143,6 +143,11 @@
   }
 
   @Override
+  public boolean isJumboString() {
+    return false;
+  }
+
+  @Override
   public String getConstString() {
     if (instruction instanceof CfConstString) {
       return ((CfConstString) instruction).getString().toSourceString();
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 c195dc0..466f68f 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
@@ -259,6 +259,11 @@
   }
 
   @Override
+  public boolean isJumboString() {
+    return instruction instanceof ConstStringJumbo;
+  }
+
+  @Override
   public String getConstString() {
     if (instruction instanceof ConstString) {
       return ((ConstString) instruction).BBBB.toSourceString();
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 43ef5d4..0c5a4a6 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
@@ -48,6 +48,8 @@
 
   boolean isConstString(String value, JumboStringMode jumboStringMode);
 
+  boolean isJumboString();
+
   String getConstString();
 
   boolean isConstClass();