Publicize private constructors.
Bug: 72211928
Change-Id: Icf095761a4762175b929432768e3d4ec09deaff6
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 73ebf73..5de2ede 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -149,7 +149,8 @@
assert accessFlags.isPrivate();
if (appView.getDexItemFactory().isConstructor(encodedMethod.method)) {
- // TODO(b/72211928)
+ accessFlags.unsetPrivate();
+ accessFlags.setPublic();
return false;
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
new file mode 100644
index 0000000..d88f3c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
@@ -0,0 +1,74 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPublic;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+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 org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+abstract class AccessRelaxationTestBase extends TestBase {
+
+ static R8Command.Builder loadProgramFiles(Iterable<Class> classes) {
+ R8Command.Builder builder = R8Command.builder();
+ for (Class clazz : classes) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ }
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ return builder;
+ }
+
+ static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
+ R8Command.Builder builder = R8Command.builder();
+ builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
+ for (Class clazz : classes) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ }
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ return builder;
+ }
+
+ void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
+ // Run on Jvm.
+ String jvmOutput = runOnJava(mainClass);
+
+ // Run on Art to check generated code against verifier.
+ String artOutput = runOnArt(app, mainClass);
+
+ String adjustedArtOutput = artOutput.replace(
+ "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
+ assertEquals(jvmOutput, adjustedArtOutput);
+ }
+
+ static void assertPublic(DexInspector dexInspector, Class clazz, MethodSignature signature) {
+ ClassSubject classSubject = dexInspector.clazz(clazz);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(signature);
+ assertThat(methodSubject, isPublic());
+ }
+
+ static void assertNotPublic(DexInspector dexInspector, Class clazz, MethodSignature signature) {
+ ClassSubject classSubject = dexInspector.clazz(clazz);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(signature);
+ assertThat(methodSubject, not(isPublic()));
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
new file mode 100644
index 0000000..01d5621
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
@@ -0,0 +1,198 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+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.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import org.junit.Test;
+
+class L1 {
+ private final String x;
+
+ private L1(String x) {
+ this.x = x;
+ }
+
+ private L1() {
+ this("private_x");
+ }
+
+ static L1 create() {
+ return new L1();
+ }
+
+ L1(int i) {
+ this(String.valueOf(i));
+ }
+
+ @Override
+ public String toString() {
+ return x;
+ }
+}
+
+class L2_1 extends L1 {
+ private String y;
+
+ private L2_1() {
+ this(21);
+ this.y = "private_L2_1_y";
+ }
+
+ L2_1(int i) {
+ super(i);
+ this.y = "L2_1_y";
+ }
+
+ private L2_1(String y) {
+ this(21);
+ this.y = y;
+ }
+
+ static L2_1 create(String y) {
+ return new L2_1(y);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + y;
+ }
+}
+
+class L2_2 extends L1 {
+ private String y;
+
+ private L2_2(int i) {
+ super(i);
+ this.y = "private_L2_2_y";
+ }
+
+ L2_2(String y) {
+ this(22);
+ this.y = y;
+ }
+
+ static L2_1 create() {
+ return new L2_1(22);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + y;
+ }
+}
+
+class L3_1 extends L2_1 {
+ private final String z;
+
+ private L3_1(int i) {
+ this(String.valueOf(i));
+ }
+
+ private L3_1(String z) {
+ super(31);
+ this.z = z;
+ }
+
+ static L3_1 create(int i) {
+ return new L3_1(i);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + z;
+ }
+}
+
+class L3_2 extends L2_2 {
+ private String z;
+
+ private L3_2() {
+ super("private_L3_2_y");
+ this.z = "private_L3_2_z";
+ }
+
+ private L3_2(int i) {
+ super(String.valueOf(i));
+ this.z = "private_L3_2_z" + "_" + i;
+ }
+
+ L3_2(String z) {
+ this(32);
+ this.z = z;
+ }
+
+ static L3_2 create(String z) {
+ return new L3_2(z);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + z;
+ }
+}
+
+class CtorTestMain {
+ public static void main(String[] args) {
+ System.out.println(L1.create());
+ System.out.println(L2_1.create("main_y"));
+ System.out.println(L2_2.create());
+ System.out.println(L3_1.create(41));
+ System.out.println(L3_2.create("main_z"));
+ }
+}
+
+public final class ConstructorRelaxationTest extends AccessRelaxationTestBase {
+ private static final String INIT= "<init>";
+ private static final List<Class> CLASSES =
+ ImmutableList.of(L1.class, L2_1.class, L2_2.class, L3_1.class, L3_2.class);
+
+ @Test
+ public void test() throws Exception {
+ Class mainClass = CtorTestMain.class;
+ R8Command.Builder builder =
+ loadProgramFiles(Iterables.concat(CLASSES, ImmutableList.of(mainClass)));
+ builder.addProguardConfiguration(
+ ImmutableList.of(
+ "-keep class " + mainClass.getCanonicalName() + "{",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "",
+ "-keep class *.L* {",
+ " <init>(...);",
+ "}",
+ "",
+ "-dontobfuscate",
+ "-allowaccessmodification"
+ ),
+ Origin.unknown());
+
+ AndroidApp app = ToolHelper.runR8(builder.build(), options -> {
+ options.enableInlining = false;
+ options.enableClassMerging = false;
+ });
+ compareJvmAndArt(app, mainClass);
+
+ DexInspector dexInspector = new DexInspector(app);
+ for (Class clazz : CLASSES) {
+ ClassSubject classSubject = dexInspector.clazz(clazz);
+ assertThat(classSubject, isPresent());
+ classSubject.getDexClass().forEachMethod(m -> {
+ assertTrue(!m.isInstanceInitializer() || m.isPublicMethod());
+ });
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
similarity index 69%
rename from src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
rename to src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 53a8c31..d5190e6 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -4,17 +4,8 @@
package com.android.tools.r8.accessrelaxation;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPublic;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-
-import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.accessrelaxation.privateinstance.Base;
import com.android.tools.r8.accessrelaxation.privateinstance.Sub1;
import com.android.tools.r8.accessrelaxation.privateinstance.Sub2;
@@ -27,55 +18,12 @@
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.google.common.collect.ImmutableList;
import org.junit.Test;
-import org.junit.runner.RunWith;
-@RunWith(VmTestRunner.class)
-public class AccessRelaxationTest extends TestBase {
+public final class NonConstructorRelaxationTest extends AccessRelaxationTestBase {
private static final String STRING = "java.lang.String";
- private static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
- R8Command.Builder builder = R8Command.builder();
- builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
- for (Class clazz : classes) {
- builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
- }
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- return builder;
- }
-
- private void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
- // Run on Jvm.
- String jvmOutput = runOnJava(mainClass);
-
- // Run on Art to check generated code against verifier.
- String artOutput = runOnArt(app, mainClass);
-
- String adjustedArtOutput = artOutput.replace(
- "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
- assertEquals(jvmOutput, adjustedArtOutput);
- }
-
- private static void assertPublic(
- DexInspector dexInspector, Class clazz, MethodSignature signature) {
- ClassSubject classSubject = dexInspector.clazz(clazz);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(signature);
- assertThat(methodSubject, isPublic());
- }
-
- private static void assertNotPublic(
- DexInspector dexInspector, Class clazz, MethodSignature signature) {
- ClassSubject classSubject = dexInspector.clazz(clazz);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(signature);
- assertThat(methodSubject, not(isPublic()));
- }
-
@Test
public void testStaticMethodRelaxation() throws Exception {
Class mainClass = C.class;