Test missing classes from lambda signatures
Bug: 179466825
Change-Id: I5e2ca3b944f2b2fd91aa71417d875a141f234b2d
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
index 3406c0d..23abebb 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
@@ -10,6 +10,7 @@
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.graph.ProgramDerivedContext;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
@@ -68,8 +69,10 @@
private final Set<DexReference> contexts = Sets.newIdentityHashSet();
- Builder addAll(Set<DexReference> contexts) {
- this.contexts.addAll(contexts);
+ Builder addAll(Set<ProgramDerivedContext> contexts) {
+ for (ProgramDerivedContext context : contexts) {
+ this.contexts.add(context.getContext().getReference());
+ }
return this;
}
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
index 044b1d5..ab2d603 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
@@ -7,8 +7,8 @@
import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDerivedContext;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.references.ClassReference;
@@ -24,13 +24,11 @@
public class MissingDefinitionsDiagnosticImpl implements MissingDefinitionsDiagnostic {
- private final boolean fatal;
private final SortedMap<ClassReference, MissingClassAccessContexts> missingClasses;
private MissingDefinitionsDiagnosticImpl(
- boolean fatal, SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
+ SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
assert !missingClasses.isEmpty();
- this.fatal = fatal;
this.missingClasses = missingClasses;
}
@@ -62,30 +60,6 @@
@Override
public String getDiagnosticMessage() {
- return fatal ? getFatalDiagnosticMessage() : getNonFatalDiagnosticMessage();
- }
-
- private String getFatalDiagnosticMessage() {
- if (missingClasses.size() == 1) {
- 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:");
- missingClasses.forEach(
- (missingClass, contexts) ->
- writeMissingClass(
- builder.append(System.lineSeparator()).append("- "), missingClass, contexts));
- return builder.toString();
- }
-
- private String getNonFatalDiagnosticMessage() {
StringBuilder builder = new StringBuilder();
Iterator<Entry<ClassReference, MissingClassAccessContexts>> missingClassesIterator =
missingClasses.entrySet().iterator();
@@ -119,12 +93,11 @@
public static class Builder {
- private boolean fatal;
private ImmutableSortedMap.Builder<ClassReference, MissingClassAccessContexts>
missingClassesBuilder =
ImmutableSortedMap.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
- public Builder addMissingClasses(Map<DexType, Set<DexReference>> missingClasses) {
+ public Builder addMissingClasses(Map<DexType, Set<ProgramDerivedContext>> missingClasses) {
missingClasses.forEach(
(missingClass, contexts) ->
missingClassesBuilder.put(
@@ -133,13 +106,8 @@
return this;
}
- public Builder setFatal(boolean fatal) {
- this.fatal = fatal;
- return this;
- }
-
public MissingDefinitionsDiagnostic build() {
- return new MissingDefinitionsDiagnosticImpl(fatal, missingClassesBuilder.build());
+ return new MissingDefinitionsDiagnosticImpl(missingClassesBuilder.build());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
index ff4a73d..d14300b 100644
--- a/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
@@ -4,11 +4,10 @@
package com.android.tools.r8.errors.dontwarn;
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.Definition;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.utils.InternalOptions;
-import java.util.Set;
public abstract class DontWarnConfiguration {
@@ -24,10 +23,8 @@
return new EmptyDontWarnConfiguration();
}
- public abstract Set<DexType> getNonMatches(Set<DexType> types);
-
- public final boolean matches(DexClass clazz) {
- return matches(clazz.getType());
+ public final boolean matches(Definition clazz) {
+ return matches(clazz.getContextType());
}
public abstract boolean matches(DexType type);
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
index e115168..4cc7f04 100644
--- a/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
@@ -6,16 +6,10 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.InternalOptions;
-import java.util.Set;
public class EmptyDontWarnConfiguration extends DontWarnConfiguration {
@Override
- public Set<DexType> getNonMatches(Set<DexType> types) {
- return types;
- }
-
- @Override
public boolean matches(DexType type) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
index 89bcb10..ae31723 100644
--- a/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
@@ -29,17 +29,6 @@
}
@Override
- public Set<DexType> getNonMatches(Set<DexType> types) {
- Set<DexType> nonMatches = Sets.newIdentityHashSet();
- for (DexType type : types) {
- if (!matches(type)) {
- nonMatches.add(type);
- }
- }
- return nonMatches;
- }
-
- @Override
public boolean matches(DexType type) {
for (ProguardClassNameList dontWarnPattern : dontWarnPatterns) {
if (dontWarnPattern.matches(type)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 8b09674..7622121 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -39,6 +39,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
@@ -454,6 +455,10 @@
return appInfo.options();
}
+ public Reporter reporter() {
+ return options().reporter;
+ }
+
public TestingOptions testing() {
return options().testing;
}
diff --git a/src/main/java/com/android/tools/r8/graph/Definition.java b/src/main/java/com/android/tools/r8/graph/Definition.java
index a7e4ed9..abf7c07 100644
--- a/src/main/java/com/android/tools/r8/graph/Definition.java
+++ b/src/main/java/com/android/tools/r8/graph/Definition.java
@@ -18,4 +18,12 @@
DexType getContextType();
DexReference getReference();
+
+ default boolean isProgramDefinition() {
+ return false;
+ }
+
+ default ProgramDefinition asProgramDefinition() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 862efbc..7310270 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -38,6 +38,16 @@
return null;
}
+ @Override
+ default boolean isProgramDefinition() {
+ return true;
+ }
+
+ @Override
+ default ProgramDefinition asProgramDefinition() {
+ return this;
+ }
+
default boolean isProgramField() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java b/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
index f9ec920..d46cf33 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
@@ -7,4 +7,8 @@
public interface ProgramDerivedContext {
Definition getContext();
+
+ default boolean isProgramContext() {
+ return getContext().isProgramDefinition();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 4041efd..cb446d6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -41,8 +42,9 @@
}
public static R8CfInstructionDesugaringEventConsumer createForR8(
- AppView<? extends AppInfoWithClassHierarchy> appView) {
- return new R8CfInstructionDesugaringEventConsumer(appView);
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer) {
+ return new R8CfInstructionDesugaringEventConsumer(appView, lambdaClassConsumer);
}
public static CfInstructionDesugaringEventConsumer createForDesugaredCode() {
@@ -162,14 +164,17 @@
extends CfInstructionDesugaringEventConsumer {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer;
private final Map<LambdaClass, ProgramMethod> synthesizedLambdaClasses =
new IdentityHashMap<>();
private final List<InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges = new ArrayList<>();
public R8CfInstructionDesugaringEventConsumer(
- AppView<? extends AppInfoWithClassHierarchy> appView) {
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer) {
this.appView = appView;
+ this.lambdaClassConsumer = lambdaClassConsumer;
}
@Override
@@ -184,6 +189,7 @@
synchronized (synthesizedLambdaClasses) {
synthesizedLambdaClasses.put(lambdaClass, context);
}
+ lambdaClassConsumer.accept(lambdaClass, context);
}
@Override
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 2f7edb2..caa97fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -90,6 +90,7 @@
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.R8CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
@@ -108,6 +109,7 @@
import com.android.tools.r8.shaking.RootSetUtils.RootSet;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
+import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
@@ -249,6 +251,11 @@
private final Map<DexProgramClass, ProgramFieldSet> reachableInstanceFields =
Maps.newIdentityHashMap();
+ // TODO(b/180091213): Remove when supported by synthetic items.
+ /** The synthesizing contexts for the synthesized lambda classes. */
+ private final Map<DexProgramClass, ProgramMethod> lambdaSynthesizingContexts =
+ new IdentityHashMap<>();
+
/**
* Set of types that are mentioned in the program. We at least need an empty abstract class item
* for these.
@@ -3202,7 +3209,8 @@
return;
}
R8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
- CfInstructionDesugaringEventConsumer.createForR8(appView);
+ CfInstructionDesugaringEventConsumer.createForR8(
+ appView, this::recordLambdaSynthesizingContext);
ThreadUtils.processItems(
pendingDesugaring,
method ->
@@ -3213,6 +3221,12 @@
pendingDesugaring.clear();
}
+ private void recordLambdaSynthesizingContext(LambdaClass lambdaClass, ProgramMethod context) {
+ synchronized (lambdaSynthesizingContexts) {
+ lambdaSynthesizingContexts.put(lambdaClass.getLambdaProgramClass(), context);
+ }
+ }
+
private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
DexProgramClass holder = bridge.getHolder();
@@ -3305,6 +3319,13 @@
// Verify the references on the pruned application after type synthesis.
assert verifyReferences(app);
+ SynthesizingContextOracle lambdaSynthesizingContextOracle =
+ syntheticClass -> {
+ ProgramMethod lambdaSynthesisContext = lambdaSynthesizingContexts.get(syntheticClass);
+ return lambdaSynthesisContext != null
+ ? ImmutableSet.of(lambdaSynthesisContext.getReference())
+ : ImmutableSet.of(syntheticClass.getType());
+ };
AppInfoWithLiveness appInfoWithLiveness =
new AppInfoWithLiveness(
appInfo.getSyntheticItems().commit(app),
@@ -3312,7 +3333,10 @@
appInfo.getMainDexInfo(),
deadProtoTypes,
appView.testing().enableExperimentalMissingClassesReporting
- ? missingClassesBuilder.reportMissingClasses(appView)
+ ? (mode.isInitialTreeShaking()
+ ? missingClassesBuilder.reportMissingClasses(
+ appView, lambdaSynthesizingContextOracle)
+ : missingClassesBuilder.assertNoMissingClasses(appView))
: missingClassesBuilder.ignoreMissingClasses(),
SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
Enqueuer.toDescriptorSet(targetedMethods.getItems()),
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 aa49727..9f306c0 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -7,24 +7,30 @@
import static com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
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 static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet;
import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
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.DexProgramClass;
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.graph.ProgramDerivedContext;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.synthesis.CommittedItems;
+import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
import com.android.tools.r8.utils.InternalOptions;
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.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
@@ -62,7 +68,8 @@
public static class Builder {
private final Set<DexType> alreadyMissingClasses;
- private final Map<DexType, Set<DexReference>> newMissingClasses = new IdentityHashMap<>();
+ private final Map<DexType, Set<ProgramDerivedContext>> 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.
@@ -81,16 +88,15 @@
assert context.getContext().getContextType() != type;
if (!alreadyMissingClasses.contains(type)) {
newMissingClasses
- .computeIfAbsent(type, ignore -> Sets.newIdentityHashSet())
- .add(context.getContext().getReference());
+ .computeIfAbsent(type, ignore -> newProgramDerivedContextSet())
+ .add(context);
}
}
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);
+ // The legacy reporting is context insensitive, so we just use an empty set of contexts.
+ newMissingClasses.computeIfAbsent(type, ignore -> newProgramDerivedContextSet());
}
}
@@ -115,34 +121,109 @@
return this;
}
+ public MissingClasses assertNoMissingClasses(AppView<?> appView) {
+ assert getMissingClassesToBeReported(appView, clazz -> ImmutableSet.of(clazz.getType()))
+ .isEmpty();
+ return build();
+ }
+
@Deprecated
public MissingClasses ignoreMissingClasses() {
return build();
}
public MissingClasses reportMissingClasses(AppView<?> appView) {
- InternalOptions options = appView.options();
- Map<DexType, Set<DexReference>> missingClassesToBeReported =
- getMissingClassesToBeReported(appView);
+ return reportMissingClasses(appView, clazz -> ImmutableSet.of(clazz.getType()));
+ }
+
+ public MissingClasses reportMissingClasses(
+ AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) {
+ Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported =
+ getMissingClassesToBeReported(appView, synthesizingContextOracle);
if (!missingClassesToBeReported.isEmpty()) {
MissingDefinitionsDiagnostic diagnostic =
MissingDefinitionsDiagnosticImpl.builder()
.addMissingClasses(missingClassesToBeReported)
- .setFatal(!options.ignoreMissingClasses)
.build();
- if (options.ignoreMissingClasses) {
- options.reporter.warning(diagnostic);
+ if (appView.options().ignoreMissingClasses) {
+ appView.reporter().warning(diagnostic);
} else {
- throw options.reporter.fatalError(diagnostic);
+ throw appView.reporter().fatalError(diagnostic);
}
}
return build();
}
- private Map<DexType, Set<DexReference>> getMissingClassesToBeReported(AppView<?> appView) {
+ private void rewriteMissingClassContexts(
+ AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) {
+ Iterator<Entry<DexType, Set<ProgramDerivedContext>>> newMissingClassesIterator =
+ newMissingClasses.entrySet().iterator();
+ while (newMissingClassesIterator.hasNext()) {
+ Entry<DexType, Set<ProgramDerivedContext>> entry = newMissingClassesIterator.next();
+ entry.setValue(
+ rewriteMissingClassContextsForSingleMissingClass(
+ appView, entry.getValue(), synthesizingContextOracle));
+ }
+ }
+
+ private static Set<ProgramDerivedContext> rewriteMissingClassContextsForSingleMissingClass(
+ AppView<?> appView,
+ Set<ProgramDerivedContext> contexts,
+ SynthesizingContextOracle synthesizingContextOracle) {
+ if (contexts.isEmpty()) {
+ // Legacy reporting does not have any contexts.
+ return contexts;
+ }
+
+ Set<ProgramDerivedContext> rewrittenContexts = newProgramDerivedContextSet();
+ for (ProgramDerivedContext context : contexts) {
+ if (!context.isProgramContext()) {
+ rewrittenContexts.add(context);
+ continue;
+ }
+
+ DexProgramClass clazz = context.getContext().asProgramDefinition().getContextClass();
+ if (!appView.getSyntheticItems().isSyntheticClass(clazz)) {
+ rewrittenContexts.add(context);
+ continue;
+ }
+
+ // Rewrite the synthetic context to its synthesizing contexts.
+ Set<DexReference> synthesizingContextReferences =
+ appView.getSyntheticItems().getSynthesizingContexts(clazz, synthesizingContextOracle);
+ assert synthesizingContextReferences != null;
+ assert !synthesizingContextReferences.isEmpty();
+ for (DexReference synthesizingContextReference : synthesizingContextReferences) {
+ if (synthesizingContextReference.isDexMethod()) {
+ DexProgramClass holder =
+ appView
+ .definitionFor(synthesizingContextReference.getContextType())
+ .asProgramClass();
+ ProgramMethod synthesizingContext =
+ holder.lookupProgramMethod(synthesizingContextReference.asDexMethod());
+ assert synthesizingContext != null;
+ rewrittenContexts.add(synthesizingContext);
+ } else {
+ assert synthesizingContextReference.isDexType()
+ && synthesizingContextReference
+ .toSourceString()
+ .contains("-$$InternalSyntheticTwrCloseResource$")
+ : "Unexpected synthesizing context: "
+ + synthesizingContextReference.toSourceString();
+ }
+ }
+ }
+ return rewrittenContexts;
+ }
+
+ private Map<DexType, Set<ProgramDerivedContext>> getMissingClassesToBeReported(
+ AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) {
+ // Rewrite synthetic program contexts into their synthesizing contexts, to avoid reporting
+ // any synthetic contexts.
+ rewriteMissingClassContexts(appView, synthesizingContextOracle);
Predicate<DexType> allowedMissingClassesPredicate =
getIsAllowedMissingClassesPredicate(appView);
- Map<DexType, Set<DexReference>> missingClassesToBeReported =
+ Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported =
new IdentityHashMap<>(newMissingClasses.size());
newMissingClasses.forEach(
(missingClass, contexts) -> {
@@ -151,10 +232,20 @@
return;
}
+ // TODO(b/175543745): This is a legacy reported missing class; remove once no longer
+ // supported.
+ if (contexts.isEmpty()) {
+ missingClassesToBeReported.put(missingClass, contexts);
+ 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()));
+ context ->
+ appView
+ .getDontWarnConfiguration()
+ .matches(context.getContext().getContextType()));
// If there are any contexts not matched by a -dontwarn rule, then report.
if (!contexts.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 73b61b3..8263f40 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -217,9 +217,14 @@
// TODO(b/180091213): Implement this and remove client provided the oracle.
public Set<DexReference> getSynthesizingContexts(
- DexProgramClass clazz, Function<DexProgramClass, Set<DexReference>> oracle) {
+ DexProgramClass clazz, SynthesizingContextOracle oracle) {
assert isSyntheticClass(clazz);
- return oracle.apply(clazz);
+ return oracle.getSynthesizingContexts(clazz);
+ }
+
+ public interface SynthesizingContextOracle {
+
+ Set<DexReference> getSynthesizingContexts(DexProgramClass clazz);
}
// The compiler should not inspect the kind of a synthetic, so this provided only as a assertion
diff --git a/src/main/java/com/android/tools/r8/utils/collections/IdentityHashSetFromMap.java b/src/main/java/com/android/tools/r8/utils/collections/IdentityHashSetFromMap.java
new file mode 100644
index 0000000..0322582
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/IdentityHashSetFromMap.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2021, 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.collections;
+
+import com.android.tools.r8.graph.ProgramDerivedContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+public class IdentityHashSetFromMap<K, V> implements Set<V> {
+
+ private final Map<K, V> backing = new IdentityHashMap<>();
+ private final Function<V, K> valueToKeyMapping;
+
+ public IdentityHashSetFromMap(Function<V, K> valueToKeyMapping) {
+ this.valueToKeyMapping = valueToKeyMapping;
+ }
+
+ public static Set<ProgramDerivedContext> newProgramDerivedContextSet() {
+ return new IdentityHashSetFromMap<>(context -> context.getContext().getReference());
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean contains(Object o) {
+ return backing.containsKey(valueToKeyMapping.apply((V) o));
+ }
+
+ @Override
+ public Iterator<V> iterator() {
+ return backing.values().iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return backing.values().toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] ts) {
+ return backing.values().toArray(ts);
+ }
+
+ @Override
+ public boolean add(V v) {
+ return backing.put(valueToKeyMapping.apply(v), v) == null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean remove(Object o) {
+ return backing.remove(valueToKeyMapping.apply((V) o)) != null;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> collection) {
+ return backing.values().containsAll(collection);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends V> collection) {
+ boolean changed = false;
+ for (V element : collection) {
+ changed |= add(element);
+ }
+ return changed;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ Collection<Object> found = new ArrayList<>(collection.size());
+ for (Object element : collection) {
+ if (contains(element)) {
+ found.add(element);
+ }
+ }
+ if (found.size() < size()) {
+ clear();
+ addAll((Collection<V>) found);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ boolean changed = false;
+ for (Object element : collection) {
+ changed |= remove(element);
+ }
+ return changed;
+ }
+
+ @Override
+ public void clear() {
+ backing.clear();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index c9834f8..a1a5f88 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -179,6 +179,12 @@
return proguardMap;
}
+ public R8TestCompileResult inspectProguardMap(ThrowableConsumer<String> consumer)
+ throws Throwable {
+ consumer.accept(getProguardMap());
+ return this;
+ }
+
public Path writeProguardMap() throws IOException {
Path file = state.getNewTempFolder().resolve("out.zip");
writeProguardMap(file);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1b4ca35..8648de9 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -131,6 +131,10 @@
CF,
DEX;
+ public boolean isCf() {
+ return this == CF;
+ }
+
public boolean isDex() {
return this == DEX;
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 5730bea..78ac691 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -112,10 +112,6 @@
return self();
}
- public T addDontWarn(String className) {
- return addKeepRules("-dontwarn " + className);
- }
-
public T addDontWarn(Collection<String> classes) {
for (String clazz : classes) {
addKeepRules("-dontwarn " + clazz);
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java
new file mode 100644
index 0000000..a09c4d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java
@@ -0,0 +1,113 @@
+// 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.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUnusedLambdaParameterTest extends MissingClassesTestBase {
+
+ private static final MethodReference referencedFrom =
+ Reference.method(
+ Reference.classFromClass(Main.class),
+ "lambda$main$0",
+ ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+ null);
+
+ public MissingClassReferencedFromUnusedLambdaParameterTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test
+ public void testNoRules() throws Exception {
+ assertFailsCompilationIf(
+ parameters.isCfRuntime(),
+ () ->
+ compileWithExpectedDiagnostics(
+ Main.class,
+ parameters.isCfRuntime()
+ ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ addInterface()));
+ }
+
+ // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+ // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+ @Test
+ public void testDontWarnMainClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface()
+ .andThen(
+ builder ->
+ builder
+ .addDontWarn(Main.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ R8TestBuilder::allowUnusedDontWarnPatterns)));
+ }
+
+ // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+ // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+ @Test
+ public void testDontWarnMissingClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface()
+ .andThen(
+ builder ->
+ builder
+ .addDontWarn(Main.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ R8TestBuilder::allowUnusedDontWarnPatterns)));
+ }
+
+ @Test
+ public void testIgnoreWarnings() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ parameters.isCfRuntime()
+ ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ addInterface()
+ .andThen(
+ builder ->
+ builder
+ .addIgnoreWarnings()
+ .applyIf(
+ parameters.isCfRuntime(),
+ R8TestBuilder::allowDiagnosticWarningMessages)));
+ }
+
+ ThrowableConsumer<R8FullTestBuilder> addInterface() {
+ return builder -> builder.addProgramClasses(I.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I ignore = mc -> {};
+ }
+
+ /* private static synthetic void lambda$main$0(MissingClass mc) {} */
+ }
+
+ interface I {
+
+ void m(MissingClass mc);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java
new file mode 100644
index 0000000..c196013
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java
@@ -0,0 +1,113 @@
+// 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.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUnusedLambdaReturnTest extends MissingClassesTestBase {
+
+ private static final MethodReference referencedFrom =
+ Reference.method(
+ Reference.classFromClass(Main.class),
+ "lambda$main$0",
+ Collections.emptyList(),
+ Reference.classFromClass(MissingClass.class));
+
+ public MissingClassReferencedFromUnusedLambdaReturnTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test
+ public void testNoRules() throws Exception {
+ assertFailsCompilationIf(
+ parameters.isCfRuntime(),
+ () ->
+ compileWithExpectedDiagnostics(
+ Main.class,
+ parameters.isCfRuntime()
+ ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ addInterface()));
+ }
+
+ // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+ // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+ @Test
+ public void testDontWarnMainClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface()
+ .andThen(
+ builder ->
+ builder
+ .addDontWarn(Main.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ R8TestBuilder::allowUnusedDontWarnPatterns)));
+ }
+
+ // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+ // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+ @Test
+ public void testDontWarnMissingClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface()
+ .andThen(
+ builder ->
+ builder
+ .addDontWarn(MissingClass.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ R8TestBuilder::allowUnusedDontWarnPatterns)));
+ }
+
+ @Test
+ public void testIgnoreWarnings() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ parameters.isCfRuntime()
+ ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ addInterface()
+ .andThen(
+ builder ->
+ builder
+ .addIgnoreWarnings()
+ .applyIf(
+ parameters.isCfRuntime(),
+ R8TestBuilder::allowDiagnosticWarningMessages)));
+ }
+
+ ThrowableConsumer<R8FullTestBuilder> addInterface() {
+ return builder -> builder.addProgramClasses(I.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I ignore = () -> null;
+ }
+
+ /* private static synthetic MissingClass lambda$main$0() { return null; } */
+ }
+
+ interface I {
+
+ MissingClass m();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java
new file mode 100644
index 0000000..2da56a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java
@@ -0,0 +1,82 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.missingclasses.MissingClassReferencedFromUsedLambdaReturnTest.I;
+import com.android.tools.r8.missingclasses.MissingClassReferencedFromUsedLambdaReturnTest.Main;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUsedLambdaParameterTest extends MissingClassesTestBase {
+
+ private static final MethodReference referencedFrom =
+ Reference.method(
+ Reference.classFromClass(I.class),
+ "m",
+ ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+ null);
+
+ public MissingClassReferencedFromUsedLambdaParameterTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testNoRules() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+ addInterface());
+ }
+
+ @Test
+ public void testDontWarnMainClassAndInterface() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface().andThen(addDontWarn(Main.class, I.class)));
+ }
+
+ @Test
+ public void testDontWarnMissingClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface().andThen(addDontWarn(MissingClass.class)));
+ }
+
+ @Test
+ public void testIgnoreWarnings() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+ addInterface().andThen(addIgnoreWarnings()));
+ }
+
+ ThrowableConsumer<R8FullTestBuilder> addInterface() {
+ return builder -> builder.addProgramClasses(I.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I i = mc -> {};
+ i.m(null);
+ }
+
+ /* private static synthetic void lambda$main$0(MissingClass mc) {} */
+ }
+
+ interface I {
+
+ void m(MissingClass mc);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java
new file mode 100644
index 0000000..1b6781d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java
@@ -0,0 +1,80 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUsedLambdaReturnTest extends MissingClassesTestBase {
+
+ private static final MethodReference referencedFrom =
+ Reference.method(
+ Reference.classFromClass(I.class),
+ "m",
+ Collections.emptyList(),
+ Reference.classFromClass(MissingClass.class));
+
+ public MissingClassReferencedFromUsedLambdaReturnTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testNoRules() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+ addInterface());
+ }
+
+ @Test
+ public void testDontWarnMainClassAndInterface() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface().andThen(addDontWarn(Main.class, I.class)));
+ }
+
+ @Test
+ public void testDontWarnMissingClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface().andThen(addDontWarn(MissingClass.class)));
+ }
+
+ @Test
+ public void testIgnoreWarnings() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+ addInterface().andThen(addIgnoreWarnings()));
+ }
+
+ ThrowableConsumer<R8FullTestBuilder> addInterface() {
+ return builder -> builder.addProgramClasses(I.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I i = () -> null;
+ i.m();
+ }
+
+ /* private static synthetic MissingClass lambda$main$0() { return null; } */
+ }
+
+ interface I {
+
+ MissingClass m();
+ }
+}
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 3968b75..629fcb6 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -46,7 +46,7 @@
interface MissingInterface {}
- private final TestParameters parameters;
+ protected final TestParameters parameters;
@Parameters(name = "{0}")
public static List<Object[]> data() {
@@ -57,8 +57,8 @@
this.parameters = parameters;
}
- public ThrowableConsumer<R8FullTestBuilder> addDontWarn(Class<?> clazz) {
- return builder -> builder.addDontWarn(clazz);
+ public ThrowableConsumer<R8FullTestBuilder> addDontWarn(Class<?>... classes) {
+ return builder -> builder.addDontWarn(classes);
}
public ThrowableConsumer<R8FullTestBuilder> addIgnoreWarnings() {
@@ -111,25 +111,21 @@
void inspectDiagnosticsWithIgnoreWarnings(
TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
inspectDiagnosticsWithIgnoreWarnings(
- diagnostics,
- getExpectedDiagnosticMessageWithIgnoreWarnings(
- referencedFrom, ClassReference::getTypeName));
+ diagnostics, getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
}
void inspectDiagnosticsWithIgnoreWarnings(
TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
inspectDiagnosticsWithIgnoreWarnings(
diagnostics,
- getExpectedDiagnosticMessageWithIgnoreWarnings(
- referencedFrom, FieldReferenceUtils::toSourceString));
+ getExpectedDiagnosticMessage(referencedFrom, FieldReferenceUtils::toSourceString));
}
void inspectDiagnosticsWithIgnoreWarnings(
TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
inspectDiagnosticsWithIgnoreWarnings(
diagnostics,
- getExpectedDiagnosticMessageWithIgnoreWarnings(
- referencedFrom, MethodReferenceUtils::toSourceString));
+ getExpectedDiagnosticMessage(referencedFrom, MethodReferenceUtils::toSourceString));
}
void inspectDiagnosticsWithIgnoreWarnings(
@@ -144,36 +140,24 @@
assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
}
- private <T> String getExpectedDiagnosticMessageWithIgnoreWarnings(
- T referencedFrom, Function<T, String> toSourceStringFunction) {
- return "Missing class "
- + getMissingClassReference().getTypeName()
- + " (referenced from: "
- + toSourceStringFunction.apply(referencedFrom)
- + ")";
- }
-
void inspectDiagnosticsWithNoRules(
TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
inspectDiagnosticsWithNoRules(
- diagnostics,
- getExpectedDiagnosticMessageWithNoRules(referencedFrom, ClassReference::getTypeName));
+ diagnostics, getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
}
void inspectDiagnosticsWithNoRules(
TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
inspectDiagnosticsWithNoRules(
diagnostics,
- getExpectedDiagnosticMessageWithNoRules(
- referencedFrom, FieldReferenceUtils::toSourceString));
+ getExpectedDiagnosticMessage(referencedFrom, FieldReferenceUtils::toSourceString));
}
void inspectDiagnosticsWithNoRules(
TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
inspectDiagnosticsWithNoRules(
diagnostics,
- getExpectedDiagnosticMessageWithNoRules(
- referencedFrom, MethodReferenceUtils::toSourceString));
+ getExpectedDiagnosticMessage(referencedFrom, MethodReferenceUtils::toSourceString));
}
void inspectDiagnosticsWithNoRules(
@@ -188,13 +172,12 @@
assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
}
- private <T> String getExpectedDiagnosticMessageWithNoRules(
+ private <T> String getExpectedDiagnosticMessage(
T referencedFrom, Function<T, String> toSourceStringFunction) {
- return "Compilation can't be completed because the following class is missing: "
+ return "Missing class "
+ getMissingClassReference().getTypeName()
+ " (referenced from: "
+ toSourceStringFunction.apply(referencedFrom)
- + ")"
- + ".";
+ + ")";
}
}
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 63c9f40..2fac539 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -3,23 +3,24 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
import static com.android.tools.r8.ToolHelper.EXAMPLES_DIR;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersBuilder;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
import java.io.BufferedReader;
import java.io.IOException;
-import java.io.PrintStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -30,7 +31,6 @@
import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -52,13 +52,16 @@
parameters.assertNoneRuntime();
}
- private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
-
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
- @Rule
- public ExpectedException thrown = ExpectedException.none();
+ private Path getProgramFiles(String test) {
+ return Paths.get(EXAMPLES_BUILD_DIR, test + ".jar");
+ }
+
+ private byte[] getProgramDexFileData(String test) throws IOException {
+ return Files.readAllBytes(Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex"));
+ }
private void finishBuild(R8Command.Builder builder, Path out, String test) throws IOException {
Path input;
@@ -75,79 +78,77 @@
@Test
public void testIgnoreWarnings() throws Exception {
// Generate R8 processed version without library option.
- Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
- Path ignoreWarnings = Paths.get(VALID_PROGUARD_DIR, "ignorewarnings.flags");
- R8Command.Builder builder = R8Command.builder()
- .addProguardConfigurationFiles(keepRules, ignoreWarnings);
- finishBuild(builder, out, test);
- R8.run(builder.build());
+ testForR8(backend)
+ .applyIf(
+ backend.isCf(),
+ builder -> builder.addProgramFiles(getProgramFiles(test)),
+ builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+ .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
+ .addIgnoreWarnings()
+ .compile();
}
@Test(expected = CompilationFailedException.class)
public void testMissingLibrary() throws Exception {
// Generate R8 processed version without library option.
- Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
- DiagnosticsHandler handler =
- new DiagnosticsHandler() {
- @Override
- public void error(Diagnostic error) {
- assertEquals(
- "Compilation can't be completed because the following class is missing: "
- + "java.lang.Object.",
- error.getDiagnosticMessage());
- }
- };
- R8Command.Builder builder = R8Command.builder(handler)
- .addProguardConfigurationFiles(keepRules);
- finishBuild(builder, out, test);
- R8.run(builder.build());
+ testForR8(backend)
+ .applyIf(
+ backend.isCf(),
+ builder -> builder.addProgramFiles(getProgramFiles(test)),
+ builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+ .addLibraryFiles()
+ .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
+ .allowDiagnosticErrorMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(diagnosticType(MissingDefinitionsDiagnostic.class));
+
+ MissingDefinitionsDiagnostic diagnostic =
+ (MissingDefinitionsDiagnostic) diagnostics.getErrors().get(0);
+ assertThat(
+ diagnostic.getDiagnosticMessage(),
+ allOf(
+ containsString("Missing class java.io.PrintStream"),
+ containsString("Missing class java.lang.Object"),
+ containsString("Missing class java.lang.String"),
+ containsString("Missing class java.lang.StringBuilder"),
+ containsString("Missing class java.lang.System")));
+ });
}
@Test
- public void testPrintMapping() throws Exception {
+ public void testPrintMapping() throws Throwable {
// Generate R8 processed version without library option.
String test = "shaking1";
- Path out = temp.getRoot().toPath();
- Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
-
- // Create a flags file in temp dir requesting dump of the mapping.
- // The mapping file will be created alongside the flags file in temp dir.
- Path printMapping = out.resolve("printmapping.flags");
- try (PrintStream mapping = new PrintStream(printMapping.toFile())) {
- mapping.println("-printmapping mapping.txt");
- }
-
- R8Command.Builder builder = R8Command.builder()
- .addProguardConfigurationFiles(keepRules, printMapping);
- // Turn off inlining, as we want the mapping that is printed to be stable.
- finishBuild(builder, out, test);
- if (backend == Backend.DEX) {
- builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
- } else {
- assert backend == Backend.CF;
- builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
- }
- ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
-
- Path outputmapping = out.resolve("mapping.txt");
- // Remove comments.
- String actualMapping =
- Stream.of(new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8).split("\n"))
- .filter(line -> !line.startsWith("#"))
- .collect(Collectors.joining("\n"));
- String refMapping =
- new String(
- Files.readAllBytes(
- Paths.get(
- EXAMPLES_DIR,
- "shaking1",
- "print-mapping-" + backend.name().toLowerCase() + ".ref")),
- StandardCharsets.UTF_8);
- assertEquals(sorted(refMapping), sorted(actualMapping));
+ testForR8(backend)
+ .applyIf(
+ backend.isCf(),
+ builder -> builder.addProgramFiles(getProgramFiles(test)),
+ builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+ .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
+ .addOptionsModification(options -> options.enableInlining = false)
+ .compile()
+ .inspectProguardMap(
+ proguardMap -> {
+ // Remove comments.
+ String actualMapping =
+ Stream.of(proguardMap.split("\n"))
+ .filter(line -> !line.startsWith("#"))
+ .collect(Collectors.joining("\n"));
+ String refMapping =
+ new String(
+ Files.readAllBytes(
+ Paths.get(
+ EXAMPLES_DIR,
+ "shaking1",
+ "print-mapping-" + backend.name().toLowerCase() + ".ref")),
+ StandardCharsets.UTF_8);
+ assertEquals(sorted(refMapping), sorted(actualMapping));
+ });
}
private static String sorted(String str) {