blob: 0f4e97b0b45a1326defbd9b9833be13824c07ce5 [file] [log] [blame]
// 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.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.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.SortedMap;
import java.util.function.Function;
@Keep
public class MissingClassesDiagnostic implements Diagnostic {
private static class MissingClassAccessContexts {
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 (!fieldContexts.isEmpty()) {
return " (referenced from: "
+ FieldReferenceUtils.toSourceString(fieldContexts.iterator().next())
+ ")";
}
if (!methodContexts.isEmpty()) {
return " (referenced from: "
+ MethodReferenceUtils.toSourceString(methodContexts.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();
return classContext
.map(classReference -> " (referenced from: " + classReference.getTypeName() + ")")
.orElse("");
}
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.keySet();
}
/** A missing class(es) failure can generally not be attributed to a single origin. */
@Override
public Origin getOrigin() {
return Origin.unknown();
}
/** A missing class(es) failure can generally not be attributed to a single position. */
@Override
public Position getPosition() {
return Position.UNKNOWN;
}
@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();
// 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 ImmutableSortedMap.Builder<ClassReference, MissingClassAccessContexts>
missingClassesBuilder =
ImmutableSortedMap.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
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;
}
public MissingClassesDiagnostic.Builder setFatal(boolean fatal) {
this.fatal = fatal;
return this;
}
public MissingClassesDiagnostic build() {
return new MissingClassesDiagnostic(fatal, missingClassesBuilder.build());
}
}
}