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

First step:
  * Full API added
  * Only support setting ENABLE, DISABLE or FULL for all classes

Bug: 139898386
Change-Id: Ieee4aa72b1bcac25225236bbe3c9bec5e84e52fe
diff --git a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
new file mode 100644
index 0000000..f77bbce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.utils.StringDiagnostic;
+
+@Keep
+public class AssertionsConfiguration {
+
+  /** The possible transformations of the javac generated assertion code during compilation. */
+  public enum AssertionTransformation {
+    /** Unconditionally enable the javac generated assertion code. */
+    ENABLE,
+    /**
+     * Unconditionally disable the javac generated assertion code. This will most likely remove the
+     * javac generated assertion code completely.
+     */
+    DISABLE,
+    /** Passthrough of the javac generated assertion code. */
+    PASSTHROUGH
+  }
+
+  private AssertionTransformation transformation;
+
+  private AssertionsConfiguration(AssertionTransformation transformation) {
+    this.transformation = transformation;
+  }
+
+  static Builder builder(DiagnosticsHandler handler) {
+    return new Builder(handler);
+  }
+
+  /**
+   * Builder for constructing a <code>{@link AssertionsConfiguration}</code>.
+   *
+   * <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 DiagnosticsHandler handler;
+
+    private Builder(DiagnosticsHandler handler) {
+      this.handler = handler;
+    }
+
+    /** Set how to handle javac generated assertion code. */
+    public AssertionsConfiguration.Builder setTransformation(
+        AssertionTransformation transformation) {
+      this.transformation = transformation;
+      return this;
+    }
+
+    /**
+     * Unconditionally enable javac generated assertion code in all packages and classes. This
+     * corresponds to passing <code>-enableassertions</code> or <code>-ea</code> to the java CLI.
+     */
+    public AssertionsConfiguration.Builder enable() {
+      setTransformation(AssertionTransformation.ENABLE);
+      return this;
+    }
+
+    /**
+     * Disable the javac generated assertion code in all packages and classes. This corresponds to
+     * passing <code>-disableassertions</code> or <code>-da</code> to the java CLI.
+     */
+    public AssertionsConfiguration.Builder disable() {
+      setTransformation(AssertionTransformation.DISABLE);
+      return this;
+    }
+
+    /** Passthrough of the javac generated assertion code in all packages and classes. */
+    public AssertionsConfiguration.Builder passthrough() {
+      setTransformation(AssertionTransformation.PASSTHROUGH);
+      return this;
+    }
+
+    /** 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"));
+      return this;
+    }
+
+    /**
+     * Unconditionally enable javac generated assertion code in package <code>packageName</code> and
+     * all subpackages. This corresponds to passing <code>-enableassertions:packageName...</code> or
+     * <code>-ea:packageName...</code> to the java CLI.
+     *
+     * <p>If <code>packageName</code> is the empty string, assertions are enabled in the unnamed
+     * package, which corresponds to passing <code>-enableassertions:...</code> or <code>-ea:...
+     * </code> to the java CLI.
+     */
+    public AssertionsConfiguration.Builder enableForPackage(String packageName) {
+      return setTransformationForPackage(packageName, AssertionTransformation.ENABLE);
+    }
+
+    /**
+     * Disable the javac generated assertion code in package <code>packageName</code> and all
+     * subpackages. This corresponds to passing <code>-disableassertions:packageName...</code> or
+     * <code>-da:packageName...</code> to the java CLI.
+     *
+     * <p>If <code>packageName</code> is the empty string assertions are disabled in the unnamed
+     * package, which corresponds to passing <code>-disableassertions:...</code> or <code>-da:...
+     * </code> to the java CLI.
+     */
+    public AssertionsConfiguration.Builder disableForPackage(String packageName) {
+      return setTransformationForPackage(packageName, AssertionTransformation.DISABLE);
+    }
+
+    public AssertionsConfiguration.Builder passthroughForPackage(String packageName) {
+      return setTransformationForPackage(packageName, AssertionTransformation.PASSTHROUGH);
+    }
+
+    /** Set how to handle javac generated assertion code in class. */
+    public AssertionsConfiguration.Builder setTransformationForClass(
+        String className, AssertionTransformation transformation) {
+      handler.error(new StringDiagnostic("Unsupported"));
+      return this;
+    }
+
+    /**
+     * Unconditionally enable javac generated assertion in class <code>className</code>. This
+     * corresponds to passing <code> -enableassertions:className</code> or <code>-ea:className
+     * </code> to the java CLI.
+     */
+    public AssertionsConfiguration.Builder enableForClass(String className) {
+      return setTransformationForClass(className, AssertionTransformation.ENABLE);
+    }
+
+    /**
+     * Disable the javac generated assertion code in class <code>className</code>. This corresponds
+     * to passing <code> -disableassertions:className</code> or <code>-da:className</code> to the
+     * java CLI.
+     */
+    public AssertionsConfiguration.Builder disableForClass(String className) {
+      return setTransformationForClass(className, AssertionTransformation.DISABLE);
+    }
+
+    public AssertionsConfiguration.Builder passthroughForClass(String className) {
+      return setTransformationForClass(className, AssertionTransformation.PASSTHROUGH);
+    }
+
+    /** Build and return the {@link AssertionsConfiguration}. */
+    public AssertionsConfiguration build() {
+      return new AssertionsConfiguration(transformation);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index b0d5055..ce60661 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -17,6 +17,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BiPredicate;
+import java.util.function.Function;
 
 /**
  * Base class for commands and command builders for compiler applications/tools which besides an
@@ -38,6 +39,7 @@
   private final boolean includeClassesChecksum;
   private final boolean optimizeMultidexForLinearAlloc;
   private final BiPredicate<String, Long> dexClassChecksumFilter;
+  private final AssertionsConfiguration assertionsConfiguration;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -50,6 +52,7 @@
     includeClassesChecksum = false;
     optimizeMultidexForLinearAlloc = false;
     dexClassChecksumFilter = (name, checksum) -> true;
+    assertionsConfiguration = null;
   }
 
   BaseCompilerCommand(
@@ -62,7 +65,8 @@
       boolean enableDesugaring,
       boolean optimizeMultidexForLinearAlloc,
       boolean includeClassesChecksum,
-      BiPredicate<String, Long> dexClassChecksumFilter) {
+      BiPredicate<String, Long> dexClassChecksumFilter,
+      AssertionsConfiguration assertionsConfiguration) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -75,6 +79,7 @@
     this.optimizeMultidexForLinearAlloc = optimizeMultidexForLinearAlloc;
     this.includeClassesChecksum = includeClassesChecksum;
     this.dexClassChecksumFilter = dexClassChecksumFilter;
+    this.assertionsConfiguration = assertionsConfiguration;
   }
 
   /**
@@ -129,6 +134,10 @@
     return optimizeMultidexForLinearAlloc;
   }
 
+  public AssertionsConfiguration getAssertionsConfiguration() {
+    return assertionsConfiguration;
+  }
+
   Reporter getReporter() {
     return reporter;
   }
@@ -158,6 +167,7 @@
     private boolean lookupLibraryBeforeProgram = true;
     private boolean optimizeMultidexForLinearAlloc = false;
     private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
+    private AssertionsConfiguration assertionsConfiguration;
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -476,6 +486,19 @@
       return includeClassesChecksum;
     }
 
+    /** Configure compile time assertion enabling through a {@link AssertionsConfiguration}. */
+    public B addAssertionsConfiguration(
+        Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
+            assertionsConfigurationGenerator) {
+      assertionsConfiguration =
+          assertionsConfigurationGenerator.apply(AssertionsConfiguration.builder(getReporter()));
+      return self();
+    }
+
+    public AssertionsConfiguration getAssertionsConfiguration() {
+      return assertionsConfiguration;
+    }
+
     @Override
     void validate() {
       Reporter reporter = getReporter();
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index ebebbf1..2214e2d 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -6,6 +6,7 @@
 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;
@@ -25,7 +26,6 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -159,7 +159,7 @@
 
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
-      if (options.assertionProcessing != AssertionProcessing.LEAVE) {
+      if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
         // 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 b72548b..6bdc35c 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.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.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
@@ -218,6 +219,7 @@
           getDesugarGraphConsumer(),
           desugaredLibraryKeepRuleConsumer,
           libraryConfiguration,
+          getAssertionsConfiguration(),
           factory);
     }
   }
@@ -285,6 +287,7 @@
       DesugarGraphConsumer desugarGraphConsumer,
       StringConsumer desugaredLibraryKeepRuleConsumer,
       DesugaredLibraryConfiguration libraryConfiguration,
+      AssertionsConfiguration assertionsConfiguration,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -296,7 +299,8 @@
         enableDesugaring,
         optimizeMultidexForLinearAlloc,
         encodeChecksum,
-        dexClassChecksumFilter);
+        dexClassChecksumFilter,
+        assertionsConfiguration);
     this.intermediate = intermediate;
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
@@ -357,6 +361,16 @@
     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;
