Add D8 and R8 API to control assertion code at compile time

Second step:
  * Support setting ENABLE, DISABLE or FULL for pacakge and class as well
  * Tests for inner classes when enabling for class on the JVM.

Bug: 139898386
Change-Id: Iead67e7f5bf8511ff27561b1bb10e1b0a1a6da60
diff --git a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
index f77bbce..15acd6f 100644
--- a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
+++ b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
@@ -4,12 +4,14 @@
 
 package com.android.tools.r8;
 
-import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
+import java.util.List;
 
 @Keep
 public class AssertionsConfiguration {
 
   /** The possible transformations of the javac generated assertion code during compilation. */
+  @Keep
   public enum AssertionTransformation {
     /** Unconditionally enable the javac generated assertion code. */
     ENABLE,
@@ -22,14 +24,55 @@
     PASSTHROUGH
   }
 
-  private AssertionTransformation transformation;
-
-  private AssertionsConfiguration(AssertionTransformation transformation) {
-    this.transformation = transformation;
+  public enum ConfigurationType {
+    ALL,
+    PACKAGE,
+    CLASS
   }
 
-  static Builder builder(DiagnosticsHandler handler) {
-    return new Builder(handler);
+  public static class ConfigurationEntry {
+    private final AssertionTransformation transformation;
+    private final ConfigurationType type;
+    private final String value;
+
+    private ConfigurationEntry(
+        AssertionTransformation transformation, ConfigurationType type, String value) {
+      assert value != null || type == ConfigurationType.ALL;
+      this.transformation = transformation;
+      this.type = type;
+      this.value = value;
+    }
+
+    public AssertionTransformation getTransformation() {
+      return transformation;
+    }
+
+    public ConfigurationType getType() {
+      return type;
+    }
+
+    public String getValue() {
+      return value;
+    }
+  }
+
+  // Methods which need to be public.
+  public static class InternalAssertionConfiguration {
+
+    public static List<ConfigurationEntry> getConfiguration(AssertionsConfiguration configuration) {
+      return configuration.entries;
+    }
+  }
+
+  private final List<ConfigurationEntry> entries;
+
+  private AssertionsConfiguration(List<ConfigurationEntry> entries) {
+    this.entries = entries;
+  }
+
+  static AssertionsConfiguration.Builder builder(AssertionsConfiguration previous) {
+    return new AssertionsConfiguration.Builder(
+        previous != null ? previous.entries : new ArrayList<>());
   }
 
   /**
@@ -38,24 +81,35 @@
    * <p>A builder is obtained by calling {@link
    * BaseCompilerCommand.Builder#addAssertionsConfiguration}.
    */
-  public AssertionTransformation getTransformation() {
-    return transformation;
-  }
-
   @Keep
   public static class Builder {
-    private AssertionTransformation transformation = null;
+    private final List<ConfigurationEntry> entries;
 
-    private final DiagnosticsHandler handler;
+    private Builder(List<ConfigurationEntry> previousEntries) {
+      assert previousEntries != null;
+      this.entries = previousEntries;
+    }
 
-    private Builder(DiagnosticsHandler handler) {
-      this.handler = handler;
+    private void addEntry(
+        AssertionTransformation transformation, ConfigurationType type, String value) {
+      entries.add(new ConfigurationEntry(transformation, type, value));
     }
 
     /** Set how to handle javac generated assertion code. */
     public AssertionsConfiguration.Builder setTransformation(
         AssertionTransformation transformation) {
-      this.transformation = transformation;
+      addEntry(transformation, ConfigurationType.ALL, null);
+      return this;
+    }
+
+    AssertionsConfiguration.Builder setDefault(AssertionTransformation transformation) {
+      // Add the default by inserting a transform all entry at the beginning of the list, if there
+      // isn't already one.
+      ConfigurationEntry defaultEntry =
+          new ConfigurationEntry(transformation, ConfigurationType.ALL, null);
+      if (entries.size() == 0 || entries.get(0).type != ConfigurationType.ALL) {
+        entries.listIterator().add(defaultEntry);
+      }
       return this;
     }
 
@@ -86,7 +140,7 @@
     /** Set how to handle javac generated assertion code in package and all subpackages. */
     public AssertionsConfiguration.Builder setTransformationForPackage(
         String packageName, AssertionTransformation transformation) {
-      handler.error(new StringDiagnostic("Unsupported"));
+      addEntry(transformation, ConfigurationType.PACKAGE, packageName);
       return this;
     }
 
@@ -123,7 +177,7 @@
     /** Set how to handle javac generated assertion code in class. */
     public AssertionsConfiguration.Builder setTransformationForClass(
         String className, AssertionTransformation transformation) {
-      handler.error(new StringDiagnostic("Unsupported"));
+      addEntry(transformation, ConfigurationType.CLASS, className);
       return this;
     }
 
@@ -151,7 +205,7 @@
 
     /** Build and return the {@link AssertionsConfiguration}. */
     public AssertionsConfiguration build() {
-      return new AssertionsConfiguration(transformation);
+      return new AssertionsConfiguration(entries);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index ce60661..2b94a54 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -134,8 +135,11 @@
     return optimizeMultidexForLinearAlloc;
   }
 
-  public AssertionsConfiguration getAssertionsConfiguration() {
-    return assertionsConfiguration;
+  AssertionsConfiguration getAssertionsConfiguration(
+      AssertionTransformation defaultTransformation) {
+    return AssertionsConfiguration.builder(assertionsConfiguration)
+        .setDefault(defaultTransformation)
+        .build();
   }
 
   Reporter getReporter() {
@@ -491,7 +495,8 @@
         Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
             assertionsConfigurationGenerator) {
       assertionsConfiguration =
-          assertionsConfigurationGenerator.apply(AssertionsConfiguration.builder(getReporter()));
+          assertionsConfigurationGenerator.apply(
+              AssertionsConfiguration.builder(assertionsConfiguration));
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2214e2d..d3ec378 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.D8Command.USAGE_MESSAGE;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -19,6 +18,7 @@
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
@@ -159,7 +159,7 @@
 
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
-      if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+      if (!AssertionsRewriter.isPassthroughAll(options.assertionsConfiguration)) {
         // Run analysis to mark all <clinit> methods having the javac generated assertion
         // enabling code.
         ClassInitializerAssertionEnablingAnalysis analysis =
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 6bdc35c..2401aa5 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -361,15 +361,10 @@
     internal.desugaredLibraryConfiguration = libraryConfiguration;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
-    assert internal.assertionTransformation == null;
-    if (getAssertionsConfiguration() != null) {
-      internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
-    }
-    if (internal.assertionTransformation == null) {
-      // Default, when no configuration is provided, is to disable all javac generated assertion
-      // code when generating dex.
-      internal.assertionTransformation = AssertionTransformation.DISABLE;
-    }
+    assert internal.assertionsConfiguration == null;
+    // Default, when no configuration is provided, is to remove all javac generated assertion
+    // code when generating dex.
+    internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index ff64d62..1346623 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -141,6 +142,11 @@
     // TODO(134732760): This is still work in progress.
     internal.desugaredLibraryConfiguration = libraryConfiguration;
 
+    assert internal.assertionsConfiguration == null;
+    // Default, when no configuration is provided, is to remove all javac generated assertion
+    // code when generating dex.
+    internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
+
     return internal;
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 03235a4..8cacb12 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.R8Command.USAGE_MESSAGE;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -33,6 +32,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.SourceDebugExtensionRewriter;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -813,7 +813,7 @@
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
     }
-    if (appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+    if (!AssertionsRewriter.isPassthroughAll(appView.options().assertionsConfiguration)) {
       enqueuer.registerAnalysis(
           new ClassInitializerAssertionEnablingAnalysis(
               appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 45f9e84..265fa38 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -870,19 +870,14 @@
 
     internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
 
-    // Default is to remove Java assertion code as Dalvik and Art does not reliable support
-    // Java assertions. When generation class file output always keep the Java assertions code.
-    assert internal.assertionTransformation == null;
-    if (getAssertionsConfiguration() == null) {
-      // Default, when no configuration is provided, is to disable all javac generated assertion
-      // code when generating dex and leave it when generating class files.
-      internal.assertionTransformation =
-          internal.isGeneratingClassFiles()
-              ? AssertionTransformation.PASSTHROUGH
-              : AssertionTransformation.DISABLE;
-    } else {
-      internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
-    }
+    assert internal.assertionsConfiguration == null;
+    // Default, when no configuration is provided, is to remove all javac generated assertion
+    // code when generating dex and leave it when generating class files.
+    internal.assertionsConfiguration =
+        getAssertionsConfiguration(
+            internal.isGeneratingClassFiles()
+                ? AssertionTransformation.PASSTHROUGH
+                : AssertionTransformation.DISABLE);
 
     // When generating class files the build is "intermediate" and we cannot pollute the namespace
     // with the a hard-coded outline class. Doing so would prohibit subsequent merging of two
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 5d85611..ab96c0d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -119,6 +119,7 @@
   public final DexString longDescriptor = createString("J");
   public final DexString shortDescriptor = createString("S");
   public final DexString voidDescriptor = createString("V");
+  public final DexString descriptorSeparator = createString("/");
 
   public final DexString boxedBooleanDescriptor = createString("Ljava/lang/Boolean;");
   public final DexString boxedByteDescriptor = createString("Ljava/lang/Byte;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 131cee6..f3a829c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -442,6 +442,22 @@
     return true;
   }
 
+  public boolean contains(DexString s) {
+    // TODO(b/146621590): This does not handle character boundaries correctly.
+    int index = 0;
+    while (content.length - index >= s.content.length) {
+      int i = 0;
+      while (i < s.content.length - 1 && content[index + i] == s.content[i]) {
+        i++;
+      }
+      if (i == s.content.length - 1) {
+        return true;
+      }
+      index++;
+    }
+    return false;
+  }
+
   public boolean endsWith(DexString suffix) {
     if (content.length < suffix.content.length) {
       return false;
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 e28aa62..00d2f79 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
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -49,6 +48,7 @@
 import com.android.tools.r8.ir.desugar.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.AliasIntroducer;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Assumer;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -160,6 +160,7 @@
   public final Collection<Assumer> assumers = new ArrayList<>();
   private final DynamicTypeOptimization dynamicTypeOptimization;
 
+  final AssertionsRewriter assertionsRewriter;
   final DeadCodeRemover deadCodeRemover;
 
   private final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
@@ -198,6 +199,7 @@
     this.stringOptimizer = new StringOptimizer(appView);
     this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
     this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
+    this.assertionsRewriter = new AssertionsRewriter(appView);
     this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
     this.neverMergePrefixes =
         options.neverMergePrefixes.stream()
@@ -1172,13 +1174,13 @@
     if (memberValuePropagation != null) {
       memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
     }
+
     if (options.enableEnumValueOptimization) {
       assert appView.enableWholeProgramOptimizations();
       codeRewriter.removeSwitchMaps(code);
     }
-    if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
-      codeRewriter.processAssertions(appView, method, code, feedback);
-    }
+
+    assertionsRewriter.run(method, code);
 
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
new file mode 100644
index 0000000..2292b0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -0,0 +1,268 @@
+// 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;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationEntry;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationType;
+import com.android.tools.r8.AssertionsConfiguration.InternalAssertionConfiguration;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ThrowingCharIterator;
+import java.io.UTFDataFormatException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class AssertionsRewriter {
+
+  private static class ConfigurationEntryWithDexString {
+
+    private ConfigurationEntry entry;
+    private final DexString value;
+
+    private ConfigurationEntryWithDexString(
+        ConfigurationEntry entry, DexItemFactory dexItemFactory) {
+      this.entry = entry;
+      switch (entry.getType()) {
+        case PACKAGE:
+          if (entry.getValue().length() == 0) {
+            value = dexItemFactory.createString("");
+          } else {
+            value =
+                dexItemFactory.createString(
+                    "L"
+                        + entry
+                            .getValue()
+                            .replace(
+                                DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+                                DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+                        + "/");
+          }
+          break;
+        case CLASS:
+          value =
+              dexItemFactory.createString(
+                  "L"
+                      + entry
+                          .getValue()
+                          .replace(
+                              DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+                              DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+                      + ";");
+          break;
+        case ALL:
+          value = null;
+          break;
+        default:
+          throw new Unreachable();
+      }
+    }
+  }
+
+  private final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+  private final List<ConfigurationEntryWithDexString> configuration;
+  private final boolean enabled;
+
+  public AssertionsRewriter(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+    if (appView.options().assertionsConfiguration == null) {
+      this.configuration = null;
+      this.enabled = false;
+    } else {
+      List<ConfigurationEntry> configuration =
+          InternalAssertionConfiguration
+              .getConfiguration(appView.options().assertionsConfiguration);
+      this.configuration =
+          configuration.stream()
+              .map(entry -> new ConfigurationEntryWithDexString(entry, appView.dexItemFactory()))
+              .collect(Collectors.toList());
+      this.enabled = !isPassthroughAll(appView.options().assertionsConfiguration);
+    }
+  }
+
+  public static boolean isPassthroughAll(AssertionsConfiguration assertionsConfiguration) {
+    List<ConfigurationEntry> configuration =
+        InternalAssertionConfiguration.getConfiguration(assertionsConfiguration);
+    return configuration.size() == 1
+        && configuration.get(0).getTransformation() == AssertionTransformation.PASSTHROUGH
+        && configuration.get(0).getType() == ConfigurationType.ALL;
+  }
+
+  private AssertionTransformation getTransformationForMethod(DexEncodedMethod method) {
+    AssertionTransformation transformation = null;
+    for (ConfigurationEntryWithDexString entry : configuration) {
+      switch (entry.entry.getType()) {
+        case ALL:
+          transformation = entry.entry.getTransformation();
+          break;
+        case PACKAGE:
+          if (entry.value.size == 0) {
+            if (!method.method.holder.descriptor.contains(dexItemFactory.descriptorSeparator)) {
+              transformation = entry.entry.getTransformation();
+            }
+          } else if (method.method.holder.descriptor.startsWith(entry.value)) {
+            transformation = entry.entry.getTransformation();
+          }
+          break;
+        case CLASS:
+          if (method.method.holder.descriptor.equals(entry.value)) {
+            transformation = entry.entry.getTransformation();
+          }
+          if (isDescriptorForClassOrInnerClass(entry.value, method.method.holder.descriptor)) {
+            transformation = entry.entry.getTransformation();
+          }
+          break;
+        default:
+          throw new Unreachable();
+      }
+    }
+    assert transformation != null; // Default transformation are always added.
+    return transformation;
+  }
+
+  private boolean isDescriptorForClassOrInnerClass(
+      DexString classDescriptor, DexString classOrInnerClassDescriptor) {
+    // Same string same class.
+    if (classOrInnerClassDescriptor == classDescriptor) {
+      return true;
+    }
+
+    // Check for inner class name by checking if the prefix is the class descriptor,
+    // where ';' is replaced whit '$' and no '/' after that.
+    if (classOrInnerClassDescriptor.size < classDescriptor.size) {
+      return false;
+    }
+    ThrowingCharIterator<UTFDataFormatException> i1 = classDescriptor.iterator();
+    ThrowingCharIterator<UTFDataFormatException> i2 = classOrInnerClassDescriptor.iterator();
+    try {
+      while (i1.hasNext()) {
+        char c1 = i1.nextChar();
+        char c2 = i2.nextChar();
+        // The Java VM behaviour is including all inner classes as well when a class is specified.
+        if (c1 == ';' && c2 == DescriptorUtils.INNER_CLASS_SEPARATOR) {
+          // If there is a '/' after the '$' this is not an inner class after all.
+          while (i2.hasNext()) {
+            if (i2.nextChar() == DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR) {
+              return false;
+            }
+          }
+          return true;
+        }
+        if (c1 != c2) {
+          return false;
+        }
+      }
+      assert i2.hasNext();
+      return false;
+    } catch (UTFDataFormatException e) {
+      return false;
+    }
+  }
+
+  /**
+   * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
+   * methods with assertions. This is used to support the Java VM -ea flag.
+   *
+   * <p>The class:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *     assert xxx;
+   *   }
+   * }
+   * </pre>
+   *
+   * Is compiled into:
+   *
+   * <pre>
+   * class A {
+   *   static boolean $assertionsDisabled;
+   *   static {
+   *     $assertionsDisabled = A.class.desiredAssertionStatus();
+   *   }
+   *
+   *   // method with "assert xxx";
+   *   void m() {
+   *     if (!$assertionsDisabled) {
+   *       if (xxx) {
+   *         throw new AssertionError(...);
+   *       }
+   *     }
+   *   }
+   * }
+   * </pre>
+   *
+   * With the rewriting below (and other rewritings) the resulting code is:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *   }
+   * }
+   * </pre>
+   */
+  public void run(DexEncodedMethod method, IRCode code) {
+    if (!enabled) {
+      return;
+    }
+    AssertionTransformation transformation = getTransformationForMethod(method);
+    if (transformation == AssertionTransformation.PASSTHROUGH) {
+      return;
+    }
+    DexEncodedMethod clinit;
+    // If the <clinit> of this class did not have have code to turn on assertions don't try to
+    // remove assertion code from the method (including <clinit> itself.
+    if (method.isClassInitializer()) {
+      clinit = method;
+    } else {
+      DexClass clazz = appView.definitionFor(method.method.holder);
+      if (clazz == null) {
+        return;
+      }
+      clinit = clazz.getClassInitializer();
+    }
+    if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+      return;
+    }
+
+    // This code will process the assertion code in all methods including <clinit>.
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+      if (current.isInvokeMethod()) {
+        InvokeMethod invoke = current.asInvokeMethod();
+        if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+          iterator.replaceCurrentInstruction(code.createIntConstant(0));
+        }
+      } else if (current.isStaticPut()) {
+        StaticPut staticPut = current.asStaticPut();
+        if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
+          iterator.remove();
+        }
+      } else if (current.isStaticGet()) {
+        StaticGet staticGet = current.asStaticGet();
+        if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
+          iterator.replaceCurrentInstruction(
+              code.createIntConstant(transformation == AssertionTransformation.DISABLE ? 1 : 0));
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 527874a..65e86b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -9,7 +9,6 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -72,7 +71,6 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
@@ -80,7 +78,6 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1275,95 +1272,6 @@
     assert code.isConsistentSSA();
   }
 
-  /**
-   * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
-   * methods with assertions. This is used to support the Java VM -ea flag.
-   *
-   * <p>The class:
-   *
-   * <pre>
-   * class A {
-   *   void m() {
-   *     assert xxx;
-   *   }
-   * }
-   * </pre>
-   *
-   * Is compiled into:
-   *
-   * <pre>
-   * class A {
-   *   static boolean $assertionsDisabled;
-   *   static {
-   *     $assertionsDisabled = A.class.desiredAssertionStatus();
-   *   }
-   *
-   *   // method with "assert xxx";
-   *   void m() {
-   *     if (!$assertionsDisabled) {
-   *       if (xxx) {
-   *         throw new AssertionError(...);
-   *       }
-   *     }
-   *   }
-   * }
-   * </pre>
-   *
-   * With the rewriting below (and other rewritings) the resulting code is:
-   *
-   * <pre>
-   * class A {
-   *   void m() {
-   *   }
-   * }
-   * </pre>
-   */
-  public void processAssertions(
-      AppView<?> appView, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    assert appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH;
-    DexEncodedMethod clinit;
-    // If the <clinit> of this class did not have have code to turn on assertions don't try to
-    // remove assertion code from the method (including <clinit> itself.
-    if (method.isClassInitializer()) {
-      clinit = method;
-    } else {
-      DexClass clazz = appView.definitionFor(method.method.holder);
-      if (clazz == null) {
-        return;
-      }
-      clinit = clazz.getClassInitializer();
-    }
-    if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
-      return;
-    }
-
-    // This code will process the assertion code in all methods including <clinit>.
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-      if (current.isInvokeMethod()) {
-        InvokeMethod invoke = current.asInvokeMethod();
-        if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
-          iterator.replaceCurrentInstruction(code.createIntConstant(0));
-        }
-      } else if (current.isStaticPut()) {
-        StaticPut staticPut = current.asStaticPut();
-        if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
-          iterator.remove();
-        }
-      } else if (current.isStaticGet()) {
-        StaticGet staticGet = current.asStaticGet();
-        if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
-          iterator.replaceCurrentInstruction(
-              code.createIntConstant(
-                  appView.options().assertionTransformation == AssertionTransformation.DISABLE
-                      ? 1
-                      : 0));
-        }
-      }
-    }
-  }
-
   enum RemoveCheckCastInstructionIfTrivialResult {
     NO_REMOVALS,
     REMOVED_CAST_DO_NARROW
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 c4815ae..128c18b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -5,7 +5,7 @@
 
 import static com.google.common.base.Predicates.not;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DataResourceConsumer;
@@ -435,7 +435,7 @@
   public boolean ignoreMissingClasses = false;
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
-  public AssertionTransformation assertionTransformation = null;
+  public AssertionsConfiguration assertionsConfiguration = null;
   public boolean configurationDebugging = false;
 
   // Don't convert Code objects to IRCode.
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index af84f69..ce9f1c5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.rewrite.assertions.testclasses.TestClassForInnerClass;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -25,12 +26,35 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
 
 @RunWith(Parameterized.class)
-public class AssertionsConfigurationTest extends TestBase {
+public class AssertionsConfigurationTest extends TestBase implements Opcodes {
 
   private final TestParameters parameters;
 
+  private Class<?> class1 = com.android.tools.r8.rewrite.assertions.testclasses.Class1.class;
+  private Class<?> class2 = com.android.tools.r8.rewrite.assertions.testclasses.Class2.class;
+  private Class<?> subpackageClass1 =
+      com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class;
+  private Class<?> subpackageClass2 =
+      com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class;
+
+  private List<Class<?>> testClasses =
+      ImmutableList.of(TestClass.class, class1, class2, subpackageClass1, subpackageClass2);
+
+  private String packageName =
+      com.android.tools.r8.rewrite.assertions.testclasses.Class1.class.getPackage().getName();
+  private String subPackageName =
+      com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class
+          .getPackage()
+          .getName();
+
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevels().build();
@@ -47,12 +71,7 @@
       List<String> outputLines)
       throws Exception {
     testForD8()
-        .addProgramClasses(
-            TestClass.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+        .addProgramClasses(testClasses)
         .setMinApi(parameters.getApiLevel())
         .addAssertionsConfiguration(assertionsConfigurationBuilder)
         .compile()
@@ -79,18 +98,9 @@
       throws Exception {
 
     testForR8(parameters.getBackend())
-        .addProgramClasses(
-            TestClass.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+        .addProgramClasses(testClasses)
         .addKeepMainRule(TestClass.class)
-        .addKeepClassAndMembersRules(
-            com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+        .addKeepClassAndMembersRules(class1, class2, subpackageClass1, subpackageClass2)
         .setMinApi(parameters.getApiLevel())
         .addAssertionsConfiguration(assertionsConfigurationBuilder)
         .compile()
@@ -125,8 +135,7 @@
     return ImmutableList.of("DONE");
   }
 
-  private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
-    ClassSubject subject = inspector.clazz(clazz);
+  private void checkAssertionCodeRemoved(ClassSubject subject) {
     assertThat(subject, isPresent());
     // <clinit> is removed by R8 as it becomes empty.
     if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -143,8 +152,11 @@
             .anyMatch(InstructionSubject::isThrow));
   }
 
-  private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
-    ClassSubject subject = inspector.clazz(clazz);
+  private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
+    checkAssertionCodeRemoved(inspector.clazz(clazz));
+  }
+
+  private void checkAssertionCodeEnabled(ClassSubject subject) {
     assertThat(subject, isPresent());
     // <clinit> is removed by R8.
     if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -161,6 +173,10 @@
             .anyMatch(InstructionSubject::isThrow));
   }
 
+  private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
+    checkAssertionCodeEnabled(inspector.clazz(clazz));
+  }
+
   private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) {
     ClassSubject subject = inspector.clazz(clazz);
     assertThat(subject, isPresent());
@@ -177,40 +193,28 @@
   }
 
   private void checkAssertionCodeRemoved(CodeInspector inspector) {
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+    checkAssertionCodeRemoved(inspector, class1);
+    checkAssertionCodeRemoved(inspector, class2);
+    checkAssertionCodeRemoved(inspector, subpackageClass1);
+    checkAssertionCodeRemoved(inspector, subpackageClass2);
   }
 
   private void checkAssertionCodeEnabled(CodeInspector inspector) {
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+    checkAssertionCodeEnabled(inspector, class1);
+    checkAssertionCodeEnabled(inspector, class2);
+    checkAssertionCodeEnabled(inspector, subpackageClass1);
+    checkAssertionCodeEnabled(inspector, subpackageClass2);
   }
 
   private void checkAssertionCodeLeft(CodeInspector inspector) {
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+    checkAssertionCodeLeft(inspector, class1);
+    checkAssertionCodeLeft(inspector, class2);
+    checkAssertionCodeLeft(inspector, subpackageClass1);
+    checkAssertionCodeLeft(inspector, subpackageClass2);
   }
 
   @Test
-  public void testEnableAllAssertionsForDex() throws Exception {
+  public void testAssertionsForDex() throws Exception {
     Assume.assumeTrue(parameters.isDexRuntime());
     // Leaving assertions in or disabling them on Dalvik/Art means no assertions.
     runD8Test(
@@ -230,6 +234,15 @@
         this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
     runR8Test(
         this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+    // Enabling for the package should enable all.
+    runD8Test(
+        builder -> builder.enableForPackage(packageName).build(),
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder -> builder.enableForPackage(packageName).build(),
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines());
   }
 
   @Test
@@ -259,6 +272,415 @@
         true);
   }
 
+  @Test
+  public void testEnableForPackageForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    runD8Test(
+        builder -> builder.enableForPackage(subPackageName).build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.subpackage.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+    runR8Test(
+        builder -> builder.enableForPackage(subPackageName).build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.subpackage.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+  }
+
+  @Test
+  public void testEnableForClassForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    runD8Test(
+        builder ->
+            builder
+                .enableForClass(class1.getCanonicalName())
+                .enableForClass(subpackageClass2.getCanonicalName())
+                .build(),
+        inspector -> {
+          // checkAssertionCodeEnabled(inspector, class1);
+          // checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+    runR8Test(
+        builder ->
+            builder
+                .enableForClass(class1.getCanonicalName())
+                .enableForClass(subpackageClass2.getCanonicalName())
+                .build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, class1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+  }
+
+  @Test
+  public void testMixedForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    runD8Test(
+        builder ->
+            builder
+                .enableForPackage(packageName)
+                .disableForClass(class2.getCanonicalName())
+                .disableForClass(subpackageClass1.getCanonicalName())
+                .build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, class1);
+          checkAssertionCodeRemoved(inspector, class2);
+          checkAssertionCodeRemoved(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+    runR8Test(
+        builder ->
+            builder
+                .enableForPackage(packageName)
+                .disableForClass(class2.getCanonicalName())
+                .disableForClass(subpackageClass1.getCanonicalName())
+                .build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, class1);
+          checkAssertionCodeRemoved(inspector, class2);
+          checkAssertionCodeRemoved(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+  }
+
+  @Test
+  public void testUnnamedPackageForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(class1, class2)
+        .addProgramClassFileData(
+            testClassForUnknownPackage(),
+            classInUnnamedPackage("Class1"),
+            classInUnnamedPackage("Class2"))
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(builder -> builder.enableForPackage("").build())
+        .compile()
+        .inspect(
+            inspector -> {
+              checkAssertionCodeEnabled(inspector.clazz("Class1"));
+              checkAssertionCodeEnabled(inspector.clazz("Class2"));
+              checkAssertionCodeRemoved(inspector.clazz(class1));
+              checkAssertionCodeRemoved(inspector.clazz(class2));
+            })
+        .run(parameters.getRuntime(), "Main")
+        .assertSuccessWithOutputLines(
+            "AssertionError in Class1", "AssertionError in Class2", "DONE");
+  }
+
+  @Test
+  public void testInnerClassForJvm() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    // Pointing to the outer class enables assertions for the inner as well.
+    testForJvm()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .addVmArguments("-ea:" + TestClassForInnerClass.class.getCanonicalName())
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines(
+            "AssertionError in TestClassForInnerClass",
+            "AssertionError in TestClassForInnerClass.InnerClass",
+            "DONE");
+
+    // Pointing to the inner class enables no assertions.
+    testForJvm()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getCanonicalName())
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines("DONE");
+    testForJvm()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getTypeName())
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines("DONE");
+  }
+
+  @Test
+  public void testInnerClassForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(
+            builder ->
+                builder.enableForClass(TestClassForInnerClass.class.getCanonicalName()).build())
+        .compile()
+        .inspect(
+            inspector -> {
+              checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.class));
+              checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.InnerClass.class));
+            })
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines(
+            "AssertionError in TestClassForInnerClass",
+            "AssertionError in TestClassForInnerClass.InnerClass",
+            "DONE");
+  }
+  /**
+   * Code for the following class in the unnamed package:
+   *
+   * <p>public class Main { public static void main(String[] args) { try { Class1.m(); } catch
+   * (AssertionError e) { System.out.println("AssertionError in Class1"); } try { Class2.m(); }
+   * catch (AssertionError e) { System.out.println("AssertionError in Class2"); } try {
+   * com.android.tools.r8.rewrite.assertions.Class1.m(); } catch (AssertionError e) {
+   * System.out.println("AssertionError in Class1"); } try {
+   * com.android.tools.r8.rewrite.assertions.Class2.m(); } catch (AssertionError e) {
+   * System.out.println("AssertionError in Class2"); } System.out.println("DONE"); } }
+   */
+  public static byte[] testClassForUnknownPackage() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_FINAL | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Main.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(1, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      Label label2 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/AssertionError");
+      Label label3 = new Label();
+      Label label4 = new Label();
+      Label label5 = new Label();
+      methodVisitor.visitTryCatchBlock(label3, label4, label5, "java/lang/AssertionError");
+      Label label6 = new Label();
+      Label label7 = new Label();
+      Label label8 = new Label();
+      methodVisitor.visitTryCatchBlock(label6, label7, label8, "java/lang/AssertionError");
+      Label label9 = new Label();
+      Label label10 = new Label();
+      Label label11 = new Label();
+      methodVisitor.visitTryCatchBlock(label9, label10, label11, "java/lang/AssertionError");
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(4, label0);
+      methodVisitor.visitMethodInsn(INVOKESTATIC, "Class1", "m", "()V", false);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(7, label1);
+      methodVisitor.visitJumpInsn(GOTO, label3);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(5, label2);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label12 = new Label();
+      methodVisitor.visitLabel(label12);
+      methodVisitor.visitLineNumber(6, label12);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in Class1");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(9, label3);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitMethodInsn(INVOKESTATIC, "Class2", "m", "()V", false);
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(12, label4);
+      methodVisitor.visitJumpInsn(GOTO, label6);
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(10, label5);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label13 = new Label();
+      methodVisitor.visitLabel(label13);
+      methodVisitor.visitLineNumber(11, label13);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in Class2");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLineNumber(14, label6);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/rewrite/assertions/testclasses/Class1",
+          "m",
+          "()V",
+          false);
+      methodVisitor.visitLabel(label7);
+      methodVisitor.visitLineNumber(17, label7);
+      methodVisitor.visitJumpInsn(GOTO, label9);
+      methodVisitor.visitLabel(label8);
+      methodVisitor.visitLineNumber(15, label8);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label14 = new Label();
+      methodVisitor.visitLabel(label14);
+      methodVisitor.visitLineNumber(16, label14);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in testclasses.Class1");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label9);
+      methodVisitor.visitLineNumber(19, label9);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/rewrite/assertions/testclasses/Class2",
+          "m",
+          "()V",
+          false);
+      methodVisitor.visitLabel(label10);
+      methodVisitor.visitLineNumber(22, label10);
+      Label label15 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label15);
+      methodVisitor.visitLabel(label11);
+      methodVisitor.visitLineNumber(20, label11);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label16 = new Label();
+      methodVisitor.visitLabel(label16);
+      methodVisitor.visitLineNumber(21, label16);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in testclasses.Class2");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label15);
+      methodVisitor.visitLineNumber(24, label15);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("DONE");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label17 = new Label();
+      methodVisitor.visitLabel(label17);
+      methodVisitor.visitLineNumber(25, label17);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  /**
+   * Code for the following class in the unnamed package:
+   *
+   * <p>public class <name> { public static void m() { assert false; } }
+   */
+  public static byte[] classInUnnamedPackage(String name) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
+
+    classWriter.visitSource(name + ".java", null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(1, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, name, "$assertionsDisabled", "Z");
+      Label label1 = new Label();
+      methodVisitor.visitJumpInsn(IFNE, label1);
+      methodVisitor.visitTypeInsn(NEW, "java/lang/AssertionError");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/AssertionError", "<init>", "()V", false);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(1, label0);
+      methodVisitor.visitLdcInsn(Type.getType("L" + name + ";"));
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+      Label label1 = new Label();
+      methodVisitor.visitJumpInsn(IFNE, label1);
+      methodVisitor.visitInsn(ICONST_1);
+      Label label2 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label2);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitInsn(ICONST_0);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
+      methodVisitor.visitFieldInsn(PUTSTATIC, name, "$assertionsDisabled", "Z");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
   static class TestClass {
     public static void main(String[] args) {
       try {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 446821a..27e53ba 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -199,7 +199,7 @@
         .debug()
         .noTreeShaking()
         .noMinification()
-        .addOptionsModification(o -> o.assertionTransformation = transformation)
+        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
         .compile();
   }
 
@@ -337,7 +337,7 @@
         .addProgramClasses(ClassWithAssertions.class)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addOptionsModification(o -> o.assertionTransformation = transformation)
+        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
         .compile();
   }
 
@@ -357,7 +357,7 @@
         .addProgramFiles(program)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addOptionsModification(o -> o.assertionTransformation = transformation)
+        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
         .compile();
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
new file mode 100644
index 0000000..6a719f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
@@ -0,0 +1,31 @@
+// 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.rewrite.assertions.testclasses;
+
+public class TestClassForInnerClass {
+  public static class InnerClass {
+    public static void m() {
+      assert false;
+    }
+  }
+
+  public static void m() {
+    assert false;
+  }
+
+  public static void main(String[] args) {
+    try {
+      m();
+    } catch (AssertionError e) {
+      System.out.println("AssertionError in TestClassForInnerClass");
+    }
+    try {
+      InnerClass.m();
+    } catch (AssertionError e) {
+      System.out.println("AssertionError in TestClassForInnerClass.InnerClass");
+    }
+    System.out.println("DONE");
+  }
+}