blob: 00f905e8ca93b9dca07a39fab05390de4d94a519 [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.tracereferences;
import static com.android.tools.r8.utils.FileUtils.isArchive;
import static com.android.tools.r8.utils.FileUtils.isClassFile;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.Keep;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.dump.DumpOptions;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.ArchiveResourceProvider;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@Keep
public class TraceReferencesCommand {
private final boolean printHelp;
private final boolean printVersion;
private final Reporter reporter;
private final ImmutableList<ClassFileResourceProvider> library;
private final ImmutableList<ClassFileResourceProvider> traceTarget;
private final ImmutableList<ProgramResourceProvider> traceSource;
private final TraceReferencesConsumer consumer;
TraceReferencesCommand(
boolean printHelp,
boolean printVersion,
Reporter reporter,
ImmutableList<ClassFileResourceProvider> library,
ImmutableList<ClassFileResourceProvider> traceTarget,
ImmutableList<ProgramResourceProvider> traceSource,
TraceReferencesConsumer consumer) {
this.printHelp = printHelp;
this.printVersion = printVersion;
this.reporter = reporter;
this.library = library;
this.traceTarget = traceTarget;
this.traceSource = traceSource;
this.consumer = consumer;
}
TraceReferencesCommand(boolean printHelp, boolean printVersion) {
this.printHelp = printHelp;
this.printVersion = printVersion;
this.reporter = null;
this.library = null;
this.traceTarget = null;
this.traceSource = null;
this.consumer = null;
}
/**
* Utility method for obtaining a <code>ReferenceTraceCommand.Builder</code>.
*
* @param diagnosticsHandler The diagnostics handler for consuming messages.
*/
public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
return new Builder(diagnosticsHandler);
}
/**
* Utility method for obtaining a <code>ReferenceTraceCommand.Builder</code> with a default
* diagnostics handler.
*/
public static Builder builder() {
return new Builder();
}
public static Builder parse(String[] args, Origin origin) {
return TraceReferencesCommandParser.parse(args, origin);
}
public static Builder parse(String[] args, Origin origin, DiagnosticsHandler diagnosticsHandler) {
return TraceReferencesCommandParser.parse(args, origin, diagnosticsHandler);
}
public static Builder parse(Collection<String> args, Origin origin) {
return TraceReferencesCommandParser.parse(args.toArray(new String[args.size()]), origin);
}
public boolean isPrintHelp() {
return printHelp;
}
public boolean isPrintVersion() {
return printVersion;
}
@Keep
public static class Builder {
private boolean printHelp = false;
private boolean printVersion = false;
private final Reporter reporter;
private final ImmutableList.Builder<ClassFileResourceProvider> libraryBuilder =
ImmutableList.builder();
private final ImmutableList.Builder<ClassFileResourceProvider> traceTargetBuilder =
ImmutableList.builder();
private final ImmutableList.Builder<ProgramResourceProvider> traceSourceBuilder =
ImmutableList.builder();
private TraceReferencesConsumer consumer;
private Builder() {
this(new DiagnosticsHandler() {});
}
private Builder(DiagnosticsHandler diagnosticsHandler) {
this.reporter = new Reporter(diagnosticsHandler);
}
Reporter getReporter() {
return reporter;
}
/** True if the print-help flag is enabled. */
public boolean isPrintHelp() {
return printHelp;
}
/** Set the value of the print-help flag. */
public Builder setPrintHelp(boolean printHelp) {
this.printHelp = printHelp;
return this;
}
/** True if the print-version flag is enabled. */
public boolean isPrintVersion() {
return printVersion;
}
/** Set the value of the print-version flag. */
public Builder setPrintVersion(boolean printVersion) {
this.printVersion = printVersion;
return this;
}
private static String extractClassDescriptor(byte[] data) {
class ClassNameExtractor extends ClassVisitor {
private String className;
private ClassNameExtractor() {
super(ASM_VERSION);
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
className = name;
}
String getClassInternalType() {
return className;
}
}
ClassReader reader = new ClassReader(data);
ClassNameExtractor extractor = new ClassNameExtractor();
reader.accept(
extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return "L" + extractor.getClassInternalType() + ";";
}
private static class SingleClassClassFileResourceProvider implements ClassFileResourceProvider {
private final String descriptor;
private final ProgramResource programResource;
SingleClassClassFileResourceProvider(Origin origin, byte[] data) {
this.descriptor = extractClassDescriptor(data);
this.programResource =
ProgramResource.fromBytes(origin, Kind.CF, data, ImmutableSet.of(descriptor));
}
@Override
public Set<String> getClassDescriptors() {
return ImmutableSet.of(descriptor);
}
@Override
public ProgramResource getProgramResource(String descriptor) {
return descriptor.equals(this.descriptor) ? programResource : null;
}
}
private ClassFileResourceProvider singleClassFileClassFileResourceProvider(Path file)
throws IOException {
return new SingleClassClassFileResourceProvider(
new PathOrigin(file), Files.readAllBytes(file));
}
private ProgramResourceProvider singleClassFileProgramResourceProvider(Path file)
throws IOException {
byte[] bytes = Files.readAllBytes(file);
String descriptor = extractClassDescriptor(bytes);
return new ProgramResourceProvider() {
@Override
public Collection<ProgramResource> getProgramResources() {
return ImmutableList.of(
ProgramResource.fromBytes(
new PathOrigin(file), Kind.CF, bytes, ImmutableSet.of(descriptor)));
}
};
}
private void addLibraryOrTargetFile(
Path file, ImmutableList.Builder<ClassFileResourceProvider> builder) {
if (!Files.exists(file)) {
PathOrigin pathOrigin = new PathOrigin(file);
NoSuchFileException noSuchFileException = new NoSuchFileException(file.toString());
error(new ExceptionDiagnostic(noSuchFileException, pathOrigin));
}
if (isArchive(file)) {
try {
ArchiveClassFileProvider provider = new ArchiveClassFileProvider(file);
builder.add(provider);
} catch (IOException e) {
error(new ExceptionDiagnostic(e, new PathOrigin(file)));
}
} else if (isClassFile(file)) {
try {
builder.add(singleClassFileClassFileResourceProvider(file));
} catch (IOException e) {
error(new ExceptionDiagnostic(e));
}
} else {
error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file)));
}
}
private void addSourceFile(Path file) {
if (!Files.exists(file)) {
PathOrigin pathOrigin = new PathOrigin(file);
NoSuchFileException noSuchFileException = new NoSuchFileException(file.toString());
error(new ExceptionDiagnostic(noSuchFileException, pathOrigin));
}
if (isArchive(file)) {
traceSourceBuilder.add(ArchiveResourceProvider.fromArchive(file, false));
} else if (isClassFile(file)) {
try {
traceSourceBuilder.add(singleClassFileProgramResourceProvider(file));
} catch (IOException e) {
error(new ExceptionDiagnostic(e));
}
} else if (isDexFile(file)) {
traceSourceBuilder.add(
new ProgramResourceProvider() {
ProgramResource dexResource = ProgramResource.fromFile(Kind.DEX, file);
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
return Collections.singletonList(dexResource);
}
});
} else {
error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file)));
}
}
public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
libraryBuilder.add(provider);
return this;
}
public Builder addLibraryFiles(Path... files) {
addLibraryFiles(Arrays.asList(files));
return this;
}
public Builder addLibraryFiles(Collection<Path> files) {
for (Path file : files) {
addLibraryOrTargetFile(file, libraryBuilder);
}
return this;
}
public Builder addTargetFiles(Path... files) {
addTargetFiles(Arrays.asList(files));
return this;
}
public Builder addTargetFiles(Collection<Path> files) {
for (Path file : files) {
addLibraryOrTargetFile(file, traceTargetBuilder);
}
return this;
}
public Builder addSourceFiles(Path... files) {
addSourceFiles(Arrays.asList(files));
return this;
}
public Builder addSourceFiles(Collection<Path> files) {
for (Path file : files) {
addSourceFile(file);
}
return this;
}
public Builder setConsumer(TraceReferencesConsumer consumer) {
this.consumer = consumer;
return this;
}
private TraceReferencesCommand makeCommand() {
if (isPrintHelp() || isPrintVersion()) {
return new TraceReferencesCommand(isPrintHelp(), isPrintVersion());
}
ImmutableList<ClassFileResourceProvider> library = libraryBuilder.build();
ImmutableList<ClassFileResourceProvider> traceTarget = traceTargetBuilder.build();
ImmutableList<ProgramResourceProvider> traceSource = traceSourceBuilder.build();
if (library.isEmpty()) {
error(new StringDiagnostic("No library specified"));
}
if (traceTarget.isEmpty()) {
// Target can be empty for tracing references from source outside of library.
}
if (traceSource.isEmpty()) {
error(new StringDiagnostic("No source specified"));
}
if (consumer == null) {
error(new StringDiagnostic("No consumer specified"));
}
return new TraceReferencesCommand(
printHelp, printVersion, reporter, library, traceTarget, traceSource, consumer);
}
public final TraceReferencesCommand build() throws CompilationFailedException {
Box<TraceReferencesCommand> box = new Box<>(null);
ExceptionUtils.withCompilationHandler(
reporter,
() -> {
box.set(makeCommand());
reporter.failIfPendingErrors();
});
return box.get();
}
void error(Diagnostic diagnostic) {
reporter.error(diagnostic);
// For now all errors are fatal.
}
}
Reporter getReporter() {
return reporter;
}
List<ClassFileResourceProvider> getLibrary() {
return library;
}
List<ClassFileResourceProvider> getTarget() {
return traceTarget;
}
List<ProgramResourceProvider> getSource() {
return traceSource;
}
TraceReferencesConsumer getConsumer() {
return consumer;
}
InternalOptions getInternalOptions() {
InternalOptions options = new InternalOptions();
options.loadAllClassDefinitions = true;
options.lookupLibraryBeforeProgram = true;
TraceReferencesConsumer consumer = getConsumer();
DumpOptions.Builder builder =
DumpOptions.builder(Tool.TraceReferences)
.readCurrentSystemProperties()
// The behavior of TraceReferences greatly differs depending if we have a CheckConsumer
// or a KeepRules consumer. We log the consumer type and obfuscation if relevant.
.setTraceReferencesConsumer(consumer.getClass().getName());
if (consumer instanceof TraceReferencesKeepRules) {
builder.setMinification(((TraceReferencesKeepRules) consumer).allowObfuscation());
}
options.dumpOptions = builder.build();
return options;
}
}