+    }
+
     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 d96a5a1..ff64d62 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -61,7 +61,8 @@
         true,
         false,
         false,
-        (name, checksum) -> true);
+        (name, checksum) -> true,
+        AssertionsConfiguration.builder(null).build());
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.libraryConfiguration = libraryConfiguration;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index b4ae1c8..03235a4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -6,6 +6,7 @@
 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;
@@ -81,7 +82,6 @@
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.LineNumberOptimizer;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SelfRetraceTest;
@@ -813,7 +813,7 @@
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
     }
-    if (appView.options().assertionProcessing != AssertionProcessing.LEAVE) {
+    if (appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH) {
       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 8c63d06..45f9e84 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.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.experimental.graphinfo.GraphConsumer;
@@ -23,7 +24,6 @@
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -562,7 +562,8 @@
               getDexClassChecksumFilter(),
               desugaredLibraryKeepRuleConsumer,
               libraryConfiguration,
-              featureSplitConfiguration);
+              featureSplitConfiguration,
+              getAssertionsConfiguration());
 
       return command;
     }
@@ -718,7 +719,8 @@
       BiPredicate<String, Long> dexClassChecksumFilter,
       StringConsumer desugaredLibraryKeepRuleConsumer,
       DesugaredLibraryConfiguration libraryConfiguration,
