Ignore JaCoCo instrumentation when analyzing assertions enabling code
Bug: 169866678
Bug: 170197605
Change-Id: I13f97aed2bb801c5d7ff104bdef4b187fb297462
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
index d60a589..eb82383 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -4,12 +4,14 @@
package com.android.tools.r8.graph.analysis;
+import com.android.tools.r8.cf.code.CfArrayStore;
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.CfLoad;
import com.android.tools.r8.cf.code.CfLogicalBinop;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
@@ -90,6 +92,15 @@
// 16: invokevirtual #43 // Method java/lang/Class.desiredAssertionStatus:()Z
// 19: putstatic #45 // Field ENABLED:Z
// 22: return
+ //
+ // When JaCoCo instrumentation is applied the following instruction sequence is injected into
+ // each branch to register code coverage. This includes <clinit> where the vsalue for field
+ // $assertionsDisabled is determined by a branch structure.
+ //
+ // +0: aload_X
+ // +1: iconst_Y / ldc
+ // +2: iconst_Z / ldc
+ // +3 bastore
private static List<Class<?>> javacInstructionSequence =
ImmutableList.of(
@@ -100,6 +111,8 @@
CfFieldInstruction.class);
private static List<Class<?>> r8InstructionSequence =
ImmutableList.of(CfConstNumber.class, CfLogicalBinop.class, CfFieldInstruction.class);
+ private static List<Class<?>> jacocoInstructionSequence =
+ ImmutableList.of(CfLoad.class, CfConstNumber.class, CfConstNumber.class, CfArrayStore.class);
private boolean hasJavacClinitAssertionCode(CfCode code) {
for (int i = 0; i < code.getInstructions().size(); i++) {
@@ -160,6 +173,17 @@
return false;
}
+ private boolean skipSequence(List<Class<?>> sequence, CfCode code, int fromIndex) {
+ int i = fromIndex;
+ for (; i < code.getInstructions().size() && i - fromIndex < sequence.size(); i++) {
+ CfInstruction instruction = code.getInstructions().get(i);
+ if (instruction.getClass() != sequence.get(i - fromIndex)) {
+ return false;
+ }
+ }
+ return i - fromIndex == sequence.size();
+ }
+
private CfFieldInstruction isJavacInstructionSequence(CfCode code, int fromIndex) {
List<Class<?>> sequence = javacInstructionSequence;
int nextExpectedInstructionIndex = 0;
@@ -168,12 +192,27 @@
i < code.getInstructions().size() && nextExpectedInstructionIndex < sequence.size();
i++) {
instruction = code.getInstructions().get(i);
- if (instruction.isLabel() || instruction.isFrame()) {
- // Just ignore labels and frames.
+ if (instruction.isLabel() || instruction.isFrame() || instruction.isPosition()) {
+ // Just ignore labels, frames and positions.
continue;
}
if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
- break;
+ // Skip injected JaCoCo injected code.
+ if (instruction.getClass() == jacocoInstructionSequence.get(0)
+ && skipSequence(jacocoInstructionSequence, code, i)) {
+ i += jacocoInstructionSequence.size();
+ if (i >= code.getInstructions().size()) {
+ break;
+ }
+ instruction = code.getInstructions().get(i);
+ }
+ if (instruction.isLabel() || instruction.isFrame() || instruction.isPosition()) {
+ // Just ignore labels, frames and positions.
+ continue;
+ }
+ if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
+ break;
+ }
}
nextExpectedInstructionIndex++;
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsCheckerUtils.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsCheckerUtils.java
new file mode 100644
index 0000000..c9236d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsCheckerUtils.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2020, 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 com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
+
+public class AssertionsCheckerUtils {
+
+ static void checkAssertionCodeEnabled(ClassSubject subject, String methodName) {
+ MatcherAssert.assertThat(subject, Matchers.isPresent());
+ // <clinit> is removed by R8.
+ if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
+ Assert.assertFalse(
+ subject
+ .uniqueMethodWithName("<clinit>")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isStaticPut));
+ }
+ Assert.assertTrue(
+ subject
+ .uniqueMethodWithName(methodName)
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isThrow));
+ }
+
+ private AssertionsCheckerUtils() {}
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
index 159ee81..11829f6 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.rewrite.assertions.testclasses.A;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -41,6 +41,10 @@
this.parameters = parameters;
}
+ private void checkAssertionCodeEnabled(CodeInspector inspector) {
+ AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz(A.class), "m");
+ }
+
@Test
public void testD8() throws Exception {
Assume.assumeTrue(parameters.isDexRuntime());
@@ -49,10 +53,9 @@
.addProgramClassFileData(transformClassWithJacocoInstrumentation(A.class))
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
- .compile()
.run(parameters.getRuntime(), TestClass.class)
- // TODO(b/169866678): Should be "AssertionError in A".
- .assertSuccessWithOutput(StringUtils.lines());
+ .inspect(this::checkAssertionCodeEnabled)
+ .assertSuccessWithOutputLines("AssertionError in A");
}
@Test
@@ -64,10 +67,9 @@
.addKeepClassAndMembersRules(A.class, MockJacocoInit.class)
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
- .compile()
.run(parameters.getRuntime(), TestClass.class)
- // TODO(b/169866678): Should be "AssertionError in A".
- .assertSuccessWithOutput(StringUtils.lines());
+ .inspect(this::checkAssertionCodeEnabled)
+ .assertSuccessWithOutputLines("AssertionError in A");
}
private byte[] transformClassWithJacocoInstrumentation(Class<?> clazz) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index 01c28c1..064cfdb 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -143,25 +143,8 @@
checkAssertionCodeRemoved(inspector.clazz(clazz));
}
- private void checkAssertionCodeEnabled(ClassSubject subject) {
- 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 checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
- checkAssertionCodeEnabled(inspector.clazz(clazz));
+ AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz(clazz), "m");
}
private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) {
@@ -421,8 +404,8 @@
.compile()
.inspect(
inspector -> {
- checkAssertionCodeEnabled(inspector.clazz("Class1"));
- checkAssertionCodeEnabled(inspector.clazz("Class2"));
+ AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz("Class1"), "m");
+ AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz("Class2"), "m");
checkAssertionCodeRemoved(inspector.clazz(class1));
checkAssertionCodeRemoved(inspector.clazz(class2));
})
@@ -472,8 +455,10 @@
.compile()
.inspect(
inspector -> {
- checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.class));
- checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.InnerClass.class));
+ AssertionsCheckerUtils.checkAssertionCodeEnabled(
+ inspector.clazz(TestClassForInnerClass.class), "m");
+ AssertionsCheckerUtils.checkAssertionCodeEnabled(
+ inspector.clazz(TestClassForInnerClass.InnerClass.class), "m");
})
.run(parameters.getRuntime(), TestClassForInnerClass.class)
.assertSuccessWithOutputLines(