Detect assertion enabling code earlier in the pipeline

Each <clinit> will now be checked for the javac code used to enable
assertions before IR conversion starts.

This will make the removal of assertion code independent of the IR
processing order and will ensure that all assertion code is removed
when generating dex code.

Bug: 139307059
Change-Id: I864d4ccc3d7699a367dda6a207c40e8dc059185d
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 23e4e09..2f3b50a 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -14,7 +14,9 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedbackSimple;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -152,6 +154,19 @@
 
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
+      if (options.disableAssertions) {
+        // Run analysis to mark all <clinit> methods having the javac generated assertion
+        // enabling code.
+        ClassInitializerAssertionEnablingAnalysis analysis =
+            new ClassInitializerAssertionEnablingAnalysis(
+                appInfo.dexItemFactory(), new OptimizationFeedbackSimple());
+        for (DexProgramClass clazz : appInfo.classes()) {
+          if (clazz.hasClassInitializer()) {
+            analysis.processNewlyLiveMethod(clazz.getClassInitializer());
+          }
+        }
+      }
+
       IRConverter converter = new IRConverter(appInfo, options, timing, printer);
       app = converter.convert(app, executor);
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index be25c67..d65fbc2 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -25,9 +25,11 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
@@ -324,6 +326,11 @@
         if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
           enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
         }
+        if (appView.options().disableAssertions) {
+          enqueuer.registerAnalysis(
+              (new ClassInitializerAssertionEnablingAnalysis(
+                  appView.dexItemFactory(), new OptimizationFeedbackSimple())));
+        }
 
         AppView<AppInfoWithLiveness> appViewWithLiveness =
             appView.setAppInfo(
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index cb4f267..996355b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -42,6 +42,16 @@
   }
 
   @Override
+  public CfFieldInstruction asFieldInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isFieldInstruction() {
+    return true;
+  }
+
+  @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     String owner = lens.lookupInternalName(field.holder);
     String name = lens.lookupName(declaringField).toString();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index bd47c19..256853c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -25,6 +25,16 @@
   }
 
   @Override
+  public CfGoto asGoto() {
+    return this;
+  }
+
+  @Override
+  public boolean isGoto() {
+    return true;
+  }
+
+  @Override
   public CfLabel getTarget() {
     return target;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 09fcde1..53ea637 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -45,6 +45,62 @@
     return false;
   }
 
+  public CfFieldInstruction asFieldInstruction() {
+    return null;
+  }
+
+  public boolean isFieldInstruction() {
+    return false;
+  }
+
+  public CfGoto asGoto() {
+    return null;
+  }
+
+  public boolean isGoto() {
+    return false;
+  }
+
+  public CfInvoke asInvoke() {
+    return null;
+  }
+
+  public boolean isInvoke() {
+    return false;
+  }
+
+  public CfLabel asLabel() {
+    return null;
+  }
+
+  public boolean isLabel() {
+    return false;
+  }
+
+  public CfLoad asLoad() {
+    return null;
+  }
+
+  public boolean isLoad() {
+    return false;
+  }
+
+  public CfStore asStore() {
+    return null;
+  }
+
+  public boolean isStore() {
+    return false;
+  }
+
+  public CfSwitch asSwitch() {
+    return null;
+  }
+
+  public boolean isSwitch() {
+    return false;
+  }
+
   public CfDexItemBasedConstString asDexItemBasedConstString() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index db9cbfd..2cca2a8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -55,6 +55,16 @@
   }
 
   @Override
+  public CfInvoke asInvoke() {
+    return this;
+  }
+
+  @Override
+  public boolean isInvoke() {
+    return true;
+  }
+
+  @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     String owner = lens.lookupInternalName(method.holder);
     String name = lens.lookupName(method).toString();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index f094415..c6b2bd0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -28,6 +28,16 @@
   }
 
   @Override
+  public CfLabel asLabel() {
+    return this;
+  }
+
+  @Override
+  public boolean isLabel() {
+    return true;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 6736941..4067a79 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -47,6 +47,16 @@
   }
 
   @Override