-      FeatureSplitConfiguration featureSplitConfiguration) {
+      FeatureSplitConfiguration featureSplitConfiguration,
+      AssertionsConfiguration assertionsConfiguration) {
     super(
         inputApp,
         mode,
@@ -729,7 +731,8 @@
         enableDesugaring,
         optimizeMultidexForLinearAlloc,
         encodeChecksum,
-        dexClassChecksumFilter);
+        dexClassChecksumFilter,
+        assertionsConfiguration);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -869,9 +872,16 @@
 
     // 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.assertionProcessing == AssertionProcessing.REMOVE;
-    if (internal.isGeneratingClassFiles()) {
-      internal.assertionProcessing = AssertionProcessing.LEAVE;
+    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();
     }
 
     // When generating class files the build is "intermediate" and we cannot pollute the namespace
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 5041e1d..32b7cbe 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,6 +6,7 @@
 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;
@@ -86,11 +87,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
 import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -171,7 +170,7 @@
       OptimizationFeedbackSimple.getInstance();
   private DexString highestSortingString;
 
-  private List<Action> onWaveDoneActions = null;
+  private List<com.android.tools.r8.utils.Action> onWaveDoneActions = null;
 
   private final List<DexString> neverMergePrefixes;
   boolean seenNotNeverMergePrefix = false;
@@ -819,11 +818,11 @@
 
   private void waveDone() {
     delayedOptimizationFeedback.updateVisibleOptimizationInfo();
-    onWaveDoneActions.forEach(Action::execute);
+    onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
     onWaveDoneActions = null;
   }
 
