Version 1.2.34.

Merges in changes needed for certain bytecode rewritten tests that
enable assertions programatically and produces invalid locals
information.

Merge: Strip invalid locals information instead of failing compilation.
CL: https://r8-review.googlesource.com/c/r8/+/24240

Merge: Keep assertion code when required.
CL: https://r8-review.googlesource.com/c/r8/+/23921
Change-Id: I6dc8f0785e3c03814c099adbe8825e9267df0746
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9fcca89..709e4f7 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -611,6 +611,13 @@
     internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
     internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
 
+    // 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.disableAssertions;
+    if (internal.isGeneratingClassFiles()) {
+      internal.disableAssertions = false;
+    }
+
     // EXPERIMENTAL flags.
     assert !internal.forceProguardCompatibility;
     internal.forceProguardCompatibility = forceProguardCompatibility;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 5d9b912..2629bf7 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.2.33";
+  public static final String LABEL = "1.2.34";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 026c2b5..0d47537 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -601,6 +601,7 @@
     // Stores information about instance methods and constructors for
     // class inliner, null value indicates that the method is not eligible.
     private ClassInlinerEligibility classInlinerEligibility = null;
+    private boolean initializerEnablingJavaAssertions = false;
 
     private OptimizationInfo() {
       // Intentionally left empty.
@@ -645,6 +646,14 @@
       return this.classInlinerEligibility;
     }
 
+    private void setInitializerEnablingJavaAssertions() {
+      this.initializerEnablingJavaAssertions = true;
+    }
+
+    public boolean isInitializerEnablingJavaAssertions() {
+      return initializerEnablingJavaAssertions;
+    }
+
     public long getReturnedConstant() {
       assert returnsConstant();
       return returnedConstant;
@@ -747,6 +756,10 @@
     ensureMutableOI().setClassInlinerEligibility(eligibility);
   }
 
+  synchronized public void setInitializerEnablingJavaAssertions() {
+    ensureMutableOI().setInitializerEnablingJavaAssertions();
+  }
+
   synchronized public void markForceInline() {
     ensureMutableOI().markForceInline();
   }
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 65c59ae..ccbdf67 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
@@ -103,6 +103,7 @@
   private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
 
   private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
+  private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
   private DexString highestSortingString;
 
   private IRConverter(
@@ -349,24 +350,34 @@
       ExecutorService executor) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
-      futures.add(
-          executor.submit(
-              () -> {
-                clazz.forEachMethodThrowing(this::convertMethodToDex);
-                return null; // we want a Callable not a Runnable to be able to throw
-              }));
+      futures.add(executor.submit(() -> convertMethodsToDex(clazz)));
     }
     ThreadUtils.awaitFutures(futures);
   }
 
