Merge "Test for incorrect rebinding of super call to default interface method."
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index 24986e1..44527a2 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -63,6 +63,14 @@
|| shrinker == Shrinker.R8_CF;
}
+ protected static Backend toBackend(Shrinker shrinker) {
+ if (generatesDex(shrinker)) {
+ return Backend.DEX;
+ }
+ assert generatesCf(shrinker);
+ return Backend.CF;
+ }
+
protected AndroidApp runShrinker(
Shrinker mode, List<Class> programClasses, Iterable<String> proguardConfigs)
throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
new file mode 100644
index 0000000..fe2a93f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -0,0 +1,345 @@
+// 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.shaking.forceproguardcompatibility.defaultctor;
+
+import static com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor.ExternalizableDataClass.TYPE_1;
+import static com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor.ExternalizableDataClass.TYPE_2;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.InvalidClassException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+final class ExternalizableDataClass implements Externalizable {
+ static final byte TYPE_1 = 1;
+ static final byte TYPE_2 = 2;
+
+ private byte byteField;
+ private Object objectField;
+
+ // Default constructor for deserialization
+ public ExternalizableDataClass() {
+ }
+
+ // Constructor for serialization
+ public ExternalizableDataClass(byte byteField, Object objectField) {
+ this.byteField = byteField;
+ this.objectField = objectField;
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput objectOutput) throws IOException {
+ writeInternal(byteField, objectField, objectOutput);
+ }
+
+ private static void writeInternal(byte b, Object obj, DataOutput out) throws IOException {
+ out.writeByte(b);
+ switch (b) {
+ case TYPE_1:
+ ((Delegate1) obj).delegateWrite(out);
+ break;
+ case TYPE_2:
+ ((Delegate2) obj).delegateWrite(out);
+ break;
+ default:
+ throw new InvalidClassException("Unknown type: " + b);
+ }
+ }
+
+ @Override
+ public void readExternal(ObjectInput objectInput) throws IOException {
+ byteField = objectInput.readByte();
+ objectField = readInternal(byteField, objectInput);
+ }
+
+ private static Object readInternal(byte type, DataInput in) throws IOException {
+ switch (type) {
+ case TYPE_1: return Delegate1.delegateRead(in);
+ case TYPE_2: return Delegate2.delegateRead(in);
+ default:
+ throw new InvalidClassException("Unknown type: " + type);
+ }
+ }
+
+ private Object readResolve() {
+ return objectField;
+ }
+
+ @Override
+ public String toString() {
+ return "{ type: " + byteField + ", obj: " + objectField.toString() + " }";
+ }
+}
+
+final class Delegate1 implements Serializable {
+ private int intField;
+
+ private Object writeReplace() {
+ return new ExternalizableDataClass(TYPE_1, this);
+ }
+
+ static Delegate1 of(int intField) {
+ Delegate1 instance = new Delegate1();
+ instance.intField = intField;
+ return instance;
+ }
+
+ void delegateWrite(DataOutput out) throws IOException {
+ out.writeInt(intField);
+ }
+
+ static Object delegateRead(DataInput in) throws IOException {
+ int i = in.readInt();
+ return Delegate1.of(i);
+ }
+
+ private Object readResolve() throws ObjectStreamException {
+ throw new InvalidObjectException("Deserialization via serialization delegate");
+ }
+
+ @Override
+ public String toString() {
+ return "(" + intField + ")";
+ }
+}
+
+final class Delegate2 implements Serializable {
+ private String stringField;
+
+ private Object writeReplace() {
+ return new ExternalizableDataClass(TYPE_2, this);
+ }
+
+ static Delegate2 of(String stringField) {
+ Delegate2 instance = new Delegate2();
+ instance.stringField = stringField;
+ return instance;
+ }
+
+ void delegateWrite(DataOutput out) throws IOException {
+ out.writeUTF(stringField);
+ }
+
+ static Object delegateRead(DataInput in) throws IOException {
+ String s = in.readUTF();
+ return Delegate2.of(s);
+ }
+
+ private Object readResolve() throws ObjectStreamException {
+ throw new InvalidObjectException("Deserialization via serialization delegate");
+ }
+
+ @Override
+ public String toString() {
+ return "<" + stringField + ">";
+ }
+}
+
+class ExternalizableTestMain {
+ public static void main(String[] args) throws Exception {
+ Delegate2 data2 = Delegate2.of("MessageToSerialize");
+ // "Before: <MessageToSerialize>"
+ System.out.println("Before: " + data2.toString());
+
+ // Serialization
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
+ objectOutputStream.writeObject(data2);
+ objectOutputStream.close();
+
+ byte[] byteArray = out.toByteArray();
+
+ // Deserialization
+ ByteArrayInputStream in = new ByteArrayInputStream(byteArray);
+ ObjectInputStream objectInputStream = new ObjectInputStream(in);
+ Object copy = objectInputStream.readObject();
+ assert copy instanceof Delegate2;
+ // "After: <MessageToSerialize>"
+ System.out.println("After: " + copy.toString());
+ }
+}
+
+class NonSerializableSuperClass {
+ protected String tag;
+
+ // Default constructor for deserialization
+ public NonSerializableSuperClass() {
+ this.tag = null;
+ }
+
+ public NonSerializableSuperClass(String tag) {
+ this.tag = tag;
+ }
+
+ @Override
+ public String toString() {
+ return tag == null ? "NULL" : tag;
+ }
+}
+
+class SerializableDataClass extends NonSerializableSuperClass implements Serializable {
+ private String extraTag;
+
+ public SerializableDataClass() {
+ super();
+ this.extraTag = null;
+ }
+
+ public SerializableDataClass(String tag, String extraTag) {
+ super(tag);
+ this.extraTag = extraTag;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ", " + (extraTag == null ? "NULL" : extraTag);
+ }
+}
+
+class SerializableTestMain {
+ public static void main(String[] args) throws Exception {
+ SerializableDataClass data = new SerializableDataClass("TagToSerialize", "ExtraToSerialize");
+ // "Before: TagToSerialize, ExtraToSerialize"
+ System.out.println("Before: " + data.toString());
+
+ // Serialization
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
+ objectOutputStream.writeObject(data);
+ objectOutputStream.close();
+
+ byte[] byteArray = out.toByteArray();
+
+ // Deserialization
+ ByteArrayInputStream in = new ByteArrayInputStream(byteArray);
+ ObjectInputStream objectInputStream = new ObjectInputStream(in);
+ Object copy = objectInputStream.readObject();
+ assert copy instanceof SerializableDataClass;
+ // "After: NULL, ExtraToSerialize"
+ System.out.println("After: " + copy.toString());
+ }
+}
+
+@RunWith(Parameterized.class)
+public class ExternalizableTest extends ProguardCompatibilityTestBase {
+ private final static List<Class> CLASSES_FOR_EXTERNALIZABLE = ImmutableList.of(
+ ExternalizableDataClass.class, Delegate1.class, Delegate2.class, ExternalizableTestMain.class
+ );
+
+ private final static List<Class> CLASSES_FOR_SERIALIZABLE = ImmutableList.of(
+ NonSerializableSuperClass.class, SerializableDataClass.class, SerializableTestMain.class
+ );
+
+ private final Shrinker shrinker;
+
+ @Parameterized.Parameters(name = "Shrinker: {0}")
+ public static Collection<Object> data() {
+ return ImmutableList.of(
+ Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+ }
+
+ public ExternalizableTest(Shrinker shrinker) {
+ this.shrinker = shrinker;
+ }
+
+ @Test
+ public void testExternalizable() throws Exception {
+ // TODO(b/116735204): R8 should keep default ctor() of classes that implement Externalizable
+ if (isR8(shrinker)) {
+ return;
+ }
+
+ String javaOutput = runOnJava(ExternalizableTestMain.class);
+
+ List<String> config = ImmutableList.of(
+ keepMainProguardConfiguration(ExternalizableTestMain.class),
+ // https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
+ "-keepclassmembers class * implements java.io.Serializable {",
+ //" private static final java.io.ObjectStreamField[] serialPersistentFields;",
+ " private void writeObject(java.io.ObjectOutputStream);",
+ " private void readObject(java.io.ObjectInputStream);",
+ " java.lang.Object writeReplace();",
+ " java.lang.Object readResolve();",
+ "}");
+
+ AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_EXTERNALIZABLE, config);
+
+ // TODO(b/117302947): Need to update ART binary.
+ if (generatesCf(shrinker)) {
+ String output = runOnVM(
+ processedApp, ExternalizableTestMain.class.getCanonicalName(), toBackend(shrinker));
+ assertEquals(javaOutput.trim(), output.trim());
+ }
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
+ ClassSubject classSubject = codeInspector.clazz(ExternalizableDataClass.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject init = classSubject.init(ImmutableList.of());
+ assertThat(init, isPresent());
+ }
+
+ @Test
+ public void testSerializable() throws Exception {
+ // TODO(b/116735204): R8 should keep default ctor() of first non-serializable superclass of
+ // serializable class.
+ if (isR8(shrinker)) {
+ return;
+ }
+
+ String javaOutput = runOnJava(SerializableTestMain.class);
+
+ List<String> config = ImmutableList.of(
+ keepMainProguardConfiguration(SerializableTestMain.class),
+ // https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
+ "-keepclassmembers class * implements java.io.Serializable {",
+ //" private static final java.io.ObjectStreamField[] serialPersistentFields;",
+ " private void writeObject(java.io.ObjectOutputStream);",
+ " private void readObject(java.io.ObjectInputStream);",
+ " java.lang.Object writeReplace();",
+ " java.lang.Object readResolve();",
+ "}");
+
+ AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config);
+ // TODO(b/117302947): Need to update ART binary.
+ if (generatesCf(shrinker)) {
+ String output = runOnVM(
+ processedApp, SerializableTestMain.class.getCanonicalName(), toBackend(shrinker));
+ assertEquals(javaOutput.trim(), output.trim());
+ }
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
+ ClassSubject classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject init = classSubject.init(ImmutableList.of());
+ assertThat(init, isPresent());
+ }
+}