-  public void addWaveDoneAction(Action action) {
+  public void addWaveDoneAction(com.android.tools.r8.utils.Action action) {
     if (!appView.enableWholeProgramOptimizations()) {
       throw new Unreachable("addWaveDoneAction() should never be used in D8.");
     }
@@ -1177,7 +1176,7 @@
       assert appView.enableWholeProgramOptimizations();
       codeRewriter.removeSwitchMaps(code);
     }
-    if (options.assertionProcessing != AssertionProcessing.LEAVE) {
+    if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
       codeRewriter.processAssertions(appView, method, code, feedback);
     }
 
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 04dd590..527874a 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,6 +9,7 @@
 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;
@@ -83,7 +84,6 @@
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.SetUtils;
@@ -1320,7 +1320,7 @@
    */
   public void processAssertions(
       AppView<?> appView, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    assert appView.options().assertionProcessing != AssertionProcessing.LEAVE;
+    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.
@@ -1356,7 +1356,9 @@
         if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
           iterator.replaceCurrentInstruction(
               code.createIntConstant(
-                  appView.options().assertionProcessing == AssertionProcessing.REMOVE ? 1 : 0));
+                  appView.options().assertionTransformation == AssertionTransformation.DISABLE
+                      ? 1
+                      : 0));
         }
       }
     }
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 a5f672e..98ac732 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -5,6 +5,7 @@
 
 import static com.google.common.base.Predicates.not;
 
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DataResourceConsumer;
@@ -74,15 +75,6 @@
     ON
   }
 
-  public enum AssertionProcessing {
-    /** Leave the conditional javac generated assertion code untouched. */
-    LEAVE,
-    /** Remove the javac generated assertion code completely. */
-    REMOVE,
-    /** Enable the javac generated assertion code unconditionally at compile time. */
-    ENABLE
-  }
-
   public static final int SUPPORTED_CF_MAJOR_VERSION = Opcodes.V11;
   public static final int SUPPORTED_DEX_VERSION =
       AndroidApiLevel.LATEST.getDexVersion().getIntValue();
@@ -442,7 +434,7 @@
   public boolean ignoreMissingClasses = false;
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
-  public AssertionProcessing assertionProcessing = AssertionProcessing.REMOVE;
+  public AssertionTransformation assertionTransformation = null;
   public boolean configurationDebugging = false;
 
   // Don't convert Code objects to IRCode.
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index e3f52a1..e04a2c1 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -204,6 +204,24 @@
     return self();
   }
 
+  public CR enableRuntimeAssertions(boolean enable) {
+    if (getBackend() == Backend.CF) {
+      if (enable) {
+        enableRuntimeAssertions();
+      }
+    } else {
+      // Assertions cannot be enabled on dex VMs.
+      assert !enable;
+    }
+
+    if (enable) {
+      if (!this.vmArguments.contains("-ea")) {
+        this.vmArguments.add("-ea");
+      }
+    }
+    return self();
+  }
+
   public Path writeToZip() throws IOException {
     Path file = state.getNewTempFolder().resolve("out.zip");
     writeToZip(file);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 3b368b6..2f4664e 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -21,6 +21,7 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 public abstract class TestCompilerBuilder<
@@ -299,4 +300,11 @@
     additionalRunClassPath.addAll(files);
     return self();
   }