-  void convertMethodToDex(DexEncodedMethod method) {
+  private void convertMethodsToDex(DexProgramClass clazz) {
+    // When converting all methods on a class always convert <clinit> first.
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      if (method.isClassInitializer()) {
+        convertMethodToDex(method);
+        break;
+      }
+    }
+    clazz.forEachMethod(method -> {
+      if (!method.isClassInitializer()) {
+        convertMethodToDex(method);
+      }
+    });
+  }
+
+  private void convertMethodToDex(DexEncodedMethod method) {
     assert options.isGeneratingDex();
     if (method.getCode() != null) {
       boolean matchesMethodFilter = options.methodMatchesFilter(method);
       if (matchesMethodFilter) {
         if (!(options.passthroughDexCode && method.getCode().isDexCode())) {
           // We do not process in call graph order, so anything could be a leaf.
-          rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+          rewriteCode(method, simpleOptimizationFeedback, x -> true, CallSiteInformation.empty(),
               Outliner::noProcessing);
         }
         updateHighestSortingStrings(method);
@@ -659,7 +670,7 @@
       codeRewriter.removeSwitchMaps(code);
     }
     if (options.disableAssertions) {
-      codeRewriter.disableAssertions(code);
+      codeRewriter.disableAssertions(appInfo, method, code, feedback);
     }
     if (options.enableNonNullTracking && nonNullTracker != null) {
       nonNullTracker.addNonNull(code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 99bca1c..b67d12e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -339,6 +339,12 @@
     }
     int start = source.getOffset(node.start);
     int end = source.getOffset(node.end);
+    // If the locals information is invalid the node start or end could be a label that does
+    // not exist in the program.
+    if (start == -1 || end == -1) {
+      throw new InvalidDebugInfoException(
+          "Locals information for '" + node.name + "' has undefined start or end point.");
+    }
     // Ensure that there is an entry at the starting point of the local.
     {
       LocalsAtOffset atStart;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index 16a77c8..0c9fb42 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -17,4 +17,5 @@
   void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
+  void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 7303b6b..254c699 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -50,4 +50,8 @@
       DexEncodedMethod method, ClassInlinerEligibility eligibility) {
     method.setClassInlinerEligibility(eligibility);
   }
+  @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+    method.setInitializerEnablingJavaAssertions();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 6915a2f..98a289b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -35,4 +35,8 @@
   public void setClassInlinerEligibility(
       DexEncodedMethod method, ClassInlinerEligibility eligibility) {
   }
+
+  @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
new file mode 100644
index 0000000..12456f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2017, 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.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+
+public class OptimizationFeedbackSimple implements OptimizationFeedback {
+
+  @Override
+  public void methodReturnsArgument(DexEncodedMethod method, int argument) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodReturnsConstant(DexEncodedMethod method, long value) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodNeverReturnsNull(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodNeverReturnsNormally(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
+  public void markProcessed(DexEncodedMethod method, Constraint state) {
+    // Just as processed, don't provide any inlining constraints.
+    method.markProcessed(Constraint.NEVER);
+  }
+
+  @Override
+  public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+    // Ignored.
+  }
+
+  @Override
+  public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+    // Ignored.
+  }
+
+  @Override
+  public void setClassInlinerEligibility(
+      DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+    // Ignored.
+  }
+
+  @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+    method.setInitializerEnablingJavaAssertions();
+  }
+}
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 bc083c9..672fb4f 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
@@ -1041,7 +1041,29 @@
    * }
    * </pre>
    */
-  public void disableAssertions(IRCode code) {
+  public void disableAssertions(
+      AppInfo appInfo, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    if (method.isClassInitializer()) {
+      if (!hasJavacClinitAssertionCode(code)) {
+        return;
+      }
+      // Mark the clinit as having code to turn on assertions.
+      feedback.setInitializerEnablingJavaAssertions(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 = appInfo.definitionFor(method.method.holder);
+      if (clazz == null) {
+        return;
+      }
+      DexEncodedMethod clinit = clazz.getClassInitializer();
+      if (clinit == null
+          || !clinit.isProcessed()
+          || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+        return;
+      }
+    }
+
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
@@ -1064,6 +1086,78 @@
     }
   }
 
+  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);
+  }
+
   // Check if the static put is a constant derived from the class holding the method.
   // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
   private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
