Update catch handlers after class merging
If an exception class A is merged into another exception class B, then all exception tables should be updated, and class A should be removed entirely.
Bug: 73958515
Change-Id: I596681b1a80837243cd6d78fb8df21e2e433a284
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 831edbe..5f0fb83 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -264,9 +264,10 @@
public void setCode(
IRCode ir,
+ GraphLense graphLense,
RegisterAllocator registerAllocator,
InternalOptions options) {
- final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
+ final DexBuilder builder = new DexBuilder(ir, graphLense, registerAllocator, options);
code = builder.build(method.getArity());
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 8201562..ee7df48 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -43,6 +43,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
@@ -81,6 +82,10 @@
// The IR representation of the code to build.
private final IRCode ir;
+ // Graph lense for building the exception handlers. Needed since program classes that inherit
+ // from Throwable may get merged into their subtypes during class merging.
+ private final GraphLense graphLense;
+
// The register allocator providing register assignments for the code to build.
private final RegisterAllocator registerAllocator;
@@ -116,11 +121,14 @@
public DexBuilder(
IRCode ir,
+ GraphLense graphLense,
RegisterAllocator registerAllocator,
InternalOptions options) {
assert ir != null;
+ assert graphLense != null;
assert registerAllocator != null;
this.ir = ir;
+ this.graphLense = graphLense;
this.registerAllocator = registerAllocator;
this.options = options;
}
@@ -706,7 +714,12 @@
assert i == handlerGroup.getGuards().size() - 1;
catchAllOffset = targetOffset;
} else {
- pairs.add(new TypeAddrPair(type, targetOffset));
+ // The type may have changed due to class merging.
+ // TODO(christofferqa): This assumes that the graph lense is context insensitive for the
+ // given type (which is always the case). Consider removing the context-argument from
+ // GraphLense.lookupType, since we do not currently have a use case for it.
+ DexType actualType = graphLense.lookupType(type, null);
+ pairs.add(new TypeAddrPair(actualType, targetOffset));
}
}
TypeAddrPair[] pairsArray = pairs.toArray(new TypeAddrPair[pairs.size()]);
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 81dc436..df4e69c 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
@@ -532,7 +532,7 @@
assert code.isConsistentSSA();
code.traceBlocks();
RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
- method.setCode(code, registerAllocator, options);
+ method.setCode(code, graphLense, registerAllocator, options);
if (Log.ENABLED) {
Log.debug(getClass(), "Resulting dex code for %s:\n%s",
method.toSourceString(), logCode(options, method));
@@ -784,7 +784,7 @@
private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
// Perform register allocation.
RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
- method.setCode(code, registerAllocator, options);
+ method.setCode(code, graphLense, registerAllocator, options);
updateHighestSortingStrings(method);
if (Log.ENABLED) {
Log.debug(getClass(), "Resulting dex code for %s:\n%s",
diff --git a/src/test/examples/classmerging/ExceptionTest.java b/src/test/examples/classmerging/ExceptionTest.java
new file mode 100644
index 0000000..b365b2a
--- /dev/null
+++ b/src/test/examples/classmerging/ExceptionTest.java
@@ -0,0 +1,29 @@
+// 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 classmerging;
+
+public class ExceptionTest {
+ public static void main(String[] args) {
+ // The following will lead to a catch handler for ExceptionA, which is merged into ExceptionB.
+ try {
+ throw new ExceptionB("Ouch!");
+ } catch (ExceptionA exception) {
+ System.out.println("Caught exception: " + exception.getMessage());
+ }
+ }
+
+ // Will be merged into ExceptionB when class merging is enabled.
+ public static class ExceptionA extends Exception {
+ public ExceptionA(String message) {
+ super(message);
+ }
+ }
+
+ public static class ExceptionB extends ExceptionA {
+ public ExceptionB(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 1cc5b87..f9e6d52 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
-keep public class classmerging.Test {
public static void main(...);
}
+-keep public class classmerging.ExceptionTest {
+ public static void main(...);
+}
# TODO(herhut): Consider supporting merging of inner-class attributes.
# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index fbf2d02..5200a09 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -151,6 +151,11 @@
return builder.build();
}
+ /** Build an AndroidApp from the specified program files. */
+ protected AndroidApp readProgramFiles(Path... programFiles) throws IOException {
+ return AndroidApp.builder().addProgramFiles(programFiles).build();
+ }
+
/**
* Create a temporary JAR file containing the specified test classes.
*/
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index d5d2c39..69f925c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -3,29 +3,34 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.classmerging;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationFailedException;
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.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-public class ClassMergingTest {
+public class ClassMergingTest extends TestBase {
+ private static final Path CF_DIR =
+ Paths.get(ToolHelper.BUILD_DIR).resolve("classes/examples/classmerging");
private static final Path EXAMPLE_JAR = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
.resolve("classmerging.jar");
private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
@@ -33,17 +38,14 @@
private static final Path DONT_OPTIMIZE = Paths.get(ToolHelper.EXAMPLES_DIR)
.resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
private void configure(InternalOptions options) {
options.enableClassMerging = true;
options.enableClassInlining = false;
+ options.enableMinification = false;
}
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
- throws IOException, ProguardRuleParserException, ExecutionException,
- CompilationFailedException {
+ throws IOException, ExecutionException, CompilationFailedException {
ToolHelper.runR8(
R8Command.builder()
.setOutput(Paths.get(temp.getRoot().getCanonicalPath()), OutputMode.DexIndexed)
@@ -73,13 +75,12 @@
assertFalse(inspector.clazz(candidate).isPresent());
}
assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
- assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
}
@Test
- public void testClassesShouldNotMerged() throws Exception {
+ public void testClassesHaveNotBeenMerged() throws Exception {
runR8(DONT_OPTIMIZE, null);
for (String candidate : CAN_BE_MERGED) {
assertTrue(inspector.clazz(candidate).isPresent());
@@ -100,4 +101,39 @@
assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
}
+ // If an exception class A is merged into another exception class B, then all exception tables
+ // should be updated, and class A should be removed entirely.
+ @Test
+ public void testExceptionTables() throws Exception {
+ String main = "classmerging.ExceptionTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ExceptionTest.class"),
+ CF_DIR.resolve("ExceptionTest$ExceptionA.class"),
+ CF_DIR.resolve("ExceptionTest$ExceptionB.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of("classmerging.ExceptionTest", "classmerging.ExceptionTest$ExceptionB");
+ runTest(main, programFiles, preservedClassNames);
+ }
+
+ private void runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
+ throws Exception {
+ AndroidApp input = readProgramFiles(programFiles);
+ AndroidApp output = compileWithR8(input, EXAMPLE_KEEP, this::configure);
+ DexInspector inspector = new DexInspector(output);
+ // Check that all classes in [preservedClassNames] are in fact preserved.
+ for (String className : preservedClassNames) {
+ assertTrue(
+ "Class " + className + " should be present", inspector.clazz(className).isPresent());
+ }
+ // Check that all other classes have been removed.
+ for (FoundClassSubject classSubject : inspector.allClasses()) {
+ String className = classSubject.getDexClass().toSourceString();
+ assertTrue(
+ "Class " + className + " should be absent", preservedClassNames.contains(className));
+ }
+ // Check that the R8-generated code produces the same result as D8-generated code.
+ assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 4214a67..d6e3612 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.CatchHandlers;
@@ -652,7 +653,7 @@
code);
IRCode ir = code.buildIR(method, null, options, Origin.unknown());
RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
- method.setCode(ir, allocator, options);
+ method.setCode(ir, GraphLense.getIdentityLense(), allocator, options);
directMethods[i] = method;
}
builder.addProgramClass(