+  public CfLoad asLoad() {
+    return this;
+  }
+
+  @Override
+  public boolean isLoad() {
+    return true;
+  }
+
+  @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitVarInsn(getLoadType(), var);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 3e84593..45f274a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -47,6 +47,16 @@
   }
 
   @Override
+  public CfStore asStore() {
+    return this;
+  }
+
+  @Override
+  public boolean isStore() {
+    return true;
+  }
+
+  @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitVarInsn(getStoreType(), var);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 0b1fdc9..3bba53c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -55,6 +55,16 @@
   }
 
   @Override
+  public CfSwitch asSwitch() {
+    return this;
+  }
+
+  @Override
+  public boolean isSwitch() {
+    return true;
+  }
+
+  @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     Label[] labels = new Label[targets.size()];
     for (int i = 0; i < targets.size(); i++) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
new file mode 100644
index 0000000..8ddc94e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -0,0 +1,160 @@
+// 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.graph.analysis;
+
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class ClassInitializerAssertionEnablingAnalysis extends EnqueuerAnalysis {
+  private final DexItemFactory dexItemFactory;
+  private final OptimizationFeedback feedback;
+
+  public ClassInitializerAssertionEnablingAnalysis(
+      DexItemFactory dexItemFactory, OptimizationFeedback feedback) {
+    this.dexItemFactory = dexItemFactory;
+    this.feedback = feedback;
+  }
+
+  @Override
+  public void processNewlyLiveMethod(DexEncodedMethod method) {
+    if (method.isClassInitializer()) {
+      if (method.getCode().isCfCode()) {
+        if (hasJavacClinitAssertionCode(method.getCode().asCfCode())) {
+          feedback.setInitializerEnablingJavaAssertions(method);
+        }
+      }
+    }
+  }
+
+  // The <clinit> instruction sequence generated by javac for classes which use assertions. The
+  // call to desiredAssertionStatus() will provide the value for the -ea option for the class.
+  //
+  //  0: ldc           #<n>                // Current class
+  //  2: invokevirtual #<n>                // Method java/lang/Class.desiredAssertionStatus:()Z
+  //  5: ifne          12
+  //  8: iconst_1
+  //  9: goto          13
+  // 12: iconst_0
+  // 13: putstatic     #<n>                // Field $assertionsDisabled:Z
+
+  // R8 processing with class file backend will rewrite this sequence to either of the following
+  // depending on debug or release mode.
+
+  //  2: ldc           #<n>                // Current class
+  //  3: invokevirtual #<n>                // Method java/lang/Class.desiredAssertionStatus:()Z
+  //  4: istore        0
+  //  5: iload         0
+  //  6: ldc           1
+  //  7: ixor
+  //  8: istore        0
+  //  9: iload         0
+  // 13: putstatic     #<n>                // Field $assertionsDisabled:Z
+
+  //  2: ldc           #<n>                // Current class
+  //  3: invokevirtual                     // Method java/lang/Class.desiredAssertionStatus:()Z
+  //  4: ldc           1
+  //  5: ixor
+  // 13: putstatic     #<n>                // Field $assertionsDisabled:Z
+
+  private static List<Class<?>> javacInstructionSequence =
+      ImmutableList.of(
+          CfIf.class,
+          CfConstNumber.class,
+          CfGoto.class,
+          CfConstNumber.class,
+          CfFieldInstruction.class);
+  private static List<Class<?>> r8InstructionSequence =
+      ImmutableList.of(CfConstNumber.class, CfLogicalBinop.class, CfFieldInstruction.class);
+
+  private boolean hasJavacClinitAssertionCode(CfCode code) {
+    for (int i = 0; i < code.instructions.size(); i++) {
+      CfInstruction instruction = code.instructions.get(i);
+      if (instruction.isInvoke()) {
+        // Check for the generated instruction sequence by looking for the call to
+        // desiredAssertionStatus() followed by the expected instruction types and finally checking
+        // the exact target of the putstatic ending the sequence.
+        CfInvoke invoke = instruction.asInvoke();
+        if (invoke.getOpcode() == Opcodes.INVOKEVIRTUAL
+            && invoke.getMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+          CfFieldInstruction fieldInstruction = isJavacInstructionSequence(code, i + 1);
+          if (fieldInstruction == null) {
+            fieldInstruction = isR8InstructionSequence(code, i + 1);
+          }
+          if (fieldInstruction != null) {
+            return fieldInstruction.getOpcode() == Opcodes.PUTSTATIC
+                && fieldInstruction.getField().name == dexItemFactory.assertionsDisabled;
+          }
+        }
+      }
+      // Only check initial straight line code.
+      if (isJumpInstruction(instruction)) {
+        return false;
+      }
+    }
+    return false;
+  }
+
+  private CfFieldInstruction isJavacInstructionSequence(CfCode code, int fromIndex) {
+    List<Class<?>> sequence = javacInstructionSequence;
+    int nextExpectedInstructionIndex = 0;
+    CfInstruction instruction = null;
+    for (int i = fromIndex;
+        i < code.instructions.size() && nextExpectedInstructionIndex < sequence.size();
+        i++) {
+      instruction = code.instructions.get(i);
+      if (instruction.isLabel()) {
+        // Just ignore labels.
+        continue;
+      }
+      if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
+        break;
+      }
+      nextExpectedInstructionIndex++;
+    }
+    return nextExpectedInstructionIndex == sequence.size()
+        ? instruction.asFieldInstruction()
+        : null;
+  }
+
+  private CfFieldInstruction isR8InstructionSequence(CfCode code, int fromIndex) {
+    List<Class<?>> sequence = r8InstructionSequence;
+    int nextExpectedInstructionIndex = 0;
+    CfInstruction instruction = null;
+    for (int i = fromIndex;
+        i < code.instructions.size() && nextExpectedInstructionIndex < sequence.size();
+        i++) {
+      instruction = code.instructions.get(i);
+      if (instruction.isStore() || instruction.isLoad()) {
+        // Just ignore stores and loads.
+        continue;
+      }
+      if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
+        break;
+      }
+      nextExpectedInstructionIndex++;
+    }
+    return nextExpectedInstructionIndex == sequence.size()
+        ? instruction.asFieldInstruction()
+        : null;
+  }
+
+  private boolean isJumpInstruction(CfInstruction instruction) {
+    return instruction.isConditionalJump()
+        || instruction.isGoto()
+        || instruction.isSwitch();
+  }
+}
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 d6c5a47..f684c38 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
@@ -1932,27 +1932,23 @@
    */
   public void disableAssertions(
       AppView<?> appView, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    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()) {
-      if (!hasJavacClinitAssertionCode(code)) {
-        return;
-      }
-      // Mark the clinit as having code to turn on assertions.
-      feedback.setInitializerEnablingJavaAssertions(method);
+      clinit = method;
     } else {
-      // 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.
       DexClass clazz = appView.definitionFor(method.method.holder);
       if (clazz == null) {
         return;
       }
-      DexEncodedMethod clinit = clazz.getClassInitializer();
-      if (clinit == null
-          || !clinit.isProcessed()
-          || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
-        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();
@@ -1975,78 +1971,6 @@
     }
   }
 
