Context-sensitive missing classes reporting
This CL also uses the new contextual information to avoid reporting missing classes that are only referenced from contexts, which are matched by -dontwarn rules.
Bug: 175755807
Bug: 122881119
Bug: 179249745
Change-Id: Iedb85ee27864d43c93dd6bf2f30cb4eb728aeb03
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index c11016d..a79b303 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -327,7 +327,8 @@
.appInfo()
.rebuildWithClassHierarchy(
MissingClasses.builderForInitialMissingClasses()
- .addNewMissingClasses(new SubtypingInfo(appView).getMissingClasses())
+ .legacyAddNewMissingClasses(
+ new SubtypingInfo(appView).getMissingClasses())
.reportMissingClasses(appView)));
}
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 adbe83c..f806886 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -46,6 +48,10 @@
this.descriptor = descriptor;
}
+ public ClassReference asClassReference() {
+ return Reference.classFromDescriptor(toDescriptorString());
+ }
+
@Override
public DexType self() {
return this;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d028682..716f2dc 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -550,7 +550,9 @@
}
private void recordTypeReference(
- DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+ DexType type,
+ ProgramDefinition context,
+ BiConsumer<DexType, DexReference> missingClassConsumer) {
if (type == null) {
return;
}
@@ -569,7 +571,9 @@
}
private void recordMethodReference(
- DexMethod method, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+ DexMethod method,
+ ProgramDefinition context,
+ BiConsumer<DexType, DexReference> missingClassConsumer) {
recordTypeReference(method.holder, context, missingClassConsumer);
recordTypeReference(method.proto.returnType, context, missingClassConsumer);
for (DexType type : method.proto.parameters.values) {
@@ -595,15 +599,19 @@
}
private DexClass definitionFor(
- DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+ DexType type,
+ ProgramDefinition context,
+ BiConsumer<DexType, DexReference> missingClassConsumer) {
return internalDefinitionFor(type, context, missingClassConsumer);
}
private DexClass internalDefinitionFor(
- DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+ DexType type,
+ ProgramDefinition context,
+ BiConsumer<DexType, DexReference> missingClassConsumer) {
DexClass clazz = appInfo().definitionFor(type);
if (clazz == null) {
- missingClassConsumer.accept(type);
+ missingClassConsumer.accept(type, context.getReference());
return null;
}
if (clazz.isNotProgramClass()) {
@@ -661,7 +669,7 @@
}
DexClass definition = appView.definitionFor(type);
if (definition == null) {
- reportMissingClass(type);
+ reportMissingClassWithoutContext(type);
return;
}
if (definition.isProgramClass() || !liveNonProgramTypes.add(definition)) {
@@ -2243,7 +2251,11 @@
missingClassesBuilder.ignoreNewMissingClass(clazz);
}
- private void reportMissingClass(DexType clazz) {
+ private void ignoreMissingClass(DexType clazz, DexReference context) {
+ ignoreMissingClass(clazz);
+ }
+
+ private void reportMissingClass(DexType clazz, DexReference context) {
assert !mode.isFinalTreeShaking()
|| missingClassesBuilder.wasAlreadyMissing(clazz)
|| appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
@@ -2251,7 +2263,19 @@
// TODO(b/157107464): See if we can clean this up.
|| (initialPrunedTypes != null && initialPrunedTypes.contains(clazz))
: "Unexpected missing class `" + clazz.toSourceString() + "`";
- missingClassesBuilder.addNewMissingClass(clazz);
+ missingClassesBuilder.addNewMissingClass(clazz, context);
+ }
+
+ @Deprecated
+ private void reportMissingClassWithoutContext(DexType clazz) {
+ assert !mode.isFinalTreeShaking()
+ || missingClassesBuilder.wasAlreadyMissing(clazz)
+ || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
+ || initialDeadProtoTypes.contains(clazz)
+ // TODO(b/157107464): See if we can clean this up.
+ || (initialPrunedTypes != null && initialPrunedTypes.contains(clazz))
+ : "Unexpected missing class `" + clazz.toSourceString() + "`";
+ missingClassesBuilder.legacyAddNewMissingClass(clazz);
}
private void reportMissingMethod(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index 1def3a9..7c0395a 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -8,15 +8,20 @@
import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
+import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.synthesis.CommittedItems;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@@ -54,7 +59,7 @@
public static class Builder {
private final Set<DexType> alreadyMissingClasses;
- private final Set<DexType> newMissingClasses = Sets.newIdentityHashSet();
+ private final Map<DexType, Set<DexReference>> newMissingClasses = new IdentityHashMap<>();
// Set of missing types that are not to be reported as missing. This does not hide reports
// if the same type is in newMissingClasses in which case it is reported regardless.
@@ -68,12 +73,25 @@
this.alreadyMissingClasses = alreadyMissingClasses;
}
- public void addNewMissingClass(DexType type) {
- newMissingClasses.add(type);
+ public void addNewMissingClass(DexType type, DexReference context) {
+ assert context != null;
+ assert context.getContextType() != type;
+ if (!alreadyMissingClasses.contains(type)) {
+ newMissingClasses.computeIfAbsent(type, ignore -> Sets.newIdentityHashSet()).add(context);
+ }
}
- public Builder addNewMissingClasses(Collection<DexType> types) {
- newMissingClasses.addAll(types);
+ public void legacyAddNewMissingClass(DexType type) {
+ if (!alreadyMissingClasses.contains(type)) {
+ // The legacy reporting is context insensitive, so therefore we use the missing classes
+ // themselves as contexts.
+ newMissingClasses.computeIfAbsent(type, ignore -> Sets.newIdentityHashSet()).add(type);
+ }
+ }
+
+ @Deprecated
+ public Builder legacyAddNewMissingClasses(Collection<DexType> types) {
+ types.forEach(this::legacyAddNewMissingClass);
return this;
}
@@ -82,7 +100,7 @@
}
public boolean contains(DexType type) {
- return alreadyMissingClasses.contains(type) || newMissingClasses.contains(type);
+ return alreadyMissingClasses.contains(type) || newMissingClasses.containsKey(type);
}
Builder removeAlreadyMissingClasses(Iterable<DexType> types) {
@@ -99,17 +117,12 @@
public MissingClasses reportMissingClasses(AppView<?> appView) {
InternalOptions options = appView.options();
- Set<DexType> newMissingClassesWithoutDontWarn =
- appView.getDontWarnConfiguration().getNonMatches(newMissingClasses);
- newMissingClassesWithoutDontWarn.removeAll(alreadyMissingClasses);
- newMissingClassesWithoutDontWarn.removeAll(
- getAllowedMissingClasses(appView.dexItemFactory()));
- newMissingClassesWithoutDontWarn.removeIf(
- isCompilerSynthesizedAllowedMissingClasses(appView));
- if (!newMissingClassesWithoutDontWarn.isEmpty()) {
+ Map<DexType, Set<DexReference>> missingClassesToBeReported =
+ getMissingClassesToBeReported(appView);
+ if (!missingClassesToBeReported.isEmpty()) {
MissingClassesDiagnostic diagnostic =
new MissingClassesDiagnostic.Builder()
- .addMissingClasses(newMissingClassesWithoutDontWarn)
+ .addMissingClasses(missingClassesToBeReported)
.setFatal(!options.ignoreMissingClasses)
.build();
if (options.ignoreMissingClasses) {
@@ -121,8 +134,44 @@
return build();
}
- private static Collection<DexType> getAllowedMissingClasses(DexItemFactory dexItemFactory) {
- return ImmutableList.<DexType>builder()
+ private Map<DexType, Set<DexReference>> getMissingClassesToBeReported(AppView<?> appView) {
+ Predicate<DexType> allowedMissingClassesPredicate =
+ getIsAllowedMissingClassesPredicate(appView);
+ Map<DexType, Set<DexReference>> missingClassesToBeReported =
+ new IdentityHashMap<>(newMissingClasses.size());
+ newMissingClasses.forEach(
+ (missingClass, contexts) -> {
+ // Don't report "allowed" missing classes (e.g., classes matched by -dontwarn).
+ if (allowedMissingClassesPredicate.test(missingClass)) {
+ return;
+ }
+
+ // Remove all contexts that are matched by a -dontwarn rule (a missing class should not
+ // be reported if it os only referenced from contexts that are matched by a -dontwarn).
+ contexts.removeIf(
+ context -> appView.getDontWarnConfiguration().matches(context.getContextType()));
+
+ // If there are any contexts not matched by a -dontwarn rule, then report.
+ if (!contexts.isEmpty()) {
+ missingClassesToBeReported.put(missingClass, contexts);
+ }
+ });
+ return missingClassesToBeReported;
+ }
+
+ private static Predicate<DexType> getIsAllowedMissingClassesPredicate(AppView<?> appView) {
+ Set<DexType> allowedMissingClasses = getAllowedMissingClasses(appView.dexItemFactory());
+ Predicate<DexType> compilerSynthesizedAllowingMissingClassPredicate =
+ getIsCompilerSynthesizedAllowedMissingClassesPredicate(appView);
+ DontWarnConfiguration dontWarnConfiguration = appView.getDontWarnConfiguration();
+ return type ->
+ allowedMissingClasses.contains(type)
+ || compilerSynthesizedAllowingMissingClassPredicate.test(type)
+ || dontWarnConfiguration.matches(type);
+ }
+
+ private static Set<DexType> getAllowedMissingClasses(DexItemFactory dexItemFactory) {
+ return ImmutableSet.<DexType>builder()
.add(
dexItemFactory.annotationDefault,
dexItemFactory.annotationMethodParameters,
@@ -133,14 +182,14 @@
dexItemFactory.serializedLambdaType,
// TODO(b/176133674) StringConcatFactory is backported, but the class is reported as
// missing because the enqueuer runs prior to backporting and thus sees the
- // non-desugared
- // code.
+ // non-desugared code.
dexItemFactory.stringConcatFactoryType)
.addAll(dexItemFactory.getConversionTypes())
.build();
}
- private Predicate<DexType> isCompilerSynthesizedAllowedMissingClasses(AppView<?> appView) {
+ private static Predicate<DexType> getIsCompilerSynthesizedAllowedMissingClassesPredicate(
+ AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
DexString emulatedLibraryClassNameSuffix =
@@ -161,7 +210,7 @@
/** Intentionally private, use {@link Builder#reportMissingClasses(AppView)}. */
private MissingClasses build() {
- // Extend the newMissingClasses set with all other missing classes.
+ // Return the new set of missing classes.
//
// We also add newIgnoredMissingClasses to newMissingClasses to be able to assert that we have
// a closed world after the first round of tree shaking: we should never lookup a class that
@@ -171,9 +220,9 @@
// Note: At this point, all missing classes in newMissingClasses have already been reported.
// Thus adding newIgnoredMissingClasses to newMissingClasses will not lead to reports for the
// classes in newIgnoredMissingClasses.
- newMissingClasses.addAll(alreadyMissingClasses);
- newMissingClasses.addAll(newIgnoredMissingClasses);
- return new MissingClasses(newMissingClasses);
+ return new MissingClasses(
+ SetUtils.newIdentityHashSet(
+ alreadyMissingClasses, newMissingClasses.keySet(), newIgnoredMissingClasses));
}
public boolean wasAlreadyMissing(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
index 5965cc5..2e59a42 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
@@ -4,34 +4,131 @@
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
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.FieldReference;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
-import com.google.common.collect.ImmutableSortedSet;
-import java.util.Collection;
+import com.android.tools.r8.utils.FieldReferenceUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
import java.util.Set;
-import java.util.SortedSet;
+import java.util.SortedMap;
+import java.util.function.Function;
@Keep
public class MissingClassesDiagnostic implements Diagnostic {
- private final boolean fatal;
- private final SortedSet<ClassReference> missingClasses;
+ private static class MissingClassAccessContexts {
- private MissingClassesDiagnostic(boolean fatal, SortedSet<ClassReference> missingClasses) {
+ private ImmutableSet<ClassReference> classContexts;
+ private ImmutableSet<FieldReference> fieldContexts;
+ private ImmutableSet<MethodReference> methodContexts;
+
+ private MissingClassAccessContexts(
+ ImmutableSet<ClassReference> classContexts,
+ ImmutableSet<FieldReference> fieldContexts,
+ ImmutableSet<MethodReference> methodContexts) {
+ this.classContexts = classContexts;
+ this.fieldContexts = fieldContexts;
+ this.methodContexts = methodContexts;
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ String getReferencedFromMessageSuffix(ClassReference missingClass) {
+ if (!methodContexts.isEmpty()) {
+ return " (referenced from: "
+ + MethodReferenceUtils.toSourceString(methodContexts.iterator().next())
+ + ")";
+ }
+ if (!fieldContexts.isEmpty()) {
+ return " (referenced from: "
+ + FieldReferenceUtils.toSourceString(fieldContexts.iterator().next())
+ + ")";
+ }
+ // TODO(b/175543745): The legacy reporting is context insensitive, and therefore uses the
+ // missing classes as their own context. Once legacy reporting is removed, this should be
+ // simplified to taking the first context.
+ Optional<ClassReference> classContext =
+ classContexts.stream().filter(not(missingClass::equals)).findFirst();
+ if (classContext.isPresent()) {
+ return " (referenced from: " + classContext.toString() + ")";
+ }
+ return "";
+ }
+
+ static class Builder {
+
+ private final Set<DexReference> contexts = Sets.newIdentityHashSet();
+
+ Builder addAll(Set<DexReference> contexts) {
+ this.contexts.addAll(contexts);
+ return this;
+ }
+
+ // TODO(b/179249745): Sort on demand in getReferencedFromMessageSuffix() instead.
+ MissingClassAccessContexts build() {
+ // Sort the contexts for deterministic reporting.
+ List<DexType> classContexts = new ArrayList<>();
+ List<DexField> fieldContexts = new ArrayList<>();
+ List<DexMethod> methodContexts = new ArrayList<>();
+ contexts.forEach(
+ context -> context.apply(classContexts::add, fieldContexts::add, methodContexts::add));
+ Collections.sort(classContexts);
+ Collections.sort(fieldContexts);
+ Collections.sort(methodContexts);
+
+ // Build immutable sets (which preserve insertion order) from the sorted lists, mapping each
+ // DexType, DexField, and DexMethod to ClassReference, FieldReference, and MethodReference,
+ // respectively.
+ return new MissingClassAccessContexts(
+ toImmutableSet(classContexts, DexType::asClassReference),
+ toImmutableSet(fieldContexts, DexField::asFieldReference),
+ toImmutableSet(methodContexts, DexMethod::asMethodReference));
+ }
+
+ private <S, T> ImmutableSet<T> toImmutableSet(List<S> list, Function<S, T> fn) {
+ ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+ list.forEach(element -> builder.add(fn.apply(element)));
+ return builder.build();
+ }
+ }
+ }
+
+ private final boolean fatal;
+ private final SortedMap<ClassReference, MissingClassAccessContexts> missingClasses;
+
+ private MissingClassesDiagnostic(
+ boolean fatal, SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
assert !missingClasses.isEmpty();
this.fatal = fatal;
this.missingClasses = missingClasses;
}
public Set<ClassReference> getMissingClasses() {
- return missingClasses;
+ return missingClasses.keySet();
}
/** A missing class(es) failure can generally not be attributed to a single origin. */
@@ -46,7 +143,6 @@
return Position.UNKNOWN;
}
- // TODO(b/175755807): Extend diagnostic message with contextual information.
@Override
public String getDiagnosticMessage() {
return fatal ? getFatalDiagnosticMessage() : getNonFatalDiagnosticMessage();
@@ -54,43 +150,70 @@
private String getFatalDiagnosticMessage() {
if (missingClasses.size() == 1) {
- return "Compilation can't be completed because the class "
- + missingClasses.iterator().next().getTypeName()
- + " is missing.";
+ StringBuilder builder =
+ new StringBuilder(
+ "Compilation can't be completed because the following class is missing: ");
+ writeMissingClass(builder, missingClasses.entrySet().iterator().next());
+ return builder.append(".").toString();
}
+
StringBuilder builder =
new StringBuilder("Compilation can't be completed because the following ")
.append(missingClasses.size())
.append(" classes are missing:");
- for (ClassReference missingClass : missingClasses) {
- builder.append(System.lineSeparator()).append("- ").append(missingClass.getTypeName());
- }
+ missingClasses.forEach(
+ (missingClass, contexts) ->
+ writeMissingClass(
+ builder.append(System.lineSeparator()).append("- "), missingClass, contexts));
return builder.toString();
}
private String getNonFatalDiagnosticMessage() {
StringBuilder builder = new StringBuilder();
- Iterator<ClassReference> missingClassesIterator = missingClasses.iterator();
- while (missingClassesIterator.hasNext()) {
- ClassReference missingClass = missingClassesIterator.next();
- builder.append("Missing class ").append(missingClass.getTypeName());
- if (missingClassesIterator.hasNext()) {
- builder.append(System.lineSeparator());
- }
- }
+ Iterator<Entry<ClassReference, MissingClassAccessContexts>> missingClassesIterator =
+ missingClasses.entrySet().iterator();
+
+ // The diagnostic is always non-empty.
+ assert missingClassesIterator.hasNext();
+
+ // Write first line.
+ writeMissingClass(builder.append("Missing class "), missingClassesIterator.next());
+
+ // Write remaining lines with line separator before.
+ missingClassesIterator.forEachRemaining(
+ missingClassInfo ->
+ writeMissingClass(
+ builder.append(System.lineSeparator()).append("Missing class "), missingClassInfo));
+
return builder.toString();
}
+ private static void writeMissingClass(
+ StringBuilder builder, Entry<ClassReference, MissingClassAccessContexts> missingClassInfo) {
+ writeMissingClass(builder, missingClassInfo.getKey(), missingClassInfo.getValue());
+ }
+
+ private static void writeMissingClass(
+ StringBuilder builder, ClassReference missingClass, MissingClassAccessContexts contexts) {
+ builder
+ .append(missingClass.getTypeName())
+ .append(contexts.getReferencedFromMessageSuffix(missingClass));
+ }
+
public static class Builder {
private boolean fatal;
- private ImmutableSortedSet.Builder<ClassReference> missingClassesBuilder =
- ImmutableSortedSet.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
+ private ImmutableSortedMap.Builder<ClassReference, MissingClassAccessContexts>
+ missingClassesBuilder =
+ ImmutableSortedMap.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
- public MissingClassesDiagnostic.Builder addMissingClasses(Collection<DexType> missingClasses) {
- for (DexType missingClass : missingClasses) {
- missingClassesBuilder.add(Reference.classFromDescriptor(missingClass.toDescriptorString()));
- }
+ public MissingClassesDiagnostic.Builder addMissingClasses(
+ Map<DexType, Set<DexReference>> missingClasses) {
+ missingClasses.forEach(
+ (missingClass, contexts) ->
+ missingClassesBuilder.put(
+ Reference.classFromDescriptor(missingClass.toDescriptorString()),
+ MissingClassAccessContexts.builder().addAll(contexts).build()));
return this;
}
diff --git a/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java
new file mode 100644
index 0000000..cb26884
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java
@@ -0,0 +1,18 @@
+// 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.utils;
+
+import com.android.tools.r8.references.FieldReference;
+
+public class FieldReferenceUtils {
+
+ public static String toSourceString(FieldReference fieldReference) {
+ return fieldReference.getFieldType().getTypeName()
+ + " "
+ + fieldReference.getHolderClass().getTypeName()
+ + "."
+ + fieldReference.getFieldName();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 7f48f2a..b62e36e 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -14,11 +14,20 @@
return toSourceString(methodReference, false, false);
}
+ public static String toSourceString(MethodReference methodReference) {
+ return toSourceString(methodReference, true, true);
+ }
+
public static String toSourceString(
MethodReference methodReference, boolean includeHolder, boolean includeReturnType) {
StringBuilder builder = new StringBuilder();
if (includeReturnType) {
- builder.append(methodReference.getReturnType().getTypeName()).append(" ");
+ builder
+ .append(
+ methodReference.getReturnType() != null
+ ? methodReference.getReturnType().getTypeName()
+ : "void")
+ .append(" ");
}
if (includeHolder) {
builder.append(methodReference.getHolderClass().getTypeName()).append(".");
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index c67225c..022f5d9 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -37,6 +37,14 @@
return result;
}
+ public static <T> Set<T> newIdentityHashSet(Iterable<T> c1, Iterable<T> c2, Iterable<T> c3) {
+ Set<T> result = Sets.newIdentityHashSet();
+ c1.forEach(result::add);
+ c2.forEach(result::add);
+ c3.forEach(result::add);
+ return result;
+ }
+
public static <T> Set<T> newIdentityHashSet(int capacity) {
return Collections.newSetFromMap(new IdentityHashMap<>(capacity));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 66e2ec7..a9e05a3 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -63,9 +63,7 @@
testMissingNestHostError(true);
testIncompleteNestError(true);
}
- // TODO R8:
- // appView.options().reportMissingNestHost(clazz);
- // clazz.clearNestHost();
+
@Test
public void testWarningR8() throws Exception {
testIncompleteNestWarning(false, parameters.isDexRuntime());
@@ -98,7 +96,6 @@
.setMinApi(parameters.getApiLevel())
.addProgramFiles(matchingClasses)
.addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
- .addDontWarn("java.lang.invoke.StringConcatFactory")
.addOptionsModification(
options -> {
options.ignoreMissingClasses = ignoreMissingClasses;
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
index 8f43ab8..975e84e 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
@@ -9,9 +9,13 @@
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.MissingClassesDiagnostic;
+import com.android.tools.r8.utils.MethodReferenceUtils;
import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import com.google.common.collect.ImmutableSet;
import org.junit.Test;
public class MissingClassReferencedFromNewInstanceTest extends MissingClassesTestBase {
@@ -24,31 +28,60 @@
@Test
public void test() throws Exception {
AssertUtils.assertFailsCompilationIf(
- // TODO(b/175542052): Should not fail compilation with -dontwarn Main.
- !getDontWarnConfiguration().isDontWarnMissingClass(),
+ getDontWarnConfiguration().isNone(),
() ->
compileWithExpectedDiagnostics(
Main.class, MissingClass.class, this::inspectDiagnostics));
}
private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
- // TODO(b/175542052): Should also not have any diagnostics with -dontwarn Main.
- if (getDontWarnConfiguration().isDontWarnMissingClass()) {
+ // There should be no diagnostics with -dontwarn.
+ if (getDontWarnConfiguration().isDontWarn()) {
diagnostics.assertNoMessages();
return;
}
- diagnostics
- .assertOnlyErrors()
- .assertErrorsCount(1)
- .assertAllErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+ // There should be a single warning if we are compiling with -ignorewarnings. Otherwise, there
+ // should be a single error.
+ MissingClassesDiagnostic diagnostic;
+ if (getDontWarnConfiguration().isIgnoreWarnings()) {
+ diagnostics
+ .assertOnlyWarnings()
+ .assertWarningsCount(1)
+ .assertAllWarningsMatch(diagnosticType(MissingClassesDiagnostic.class));
+ diagnostic = (MissingClassesDiagnostic) diagnostics.getWarnings().get(0);
+ } else {
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsCount(1)
+ .assertAllErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+ diagnostic = (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+ }
- MissingClassesDiagnostic diagnostic = (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+ // Inspect the diagnostic.
assertEquals(
- !getDontWarnConfiguration().isDontWarnMissingClass(),
- diagnostic.getMissingClasses().stream()
- .map(TypeReference::getTypeName)
- .anyMatch(MissingClass.class.getTypeName()::equals));
+ ImmutableSet.of(Reference.classFromClass(MissingClass.class)),
+ diagnostic.getMissingClasses());
+ assertEquals(getExpectedDiagnosticMessage(), diagnostic.getDiagnosticMessage());
+ }
+
+ private String getExpectedDiagnosticMessage() {
+ ClassReference missingClass = Reference.classFromClass(MissingClass.class);
+ MethodReference context;
+ try {
+ context = Reference.methodFromMethod(Main.class.getDeclaredMethod("main", String[].class));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ String referencedFromSuffix =
+ " (referenced from: " + MethodReferenceUtils.toSourceString(context) + ")";
+ if (getDontWarnConfiguration().isIgnoreWarnings()) {
+ return "Missing class " + missingClass.getTypeName() + referencedFromSuffix;
+ }
+ return "Compilation can't be completed because the following class is missing: "
+ + missingClass.getTypeName()
+ + referencedFromSuffix
+ + ".";
}
static class Main {
@@ -57,6 +90,4 @@
new MissingClass();
}
}
-
- static class MissingClass {}
}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 7c72273..769ac98 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -21,8 +21,13 @@
enum DontWarnConfiguration {
DONT_WARN_MAIN_CLASS,
DONT_WARN_MISSING_CLASS,
+ IGNORE_WARNINGS,
NONE;
+ public boolean isDontWarn() {
+ return isDontWarnMainClass() || isDontWarnMissingClass();
+ }
+
public boolean isDontWarnMainClass() {
return this == DONT_WARN_MAIN_CLASS;
}
@@ -30,8 +35,18 @@
public boolean isDontWarnMissingClass() {
return this == DONT_WARN_MISSING_CLASS;
}
+
+ public boolean isIgnoreWarnings() {
+ return this == IGNORE_WARNINGS;
+ }
+
+ public boolean isNone() {
+ return this == NONE;
+ }
}
+ static class MissingClass {}
+
private final DontWarnConfiguration dontWarnConfiguration;
private final TestParameters parameters;
@@ -53,12 +68,16 @@
return testForR8(parameters.getBackend())
.addProgramClasses(mainClass)
.addKeepMainRule(mainClass)
+ .allowDiagnosticWarningMessages(dontWarnConfiguration.isIgnoreWarnings())
.applyIf(
dontWarnConfiguration.isDontWarnMainClass(),
testBuilder -> testBuilder.addDontWarn(mainClass))
.applyIf(
dontWarnConfiguration.isDontWarnMissingClass(),
testBuilder -> testBuilder.addDontWarn(missingClass))
+ .applyIf(
+ dontWarnConfiguration.isIgnoreWarnings(),
+ testBuilder -> testBuilder.addKeepRules("-ignorewarnings"))
.addOptionsModification(TestingOptions::enableExperimentalMissingClassesReporting)
.setMinApi(parameters.getApiLevel())
.compileWithExpectedDiagnostics(diagnosticsConsumer);
diff --git a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
index 927decb..d01d917 100644
--- a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
@@ -46,10 +46,6 @@
.build()));
}
- private boolean isDesugaring() {
- return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
- }
-
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 5ad87de..7ba46d2 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
import static com.android.tools.r8.ToolHelper.EXAMPLES_DIR;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.Diagnostic;
@@ -26,7 +27,6 @@
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -92,12 +92,10 @@
new DiagnosticsHandler() {
@Override
public void error(Diagnostic error) {
- String expectedErrorMessage =
- "Compilation can't be completed because the class java.lang.Object is missing.";
- if (error.getDiagnosticMessage().equals(expectedErrorMessage)) {
- return;
- }
- throw new RuntimeException("Unexpected compilation error");
+ assertEquals(
+ "Compilation can't be completed because the following class is missing: "
+ + "java.lang.Object.",
+ error.getDiagnosticMessage());
}
};
R8Command.Builder builder = R8Command.builder(handler)
@@ -146,7 +144,7 @@
"shaking1",
"print-mapping-" + backend.name().toLowerCase() + ".ref")),
StandardCharsets.UTF_8);
- Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
+ assertEquals(sorted(refMapping), sorted(actualMapping));
}
private static String sorted(String str) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
index e9634a5..174e734 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.utils.ThrowingAction;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
import java.util.function.Consumer;
public class AssertUtils {
@@ -54,7 +56,7 @@
action.execute();
fail("Expected action to fail with an exception, but succeeded");
} catch (Throwable e) {
- assertEquals(clazz, e.getClass());
+ assertEquals(printStackTraceToString(e), clazz, e.getClass());
if (consumer != null) {
consumer.accept(e);
}
@@ -63,4 +65,12 @@
action.execute();
}
}
+
+ private static String printStackTraceToString(Throwable e) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (PrintStream ps = new PrintStream(baos)) {
+ e.printStackTrace(ps);
+ }
+ return baos.toString();
+ }
}