Version 2.0.56
Cherry-pick: Desugared library: allow invalid library superclasses
CL: https://r8-review.googlesource.com/c/r8/+/49905
Bug: 151969439
Change-Id: I7d7d4a076c58616fd565bf25302d9e4b28a108da
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 9bc4e43..eefcb5d 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.0.55";
+ public static final String LABEL = "2.0.56";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java
new file mode 100644
index 0000000..2392f0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java
@@ -0,0 +1,71 @@
+// 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.errors;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+
+/**
+ * Diagnostic for super types of library classes which are not library classes but required for
+ * desugaring.
+ */
+@Keep
+public class InvalidLibrarySuperclassDiagnostic implements DesugarDiagnostic {
+
+ private final Origin origin;
+ private final List<MethodReference> methods;
+ private final ClassReference libraryType;
+ private final ClassReference invalidSuperType;
+ private final String message;
+
+ public InvalidLibrarySuperclassDiagnostic(
+ Origin origin,
+ ClassReference libraryType,
+ ClassReference invalidSuperType,
+ String message,
+ List<MethodReference> methods) {
+ assert origin != null;
+ assert libraryType != null;
+ assert invalidSuperType != null;
+ assert message != null;
+ this.origin = origin;
+ this.libraryType = libraryType;
+ this.invalidSuperType = invalidSuperType;
+ this.message = message;
+ this.methods = methods;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public Position getPosition() {
+ return Position.UNKNOWN;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ StringBuilder builder =
+ new StringBuilder()
+ .append("Superclass `")
+ .append(invalidSuperType.getTypeName())
+ .append("` of library class `")
+ .append(libraryType.getTypeName())
+ .append("` is ")
+ .append(message)
+ .append(
+ ". A superclass of a library class should be a library class. This is required for"
+ + " the desugaring of ");
+ StringUtils.append(builder, methods, ", ", StringUtils.BraceType.NONE);
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index a1e98f7..de97baf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -8,6 +8,11 @@
import com.android.tools.r8.naming.NamingLens;
import com.google.common.collect.Maps;
import java.util.Map;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import java.util.ArrayList;
+import java.util.List;
public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod>
implements PresortedComparable<DexMethod> {
@@ -35,6 +40,22 @@
return "Method " + holder + "." + name + " " + proto.toString();
}
+ public MethodReference asMethodReference(AppView<?> appView) {
+ List<TypeReference> parameters = new ArrayList<>();
+ for (DexType value : proto.parameters.values) {
+ parameters.add(Reference.typeFromDescriptor(value.toDescriptorString()));
+ }
+ TypeReference returnType =
+ proto.returnType == appView.dexItemFactory().voidType
+ ? null
+ : Reference.typeFromDescriptor(proto.returnType.toDescriptorString());
+ return Reference.method(
+ Reference.classFromDescriptor(holder.toDescriptorString()),
+ name.toString(),
+ parameters,
+ returnType);
+ }
+
public int getArity() {
return proto.parameters.size();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 1dce2ed..0ec2004 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -289,11 +289,42 @@
if (current.type == typeToInherit) {
return true;
}
- current = appView.definitionFor(current.superType).asLibraryClass();
+ DexClass dexClass = appView.definitionFor(current.superType);
+ if (dexClass == null || dexClass.isClasspathClass()) {
+ reportInvalidLibrarySupertype(current, rewritableMethods.getEmulatedDispatchMethods());
+ return false;
+ } else if (dexClass.isProgramClass()) {
+ // If dexClass is a program class, then it is already correctly desugared.
+ return false;
+ }
+ current = dexClass.asLibraryClass();
}
return false;
}
+ private void reportInvalidLibrarySupertype(
+ DexLibraryClass libraryClass, Set<DexMethod> retarget) {
+ DexClass dexClass = appView.definitionFor(libraryClass.superType);
+ String message;
+ if (dexClass == null) {
+ message = "missing";
+ } else if (dexClass.isClasspathClass()) {
+ message = "a classpath class";
+ } else {
+ message = "INVALID";
+ assert false;
+ }
+ appView
+ .options()
+ .warningInvalidLibrarySuperclassForDesugar(
+ dexClass == null ? libraryClass.getOrigin() : dexClass.getOrigin(),
+ libraryClass.type,
+ libraryClass.superType,
+ message,
+ retarget,
+ appView);
+ }
+
private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
DexProgramClass clazz, List<DexMethod> dexMethods) {
// BackportedMethodRewriter emulate dispatch: insertion of a marker interface & forwarding
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 2de4c37..21ff710 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic;
import com.android.tools.r8.errors.MissingNestHostNestDesugarDiagnostic;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.features.FeatureSplitConfiguration;
@@ -61,6 +62,7 @@
import java.util.TreeSet;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import org.objectweb.asm.Opcodes;
public class InternalOptions {
@@ -650,6 +652,8 @@
/** A set of dexitems we have reported missing to dedupe warnings. */
private final Set<DexItem> reportedMissingForDesugaring = Sets.newConcurrentHashSet();
+ private final Set<DexItem> invalidLibraryClasses = Sets.newConcurrentHashSet();
+
public void errorMissingClassMissingNestHost(DexClass compiledClass) {
throw reporter.fatalError(messageErrorMissingNestHost(compiledClass));
}
@@ -793,6 +797,26 @@
}
}
+ public void warningInvalidLibrarySuperclassForDesugar(
+ Origin origin,
+ DexType libraryType,
+ DexType invalidSuperType,
+ String message,
+ Set<DexMethod> retarget,
+ AppView<?> appView) {
+ if (invalidLibraryClasses.add(invalidSuperType)) {
+ reporter.warning(
+ new InvalidLibrarySuperclassDiagnostic(
+ origin,
+ Reference.classFromDescriptor(libraryType.toDescriptorString()),
+ Reference.classFromDescriptor(invalidSuperType.toDescriptorString()),
+ message,
+ retarget.stream()
+ .map(method -> method.asMethodReference(appView))
+ .collect(Collectors.toList())));
+ }
+ }
+
public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {
TypeVersionPair pair = new TypeVersionPair(version, clazz);
synchronized (missingEnclosingMembers) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
new file mode 100644
index 0000000..c5c7f22
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
@@ -0,0 +1,176 @@
+// 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvalidLibraryTest extends DesugaredLibraryTestBase {
+
+ private static Path customLib;
+ private static Path superclassAsClasspath;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("1970-01-02T10:17:36.789Z", "1970-01-12T10:20:54.321123456Z");
+ private static final String INVALID_RESULT =
+ StringUtils.lines("1970-01-02T10:17:36.789Z", "1970-01-12T10:20:54.321Z");
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameterized.Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public InvalidLibraryTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ customLib =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibraryClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip();
+ superclassAsClasspath =
+ testForD8(getStaticTemp())
+ .addProgramClasses(SuperLibraryClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip();
+ }
+
+ @Test
+ public void testProgramSupertype() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(
+ Executor.class, SuperLibraryClass.class, LocalClass.class, LocalClassOverride.class)
+ .addLibraryClasses(CustomLibraryClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(customLib)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testClasspathSupertype() throws Exception {
+ Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Executor.class, LocalClass.class, LocalClassOverride.class)
+ .addClasspathClasses(SuperLibraryClass.class)
+ .addLibraryClasses(CustomLibraryClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(customLib, superclassAsClasspath)
+ .run(parameters.getRuntime(), Executor.class)
+ // The code requires desugaring to be run correctly, but with the classpath superclass,
+ // desugaring is incorrectly performed. The code therefore falls-backs to the default
+ // implementation in Date, which happens to be correct in one case, but incorrect
+ // in the other case (Warning was raised).
+ .assertSuccessWithOutput(INVALID_RESULT);
+ }
+
+ @Test
+ public void testNullSupertype() throws Exception {
+ Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Executor.class, LocalClass.class, LocalClassOverride.class)
+ .addLibraryClasses(CustomLibraryClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(customLib, superclassAsClasspath)
+ .run(parameters.getRuntime(), Executor.class)
+ // The code requires desugaring to be run correctly, but with the missing supertype,
+ // desugaring could not be performed and the code cannot simply run (Warning was raised).
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+ }
+
+ private void assertWarningInvalidLibrary(TestDiagnosticMessages testDiagnosticMessages) {
+ assert testDiagnosticMessages.getWarnings().stream()
+ .anyMatch(diagnostic -> diagnostic instanceof InvalidLibrarySuperclassDiagnostic);
+ }
+
+ static class Executor {
+ public static void main(String[] args) {
+ System.out.println(new LocalClass(123456789).toInstant());
+ System.out.println(getOverrideAsLocalClass().toInstant());
+ }
+
+ public static LocalClass getOverrideAsLocalClass() {
+ return new LocalClassOverride(987654321);
+ }
+ }
+
+ static class SuperLibraryClass extends Date {
+ public SuperLibraryClass(int nanos) {
+ super(nanos);
+ }
+ }
+
+ static class CustomLibraryClass extends SuperLibraryClass {
+ public CustomLibraryClass(int nanos) {
+ super(nanos);
+ }
+ }
+
+ static class LocalClass extends CustomLibraryClass {
+ public LocalClass(int nanos) {
+ super(nanos);
+ }
+ }
+
+ static class LocalClassOverride extends LocalClass {
+ public LocalClassOverride(int nanos) {
+ super(nanos);
+ }
+
+ @Override
+ public Instant toInstant() {
+ return super.toInstant().plusNanos(123456);
+ }
+ }
+}