-  private boolean isClassDesiredAssertionStatusInvoke(Instruction instruction) {
-    return instruction.isInvokeMethod()
-    && instruction.asInvokeMethod().getInvokedMethod()
-        == dexItemFactory.classMethods.desiredAssertionStatus;
-  }
-
-  private boolean isAssertionsDisabledFieldPut(Instruction instruction) {
-    return instruction.isStaticPut()
-        && instruction.asStaticPut().getField().name == dexItemFactory.assertionsDisabled;
-  }
-
-  private boolean isNotDebugInstruction(Instruction instruction) {
-    return !instruction.isDebugInstruction();
-  }
-
-  private Value blockWithSingleConstNumberAndGoto(BasicBlock block) {
-    InstructionIterator iterator = block.iterator();
-    Instruction constNumber = iterator.nextUntil(this::isNotDebugInstruction);
-    if (constNumber == null || !constNumber.isConstNumber()) {
-      return null;
-    }
-    Instruction exit = iterator.nextUntil(this::isNotDebugInstruction);
-    return exit != null && exit.isGoto() ? constNumber.outValue() : null;
-  }
-
-  private Value blockWithAssertionsDisabledFieldPut(BasicBlock block) {
-    InstructionIterator iterator = block.iterator();
-    Instruction fieldPut = iterator.nextUntil(this::isNotDebugInstruction);
-    return fieldPut != null
-        && isAssertionsDisabledFieldPut(fieldPut) ? fieldPut.inValues().get(0) : null;
-  }
-
-  private boolean hasJavacClinitAssertionCode(IRCode code) {
-    InstructionIterator iterator = code.instructionIterator();
-    Instruction current = iterator.nextUntil(this::isClassDesiredAssertionStatusInvoke);
-    if (current == null) {
-      return false;
-    }
-
-    Value DesiredAssertionStatus = current.outValue();
-    assert iterator.hasNext();
-    current = iterator.next();
-    if (!current.isIf()
-        || !current.asIf().isZeroTest()
-        || current.asIf().inValues().get(0) != DesiredAssertionStatus) {
-      return false;
-    }
-
-    If theIf = current.asIf();
-    BasicBlock trueTarget = theIf.getTrueTarget();
-    BasicBlock falseTarget = theIf.fallthroughBlock();
-    if (trueTarget == falseTarget) {
-      return false;
-    }
-
-    Value trueValue = blockWithSingleConstNumberAndGoto(trueTarget);
-    Value falseValue = blockWithSingleConstNumberAndGoto(falseTarget);
-    if (trueValue == null
-        || falseValue == null
-        || (trueTarget.exit().asGoto().getTarget() != falseTarget.exit().asGoto().getTarget())) {
-      return false;
-    }
-
-    BasicBlock target = trueTarget.exit().asGoto().getTarget();
-    Value storeValue = blockWithAssertionsDisabledFieldPut(target);
-    return storeValue != null
-        && storeValue.isPhi()
-        && storeValue.asPhi().getOperands().size() == 2
-        && storeValue.asPhi().getOperands().contains(trueValue)
-        && storeValue.asPhi().getOperands().contains(falseValue);
-  }
-
   enum RemoveCheckCastInstructionIfTrivialResult {
     NO_REMOVALS,
     REMOVED_CAST_DO_NARROW
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 df88a4e..6fc3c91 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
@@ -8,8 +8,10 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.Constants;
@@ -20,6 +22,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
 import java.util.Collection;
 import java.util.function.Function;
 import org.junit.ClassRule;
@@ -233,6 +236,45 @@
         compileRegress110887293(RemoveAssertionsTest::identity));
   }
 
