| // 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; |
| } |
| } |
| } |