Add a test for not staticizing synchronized methods
Change-Id: I6530b6dd615d47fda74de4a523637db232e2dfa9
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 0372a33..0231d43 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult;
@@ -38,7 +37,6 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
@@ -132,23 +130,28 @@
DexType type = field.field.type;
if (singletonFields.put(type, field) != null) {
// There is already candidate singleton field found.
- notEligible.add(type);
+ markNotEligible(type, notEligible);
}
}
// Don't allow fields with this candidate types.
for (DexEncodedField field : cls.instanceFields()) {
- notEligible.add(field.field.type);
+ markNotEligible(field.field.type, notEligible);
}
// Don't allow methods that take a value of this type.
for (DexEncodedMethod method : cls.methods()) {
- DexProto proto = method.method.proto;
- notEligible.addAll(Arrays.asList(proto.parameters.values));
+ for (DexType parameter : method.getProto().parameters.values) {
+ markNotEligible(parameter, notEligible);
+ }
+ if (method.isSynchronized()) {
+ markNotEligible(cls.type, notEligible);
+ }
}
// High-level limitations on what classes we consider eligible.
- if (cls.isInterface() // Must not be an interface or an abstract class.
+ if (cls.isInterface()
+ // Must not be an interface or an abstract class.
|| cls.accessFlags.isAbstract()
// Don't support candidates with instance fields
|| cls.instanceFields().size() > 0
@@ -159,7 +162,7 @@
// Staticizing classes implementing interfaces is more
// difficult, so don't support it until we really need it.
|| !cls.interfaces.isEmpty()) {
- notEligible.add(cls.type);
+ markNotEligible(cls.type, notEligible);
}
});
@@ -180,6 +183,12 @@
});
}
+ private void markNotEligible(DexType type, Set<DexType> notEligible) {
+ if (type.isClassType()) {
+ notEligible.add(type);
+ }
+ }
+
private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
AppInfoWithLiveness appInfo = appView.appInfo();
if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.field)) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/SynchronizedCompanionMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/SynchronizedCompanionMethodTest.java
new file mode 100644
index 0000000..8df91f6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/SynchronizedCompanionMethodTest.java
@@ -0,0 +1,85 @@
+// 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.ir.optimize.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SynchronizedCompanionMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SynchronizedCompanionMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertEquals(
+ 1,
+ testClassSubject
+ .allMethods(method -> method.isStatic() && !method.isClassInitializer())
+ .size());
+
+ ClassSubject companionClassSubject = inspector.clazz(Companion.class);
+ assertThat(companionClassSubject, isPresent());
+ assertEquals(
+ 1,
+ companionClassSubject
+ .allMethods(method -> !method.isStatic() && method.isSynchronized())
+ .size());
+ }
+
+ static class TestClass {
+
+ static final Companion companion = new Companion();
+
+ public static void main(String[] args) {
+ companion.greet();
+ }
+ }
+
+ @NeverClassInline
+ static class Companion {
+
+ @NeverInline
+ public synchronized void greet() {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index c62f945..f81dffd 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -74,6 +74,11 @@
}
@Override
+ public boolean isSynchronized() {
+ throw new Unreachable("Cannot determine if an absent method is synchronized");
+ }
+
+ @Override
public boolean isInstanceInitializer() {
throw new Unreachable("Cannot determine if an absent method is an instance initializer");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 299a6c0..536aa5e3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -122,6 +122,11 @@
}
@Override
+ public boolean isSynchronized() {
+ return dexMethod.accessFlags.isSynchronized();
+ }
+
+ @Override
public boolean isInstanceInitializer() {
return dexMethod.isInstanceInitializer();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 690838d..3c86361 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -25,6 +25,8 @@
public abstract boolean isBridge();
+ public abstract boolean isSynchronized();
+
public abstract boolean isInstanceInitializer();
public abstract boolean isClassInitializer();