blob: ef5c3cac18e9e8778b9d0b64db941f5dbf3023ff [file] [log] [blame]
// Copyright (c) 2017, 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.CompilationFailedException;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.Version;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.position.Position;
import com.google.common.collect.ObjectArrays;
import java.io.IOException;
import java.nio.file.FileSystemException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class ExceptionUtils {
public static String getMainStackTrace() {
return Thread.getAllStackTraces().entrySet().stream()
.filter(x -> x.getKey().getName().equals("main"))
.map(x -> x.getValue())
.flatMap(x -> Stream.of(x))
.map(x -> x.toString())
.collect(Collectors.joining(System.lineSeparator()));
}
public static void withConsumeResourceHandler(
Reporter reporter, StringConsumer consumer, String data) {
withConsumeResourceHandler(reporter, handler -> consumer.accept(data, handler));
}
public static void withFinishedResourceHandler(Reporter reporter, StringConsumer consumer) {
withConsumeResourceHandler(reporter, consumer::finished);
}
public static void withConsumeResourceHandler(
Reporter reporter, Consumer<DiagnosticsHandler> consumer) {
// Unchecked exceptions simply propagate out, aborting the compilation forcefully.
consumer.accept(reporter);
// Fail fast for now. We might consider delaying failure since consumer failure does not affect
// the compilation. We might need to be careful to correctly identify errors so as to exit
// compilation with an error code.
reporter.failIfPendingErrors();
}
public interface CompileAction {
void run() throws IOException, CompilationError, ResourceException;
}
public static void withD8CompilationHandler(Reporter reporter, CompileAction action)
throws CompilationFailedException {
withCompilationHandler(reporter, action);
}
public static void withR8CompilationHandler(Reporter reporter, CompileAction action)
throws CompilationFailedException {
withCompilationHandler(reporter, action);
}
public static void withMainDexListHandler(
Reporter reporter, CompileAction action) throws CompilationFailedException {
withCompilationHandler(reporter, action);
}
public static void withCompilationHandler(Reporter reporter, CompileAction action)
throws CompilationFailedException {
try {
action.run();
reporter.failIfPendingErrors();
} catch (Throwable e) {
throw failCompilation(reporter, e);
}
}
private static CompilationFailedException failCompilation(
Reporter reporter, Throwable topMostException) {
return failWithFakeEntry(
reporter, topMostException, CompilationFailedException::new, AbortException.class);
}
public static <T extends Exception, A extends Exception> T failWithFakeEntry(
DiagnosticsHandler diagnosticsHandler,
Throwable topMostException,
BiFunction<String, Throwable, T> newException,
Class<A> abortException) {
// Find inner-most cause of the failure and compute origin, position and reported for the path.
boolean hasBeenReported = false;
Origin origin = Origin.unknown();
Position position = Position.UNKNOWN;
List<Throwable> suppressed = new ArrayList<>();
Throwable innerMostCause = topMostException;
while (true) {
hasBeenReported |= abortException.isAssignableFrom(innerMostCause.getClass());
Origin nextOrigin = getOrigin(innerMostCause);
if (nextOrigin != Origin.unknown()) {
origin = nextOrigin;
}
Position nextPosition = getPosition(innerMostCause);
if (nextPosition != Position.UNKNOWN) {
position = nextPosition;
}
if (innerMostCause.getCause() == null || suppressed.contains(innerMostCause)) {
break;
}
suppressed.add(innerMostCause);
innerMostCause = innerMostCause.getCause();
}
// Add the full stack as a suppressed stack on the inner cause.
if (topMostException != innerMostCause) {
innerMostCause.addSuppressed(topMostException);
}
// If no abort is seen, the exception is not reported, so report it now.
if (!hasBeenReported) {
diagnosticsHandler.error(new ExceptionDiagnostic(innerMostCause, origin, position));
}
// Build the top-level compiler exception and version stack.
StringBuilder message = new StringBuilder("Compilation failed to complete");
if (position != Position.UNKNOWN) {
message.append(", position: ").append(position);
}
if (origin != Origin.unknown()) {
message.append(", origin: ").append(origin);
}
// Create the final exception object.
T rethrow = newException.apply(message.toString(), innerMostCause);
// Replace its stack by the cause stack and insert version info at the top.
String filename = "Version_" + Version.LABEL + ".java";
StackTraceElement versionElement =
new StackTraceElement(Version.class.getSimpleName(), "fakeStackEntry", filename, 0);
rethrow.setStackTrace(ObjectArrays.concat(versionElement, rethrow.getStackTrace()));
return rethrow;
}
private static Origin getOrigin(Throwable e) {
if (e instanceof IOException) {
return extractIOExceptionOrigin((IOException) e);
}
if (e instanceof CompilationError) {
return ((CompilationError) e).getOrigin();
}
if (e instanceof ResourceException) {
return ((ResourceException) e).getOrigin();
}
if (e instanceof OriginAttachmentException) {
return ((OriginAttachmentException) e).origin;
}
if (e instanceof AbortException) {
return ((AbortException) e).getOrigin();
}
return Origin.unknown();
}
private static Position getPosition(Throwable e) {
if (e instanceof CompilationError) {
return ((CompilationError) e).getPosition();
}
if (e instanceof OriginAttachmentException) {
return ((OriginAttachmentException) e).position;
}
if (e instanceof AbortException) {
return ((AbortException) e).getPosition();
}
return Position.UNKNOWN;
}
public interface MainAction {
void run() throws CompilationFailedException;
}
public static void withMainProgramHandler(MainAction action) {
try {
action.run();
} catch (CompilationFailedException e) {
printExitMessage(e.getCause());
throw new RuntimeException(e);
} catch (RuntimeException e) {
printExitMessage(e);
throw e;
}
}
private static void printExitMessage(Throwable cause) {
System.err.println(
isExpectedException(cause)
? "Compilation failed"
: "Compilation failed with an internal error.");
}
private static boolean isExpectedException(Throwable e) {
return e instanceof CompilationError || e instanceof AbortException;
}
// We should try to avoid the use of this extraction as it signifies a point where we don't have
// enough context to associate a specific origin with an IOException. Concretely, we should move
// towards always catching IOException and rethrowing CompilationError with proper origins.
private static Origin extractIOExceptionOrigin(IOException e) {
if (e instanceof FileSystemException) {
FileSystemException fse = (FileSystemException) e;
if (fse.getFile() != null && !fse.getFile().isEmpty()) {
return new PathOrigin(Paths.get(fse.getFile()));
}
}
return Origin.unknown();
}
public static RuntimeException unwrapExecutionException(ExecutionException executionException) {
return new RuntimeException(executionException);
}
public static void withOriginAttachmentHandler(Origin origin, Runnable action) {
withOriginAndPositionAttachmentHandler(origin, Position.UNKNOWN, action);
}
public static <T> T withOriginAttachmentHandler(Origin origin, Supplier<T> action) {
return withOriginAndPositionAttachmentHandler(origin, Position.UNKNOWN, action);
}
public static void withOriginAndPositionAttachmentHandler(
Origin origin, Position position, Runnable action) {
withOriginAndPositionAttachmentHandler(
origin,
position,
() -> {
action.run();
return null;
});
}
public static <T> T withOriginAndPositionAttachmentHandler(
Origin origin, Position position, Supplier<T> action) {
try {
return action.get();
} catch (RuntimeException e) {
throw OriginAttachmentException.wrap(e, origin, position);
}
}
private static class OriginAttachmentException extends RuntimeException {
final Origin origin;
final Position position;
public static RuntimeException wrap(RuntimeException e, Origin origin, Position position) {
return needsAttachment(e, origin, position)
? new OriginAttachmentException(e, origin, position)
: e;
}
private OriginAttachmentException(RuntimeException e, Origin origin, Position position) {
super(e);
this.origin = origin;
this.position = position;
}
private static boolean needsAttachment(RuntimeException e, Origin origin, Position position) {
if (origin == Origin.unknown() && position == Position.UNKNOWN) {
return false;
}
Origin existingOrigin = getOrigin(e);
Position existingPosition = getPosition(e);
return origin != existingOrigin || position != existingPosition;
}
}
}