+
+  public T addAssertionsConfiguration(
+      Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
+          assertionsConfigurationGenerator) {
+    builder.addAssertionsConfiguration(assertionsConfigurationGenerator);
+    return self();
+  }
 }
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
new file mode 100644
index 0000000..af84f69
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -0,0 +1,287 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+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.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.function.Function;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssertionsConfigurationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public AssertionsConfigurationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private void runD8Test(
+      Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
+          assertionsConfigurationBuilder,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+      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)
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(assertionsConfigurationBuilder)
+        .compile()
+        .inspect(inspector)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(StringUtils.lines(outputLines));
+  }
+
+  public void runR8Test(
+      Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
+          assertionsConfigurationBuilder,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+      List<String> outputLines)
+      throws Exception {
+    runR8Test(assertionsConfigurationBuilder, inspector, outputLines, false);
+  }
+
+  public void runR8Test(
+      Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
+          assertionsConfigurationBuilder,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+      List<String> outputLines,
+      boolean enableJvmAssertions)
+      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)
+        .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)
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(assertionsConfigurationBuilder)
+        .compile()
+        .inspect(inspector)
+        .enableRuntimeAssertions(enableJvmAssertions)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(StringUtils.lines(outputLines));
+  }
+
+  private AssertionsConfiguration enableAllAssertions(AssertionsConfiguration.Builder builder) {
+    return builder.enable().build();
+  }
+
+  private AssertionsConfiguration disableAllAssertions(AssertionsConfiguration.Builder builder) {
+    return builder.disable().build();
+  }
+
+  private AssertionsConfiguration leaveAllAssertions(AssertionsConfiguration.Builder builder) {
+    return builder.passthrough().build();
+  }
+
+  private List<String> allAssertionsExpectedLines() {
+    return ImmutableList.of(
+        "AssertionError in testclasses.Class1",
+        "AssertionError in testclasses.Class2",
+        "AssertionError in testclasses.subpackage.Class1",
+        "AssertionError in testclasses.subpackage.Class2",
+        "DONE");
+  }
+
+  private List<String> noAllAssertionsExpectedLines() {
+    return ImmutableList.of("DONE");
+  }
+
+  private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
+    ClassSubject subject = inspector.clazz(clazz);
+    assertThat(subject, isPresent());
+    // <clinit> is removed by R8 as it becomes empty.
+    if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
+      assertFalse(
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isStaticPut));
+    }
+    assertFalse(
+        subject
+            .uniqueMethodWithName("m")
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isThrow));
+  }
+
+  private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
+    ClassSubject subject = inspector.clazz(clazz);
+    assertThat(subject, isPresent());
+    // <clinit> is removed by R8.
+    if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
+      assertFalse(
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isStaticPut));
+    }
+    assertTrue(
+        subject
+            .uniqueMethodWithName("m")
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isThrow));
+  }
+
+  private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) {
+    ClassSubject subject = inspector.clazz(clazz);
+    assertThat(subject, isPresent());
+    assertTrue(
+        subject
+            .uniqueMethodWithName("<clinit>")
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isStaticPut));
+    assertTrue(
+        subject
+            .uniqueMethodWithName("m")
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isThrow));
+  }
+
+  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);
+  }
+
+  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);
+  }
+
+  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);
+  }
+
+  @Test
+  public void testEnableAllAssertionsForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    // Leaving assertions in or disabling them on Dalvik/Art means no assertions.
+    runD8Test(
+        this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines());
+    runR8Test(
+        this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines());
+    runD8Test(
+        this::disableAllAssertions,
+        this::checkAssertionCodeRemoved,
+        noAllAssertionsExpectedLines());
+    runR8Test(
+        this::disableAllAssertions,
+        this::checkAssertionCodeRemoved,
+        noAllAssertionsExpectedLines());
+    // Compile time enabling assertions gives assertions on Dalvik/Art.
+    runD8Test(
+        this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+    runR8Test(
+        this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+  }
+
+  @Test
+  public void testAssertionsForCf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    // Leaving assertion code means assertions are controlled by the -ea flag.
+    runR8Test(
+        this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines());
+    runR8Test(
+        this::leaveAllAssertions, this::checkAssertionCodeLeft, allAssertionsExpectedLines(), true);
+    // Compile time enabling or disabling assertions means the -ea flag has no effect.
+    runR8Test(
+        this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+    runR8Test(
+        this::enableAllAssertions,
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines(),
+        true);
+    runR8Test(
+        this::disableAllAssertions,
+        this::checkAssertionCodeRemoved,
+        noAllAssertionsExpectedLines());
+    runR8Test(
+        this::disableAllAssertions,
+        this::checkAssertionCodeRemoved,
+        noAllAssertionsExpectedLines(),
+        true);
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      try {
+        com.android.tools.r8.rewrite.assertions.testclasses.Class1.m();
+      } catch (AssertionError e) {
+        System.out.println("AssertionError in testclasses.Class1");
+      }
+      try {
+        com.android.tools.r8.rewrite.assertions.testclasses.Class2.m();
+      } catch (AssertionError e) {
+        System.out.println("AssertionError in testclasses.Class2");
+      }
+      try {
+        com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.m();
+      } catch (AssertionError e) {
+        System.out.println("AssertionError in testclasses.subpackage.Class1");
+      }
+      try {
+        com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.m();
+      } catch (AssertionError e) {
+        System.out.println("AssertionError in testclasses.subpackage.Class2");
+      }
+      System.out.println("DONE");
+    }
+  }
+}
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 298134a..446821a 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
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.R8TestCompileResult;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -192,14 +192,14 @@
         .compile();
   }
 