+  private void checkResultWithAssertionsPresent(TestCompileResult result) throws Exception {
+    if (parameters.isDexRuntime()) {
+      // Java assertions have no effect on Android (-ea is not implemented).
+      checkResultWithAssertionsRemoved(result);
+    } else {
+      String main = ClassWithAssertions.class.getCanonicalName();
+      result
+          .enableRuntimeAssertions()
+          .run(parameters.getRuntime(), main, "0")
+          .assertFailureWithOutput(StringUtils.lines("1"));
+      // Assertion is not hit.
+      result
+          .enableRuntimeAssertions()
+          .run(parameters.getRuntime(), main, "1")
+          .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+    }
+  }
+
+  private void checkResultWithAssertionsRemoved(TestCompileResult result) throws Exception {
+    String main = ClassWithAssertions.class.getCanonicalName();
+    result
+        .run(parameters.getRuntime(), main, "0")
+        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+    result
+        .run(parameters.getRuntime(), main, "1")
+        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+  }
+
+  private void checkResultWithChromiumAssertions(TestCompileResult result) throws Exception {
+    String main = ClassWithAssertions.class.getCanonicalName();
+    result
+        .run(parameters.getRuntime(), main, "0")
+        .assertSuccessWithOutput(
+            StringUtils.lines("1", "Got AssertionError java.lang.AssertionError", "2"));
+    result
+        .run(parameters.getRuntime(), main, "1")
+        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+  }
+
   @Test
   public void test() throws Exception {
     // TODO(mkroghj) Why does this fail on JDK?
@@ -254,51 +296,81 @@
   @Test
   public void testCfOutput() throws Exception {
     assumeTrue(parameters.isCfRuntime());
-    String main = ClassWithAssertions.class.getCanonicalName();
     CompilationResults results = compilationResults.apply(parameters.getBackend());
     // Assertion is hit.
-    results
-        .withAssertions
-        .enableRuntimeAssertions()
-        .run(parameters.getRuntime(), main, "0")
-        .assertFailureWithOutput(StringUtils.lines("1"));
-    // Assertion is not hit.
-    results
-        .withAssertions
-        .enableRuntimeAssertions()
-        .run(parameters.getRuntime(), main, "1")
-        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+    checkResultWithAssertionsPresent(results.withAssertions);
     // Assertion is hit, but removed.
-    results
-        .withoutAssertions
-        .enableRuntimeAssertions()
-        .run(parameters.getRuntime(), main, "0")
-        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+    checkResultWithAssertionsRemoved(results.withoutAssertions);
   }
 
   @Test
   public void regress110887293() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    String main = ClassWithAssertions.class.getCanonicalName();
     CompilationResults results = compilationResults.apply(parameters.getBackend());
     // Assertions removed for default assertion code.
-    results
-        .withoutAssertions
-        .run(parameters.getRuntime(), main, "0")
-        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
-    results
-        .withoutAssertions
-        .run(parameters.getRuntime(), main, "1")
-        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+    checkResultWithAssertionsRemoved(results.withoutAssertions);
     // Assertions not removed when default assertion code is not present.
-    results
-        .withAssertions
-        .run(parameters.getRuntime(), main, "0")
-        .assertSuccessWithOutput(
-            StringUtils.lines("1", "Got AssertionError java.lang.AssertionError", "2"));
-    results
-        .withAssertions
-        .run(parameters.getRuntime(), main, "1")
-        .assertSuccessWithOutput(StringUtils.lines("1", "2"));
+    checkResultWithChromiumAssertions(results.withAssertions);
+  }
+
+  private D8TestCompileResult compileD8(boolean disableAssertions)
+      throws CompilationFailedException {
+    return testForD8()
+        .addProgramClassFileData(ClassWithAssertionsDump.dump())
+        .debug()
+        .setMinApi(AndroidApiLevel.B)
+        .addOptionsModification(o -> o.disableAssertions = disableAssertions)
+        .compile();
+  }
+
+  private D8TestCompileResult compileR8FollowedByD8(boolean disableAssertions) throws Exception {
+    Path x =
+        testForR8(Backend.CF)
+            .addProgramClassFileData(ClassWithAssertionsDump.dump())
+            .debug()
+            .setMinApi(AndroidApiLevel.B)
+            .noTreeShaking()
+            .noMinification()
+            .compile()
+            .writeToZip();
+
+    return testForD8()
+        .addProgramFiles(x)
+        .debug()
+        .setMinApi(AndroidApiLevel.B)
+        .addOptionsModification(o -> o.disableAssertions = disableAssertions)
+        .compile();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    checkResultWithAssertionsRemoved(compileD8(true));
+    checkResultWithAssertionsPresent(compileD8(false));
+  }
+
+  private D8TestCompileResult compileD8Regress110887293(Function<byte[], byte[]> rewriter)
+      throws CompilationFailedException {
+    return testForD8()
+        .addProgramClassFileData(
+            rewriter.apply(ClassWithAssertionsDump.dump()),
+            rewriter.apply(ChromuimAssertionHookMockDump.dump()))
+        .debug()
+        .setMinApi(AndroidApiLevel.B)
+        .compile();
+  }
+
+  @Test
+  public void testD8Regress110887293() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    checkResultWithChromiumAssertions(
+        compileD8Regress110887293(RemoveAssertionsTest::chromiumAssertionEnabler));
+  }
+
+  @Test
+  public void testR8FollowedByD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    checkResultWithAssertionsRemoved(compileR8FollowedByD8(true));
+    checkResultWithAssertionsPresent(compileR8FollowedByD8(false));
   }
 }