Avoid merging classes if target has static initialization
Change-Id: I7fb2e1a1c97eb9748244d8a9b087d5a6613229c4
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index c76865e..d9940f3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -448,6 +448,13 @@
}
public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+ return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+ }
+
+ public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
+ if (ignore.test(type)) {
+ return false;
+ }
if (hasNonTrivialClassInitializer()) {
return true;
}
@@ -455,11 +462,11 @@
return true;
}
for (DexType iface : interfaces.values) {
- if (iface.classInitializationMayHaveSideEffects(appInfo)) {
+ if (iface.classInitializationMayHaveSideEffects(appInfo, ignore)) {
return true;
}
}
- if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo)) {
+ if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo, ignore)) {
return true;
}
return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 31edee9..0c48ea9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -23,6 +23,7 @@
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
public class DexType extends DexReference implements PresortedComparable<DexType> {
@@ -119,9 +120,9 @@
return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
}
- public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+ public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
DexClass clazz = appInfo.definitionFor(this);
- return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo);
+ return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo, ignore);
}
public boolean isUnknown() {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index bc22da0..b93c867 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -372,16 +372,16 @@
public DexEncodedMethod mapDexEncodedMethod(
AppInfo appInfo, DexEncodedMethod originalEncodedMethod) {
DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
- if (newMethod != originalEncodedMethod.method) {
- // We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
- // updated either yet.
- DexClass newHolder = appInfo.definitionFor(newMethod.holder);
- assert newHolder != null;
- DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
- assert newEncodedMethod != null;
- return newEncodedMethod;
- }
- return originalEncodedMethod;
+ // Note that:
+ // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
+ // up, since `originalEncodedMethod` may be obsolete.
+ // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
+ // updated either yet.
+ DexClass newHolder = appInfo.definitionFor(newMethod.holder);
+ assert newHolder != null;
+ DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
+ assert newEncodedMethod != null;
+ return newEncodedMethod;
}
public abstract DexType lookupType(DexType type);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 9c3bcc3..3ef167c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -330,8 +330,7 @@
if (type.isPrimitiveType()) {
return PrimitiveTypeLatticeElement.fromDexType(type, asArrayElementType);
}
- return appInfo.dexItemFactory.createReferenceTypeLatticeElement(
- type, nullability, appInfo);
+ return appInfo.dexItemFactory.createReferenceTypeLatticeElement(type, nullability, appInfo);
}
public boolean isValueTypeCompatible(TypeLatticeElement other) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
index a8616fb..b708ae9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.desugar;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.GraphLense;
@@ -13,14 +11,11 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
-import java.util.Map;
class InterfaceMethodDesugaringLense extends NestedGraphLense {
- private final Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode;
InterfaceMethodDesugaringLense(
BiMap<DexMethod, DexMethod> methodMapping,
- Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode,
GraphLense previous, DexItemFactory factory) {
super(
ImmutableMap.of(),
@@ -30,12 +25,5 @@
methodMapping.inverse(),
previous,
factory);
- this.methodsWithMovedCode = methodsWithMovedCode;
- }
-
- @Override
- public DexEncodedMethod mapDexEncodedMethod(AppInfo appInfo, DexEncodedMethod original) {
- return super.mapDexEncodedMethod(
- appInfo, methodsWithMovedCode.getOrDefault(original, original));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 7a19fb8..10cbabb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -428,7 +428,6 @@
converter.appView.setGraphLense(
new InterfaceMethodDesugaringLense(
processor.movedMethods,
- processor.methodsWithMovedCode,
converter.appView.graphLense(),
factory));
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 956d6bd..15dcafa 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -424,7 +424,8 @@
return false;
}
DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
- if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+ if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
+ || targetClass.classInitializationMayHaveSideEffects(appInfo, type -> type == clazz.type)) {
// TODO(herhut): Handle class initializers.
if (Log.ENABLED) {
AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
new file mode 100644
index 0000000..ce89fe1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
@@ -0,0 +1,73 @@
+// 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.classmerging;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticInitializerTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public StaticInitializerTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = StringUtils.lines("In A.m()", "In B.<clinit>()", "In B.m()");
+
+ if (backend == Backend.CF) {
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+ }
+
+ testForR8(backend)
+ .addInnerClasses(StaticInitializerTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A.m();
+ B.m();
+ }
+ }
+
+ // Cannot be merged into B because that would change the semantics due to <clinit>.
+ static class A {
+
+ @NeverInline
+ public static void m() {
+ System.out.println("In A.m()");
+ }
+ }
+
+ static class B extends A {
+
+ static {
+ System.out.println("In B.<clinit>()");
+ }
+
+ @NeverInline
+ public static void m() {
+ System.out.println("In B.m()");
+ }
+ }
+}