-  private static R8TestCompileResult compileCf(InternalOptions.AssertionProcessing assertionsState)
+  private static R8TestCompileResult compileCf(AssertionTransformation transformation)
       throws CompilationFailedException {
     return testForR8(staticTemp, Backend.CF)
         .addProgramClasses(ClassWithAssertions.class)
         .debug()
         .noTreeShaking()
         .noMinification()
-        .addOptionsModification(o -> o.assertionProcessing = assertionsState)
+        .addOptionsModification(o -> o.assertionTransformation = transformation)
         .compile();
   }
 
@@ -232,9 +232,9 @@
     if (backend == Backend.CF) {
       return new CompilationResults(
           withAccess,
-          compileCf(AssertionProcessing.LEAVE),
-          compileCf(AssertionProcessing.REMOVE),
-          compileCf(AssertionProcessing.ENABLE));
+          compileCf(AssertionTransformation.PASSTHROUGH),
+          compileCf(AssertionTransformation.DISABLE),
+          compileCf(AssertionTransformation.ENABLE));
     }
     return new CompilationResults(
         withAccess,
@@ -331,18 +331,18 @@
     checkResultWithChromiumAssertions(results.withAssertions);
   }
 
-  private D8TestCompileResult compileD8(InternalOptions.AssertionProcessing assertionsState)
+  private D8TestCompileResult compileD8(AssertionTransformation transformation)
       throws CompilationFailedException {
     return testForD8()
         .addProgramClasses(ClassWithAssertions.class)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addOptionsModification(o -> o.assertionProcessing = assertionsState)
+        .addOptionsModification(o -> o.assertionTransformation = transformation)
         .compile();
   }
 
-  private D8TestCompileResult compileR8FollowedByD8(
-      InternalOptions.AssertionProcessing assertionsState) throws Exception {
+  private D8TestCompileResult compileR8FollowedByD8(AssertionTransformation transformation)
+      throws Exception {
     Path program =
         testForR8(Backend.CF)
             .addProgramClasses(ClassWithAssertions.class)
@@ -357,16 +357,16 @@
         .addProgramFiles(program)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addOptionsModification(o -> o.assertionProcessing = assertionsState)
+        .addOptionsModification(o -> o.assertionTransformation = transformation)
         .compile();
   }
 
   @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    checkResultWithAssertionsInactive(compileD8(AssertionProcessing.REMOVE));
-    checkResultWithAssertionsInactive(compileD8(AssertionProcessing.LEAVE));
-    checkResultWithAssertionsEnabledAtCompileTime(compileD8(AssertionProcessing.ENABLE));
+    checkResultWithAssertionsInactive(compileD8(AssertionTransformation.DISABLE));
+    checkResultWithAssertionsInactive(compileD8(AssertionTransformation.PASSTHROUGH));
+    checkResultWithAssertionsEnabledAtCompileTime(compileD8(AssertionTransformation.ENABLE));
   }
 
   private D8TestCompileResult compileD8Regress110887293(Function<byte[], byte[]> rewriter)
@@ -390,9 +390,9 @@
   @Test
   public void testR8FollowedByD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionProcessing.REMOVE));
-    checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionProcessing.LEAVE));
+    checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionTransformation.DISABLE));
+    checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionTransformation.PASSTHROUGH));
     checkResultWithAssertionsEnabledAtCompileTime(
-        compileR8FollowedByD8(AssertionProcessing.ENABLE));
+        compileR8FollowedByD8(AssertionTransformation.ENABLE));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class1.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class1.java
new file mode 100644
index 0000000..9de87c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class1.java
@@ -0,0 +1,11 @@
+// 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 Class1 {
+  public static void m() {
+    assert false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class2.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class2.java
new file mode 100644
index 0000000..93b24e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class2.java
@@ -0,0 +1,11 @@
+// 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 Class2 {
+  public static void m() {
+    assert false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class1.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class1.java
new file mode 100644
index 0000000..79d0949
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class1.java
@@ -0,0 +1,11 @@
+// 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.subpackage;
+
+public class Class1 {
+  public static void m() {
+    assert false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class2.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class2.java
new file mode 100644
index 0000000..e001cf0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class2.java
@@ -0,0 +1,11 @@
+// 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.subpackage;
+
+public class Class2 {
+  public static void m() {
+    assert false;
+  }
+}