new file mode 100644
index 0000000..772ef09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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;
+
+public class ChromuimAssertionHookMock {
+  public static void assertFailureHandler(AssertionError assertion) {
+    System.out.println("Got AssertionError " + assertion);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
index cc56790..49fe85e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
@@ -16,7 +16,9 @@
   }
 
   int getX() {
+    System.out.println("1");
     assert condition();
+    System.out.println("2");
     return x;
   }
 
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 3c724c0..c118bad 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
@@ -4,17 +4,139 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// This ASM class visitor has been adapted from
+// https://chromium.googlesource.com/chromium/src/+/164e81fcd0828b40f5496e9025349ea728cde7f5/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java
+// See b/110887293.
+
+/**
+ * An ClassVisitor for replacing Java ASSERT statements with a function by modifying Java bytecode.
+ *
+ * We do this in two steps, first step is to enable assert.
+ * Following bytecode is generated for each class with ASSERT statements:
+ * 0: ldc #8 // class CLASSNAME
+ * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z
+ * 5: ifne 12
+ * 8: iconst_1
+ * 9: goto 13
+ * 12: iconst_0
+ * 13: putstatic #2 // Field $assertionsDisabled:Z
+ * Replaces line #13 to the following:
+ * 13: pop
+ * Consequently, $assertionsDisabled is assigned the default value FALSE.
+ * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert.
+ *
+ * Second step is to replace assert statement with a function:
+ * The followed instructions are generated by a java assert statement:
+ * getstatic     #3     // Field $assertionsDisabled:Z
+ * ifne          118    // Jump to instruction as if assertion if not enabled
+ * ...
+ * ifne          19
+ * new           #4     // class java/lang/AssertionError
+ * dup
+ * ldc           #5     // String (don't have this line if no assert message given)
+ * invokespecial #6     // Method java/lang/AssertionError.
+ * athrow
+ * Replace athrow with:
+ * invokestatic  #7     // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler
+ * goto          118
+ * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError,
+ * 118 is the instruction to execute as if assertion if not enabled.
+ */
+class AssertionEnablerClassAdapter extends ClassVisitor {
+  AssertionEnablerClassAdapter(ClassVisitor visitor) {
+    super(Opcodes.ASM6, visitor);
+  }
+
+  @Override
+  public MethodVisitor visitMethod(final int access, final String name, String desc,
+      String signature, String[] exceptions) {
+    return new RewriteAssertMethodVisitor(
+        Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions));
+  }
+
+  static class RewriteAssertMethodVisitor extends MethodVisitor {
+    static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled";
+    static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler";
+    static final String INSERT_INSTRUCTION_DESC =
+        Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("java/lang/AssertionError"));
+    static final boolean INSERT_INSTRUCTION_ITF = false;
+
+    boolean mStartLoadingAssert;
+    Label mGotoLabel;
+
+    public RewriteAssertMethodVisitor(int api, MethodVisitor mv) {
+      super(api, mv);
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+      if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+        super.visitInsn(Opcodes.POP); // enable assert
+      } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+        mStartLoadingAssert = true;
+        super.visitFieldInsn(opcode, owner, name, desc);
+      } else {
+        super.visitFieldInsn(opcode, owner, name, desc);
+      }
+    }
+
+    @Override
+    public void visitJumpInsn(int opcode, Label label) {
+      if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) {
+        mGotoLabel = label;
+      }
+      super.visitJumpInsn(opcode, label);
+    }
+
+    @Override
+    public void visitInsn(int opcode) {
+      if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) {
+        super.visitInsn(opcode);
+      } else {
+        super.visitMethodInsn(
+            Opcodes.INVOKESTATIC,
+            ChromuimAssertionHookMock.class.getCanonicalName().replace('.', '/'),
+            INSERT_INSTRUCTION_NAME,
+            INSERT_INSTRUCTION_DESC,
+            INSERT_INSTRUCTION_ITF);
+        super.visitJumpInsn(Opcodes.GOTO, mGotoLabel);
+        mStartLoadingAssert = false;
+        mGotoLabel = null;
+      }
+    }
+  }
+}
 
 public class RemoveAssertionsTest extends TestBase {
 
@@ -38,4 +160,73 @@
         clazz.method(new MethodSignature(Constants.CLASS_INITIALIZER_NAME, "void", new String[]{}));
     assertTrue(!clinit.isPresent());
   }
+
+  private Path buildTestToCf(Consumer<InternalOptions> consumer) throws Exception {
+    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(readClasses(ClassWithAssertions.class))
+            .setMode(CompilationMode.DEBUG)
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .build();
+    ToolHelper.runR8(command, consumer);
+    return outputJar;
+  }
+
+  @Test
+  public void testCfOutput() throws Exception {
+    String main = ClassWithAssertions.class.getCanonicalName();
+    ProcessResult result;
+    // Assertion is hit.
+    result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "0");
+    assertEquals(1, result.exitCode);
+    assertEquals("1\n", result.stdout);
+    // Assertion is not hit.
+    result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "1");
+    assertEquals(0, result.exitCode);
+    assertEquals("1\n2\n", result.stdout);
+    // Assertion is hit, but removed.
+    result = ToolHelper.runJava(
+        buildTestToCf(
+            options -> options.disableAssertions = true), "-ea", main, "0");
+    assertEquals(0, result.exitCode);
+    assertEquals("1\n2\n", result.stdout);
+  }
+
+  private byte[] identity(byte[] classBytes) {
+    return classBytes;
+  }
+
+  private byte[] chromiumAssertionEnabler(byte[] classBytes) {
+    ClassWriter writer = new ClassWriter(0);
+    new ClassReader(classBytes).accept(new AssertionEnablerClassAdapter(writer), 0);
+    return writer.toByteArray();
+  }
+
+  private AndroidApp runRegress110887293(Function<byte[], byte[]> rewriter) throws Exception {
+    return ToolHelper.runR8(
+        R8Command.builder()
+            .addClassProgramData(
+                rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)),
+                Origin.unknown())
+            .addClassProgramData(
+                ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class), Origin.unknown())
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .setMode(CompilationMode.DEBUG)
+            .build());
+  }
+
+  @Test
+  public void regress110887293() throws Exception {
+    AndroidApp app;
+    // Assertions removed for default assertion code.
+    app = runRegress110887293(this::identity);
+    assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+    assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+    // Assertions not removed when default assertion code is not present.
+    app = runRegress110887293(this::chromiumAssertionEnabler);
+    assertEquals(
+        "1\nGot AssertionError java.lang.AssertionError\n2\n",
+        runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+    assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+  }
 }