/* * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.vm.ci.hotspot; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Formatter; import java.util.List; import java.util.Objects; /** * Support for translating exceptions between different runtime heaps. */ @SuppressWarnings("serial") final class TranslatedException extends Exception { private TranslatedException(String message, Throwable translationFailure) { super("[" + translationFailure + "]" + Objects.toString(message, "")); } /** * No need to record an initial stack trace since it will be manually overwritten. */ @SuppressWarnings("sync-override") @Override public Throwable fillInStackTrace() { return this; } /** * Prints a stack trace for {@code throwable} and returns {@code true}. Used to print stack * traces only when assertions are enabled. */ private static boolean printStackTrace(Throwable throwable) { throwable.printStackTrace(); return true; } private static Throwable initCause(Throwable throwable, Throwable cause) { if (cause != null) { try { throwable.initCause(cause); } catch (IllegalStateException e) { // Cause could not be set or overwritten. assert printStackTrace(e); } } return throwable; } private static Throwable create(String className, String message, Throwable cause) { // Try create with reflection first. try { Class cls = Class.forName(className); if (cause != null) { // Handle known exception types whose cause must be set in the constructor if (cls == InvocationTargetException.class) { return new InvocationTargetException(cause, message); } if (cls == ExceptionInInitializerError.class) { return new ExceptionInInitializerError(cause); } } if (message == null) { return initCause((Throwable) cls.getConstructor().newInstance(), cause); } cls.getDeclaredConstructor(String.class); return initCause((Throwable) cls.getConstructor(String.class).newInstance(message), cause); } catch (Throwable translationFailure) { if (className.equals(TranslatedException.class.getName())) { // Chop the class name when boxing another TranslatedException return initCause(new TranslatedException(message, translationFailure), cause); } return initCause(new TranslatedException(null, translationFailure), cause); } } /** * Encodes an exception message to distinguish a null message from an empty message. * * @return {@code value} with a space prepended iff {@code value != null} */ private static String encodeMessage(String value) { return value != null ? ' ' + value : value; } private static String decodeMessage(String value) { if (value.length() == 0) { return null; } return value.substring(1); } private static String encodedString(String value) { return Objects.toString(value, "").replace('|', '_'); } /** * Encodes {@code throwable} including its stack and causes as a string. The encoding format of * a single exception is: * *
     *  '|'  '|'  '|' [ '|'  '|'  '|'  '|' ]*
     * 
* * Each exception is encoded before the exception it causes. */ @VMEntryPoint static String encodeThrowable(Throwable throwable) throws Throwable { try { Formatter enc = new Formatter(); List throwables = new ArrayList<>(); for (Throwable current = throwable; current != null; current = current.getCause()) { throwables.add(current); } // Encode from inner most cause outwards Collections.reverse(throwables); for (Throwable current : throwables) { enc.format("%s|%s|", current.getClass().getName(), encodedString(encodeMessage(current.getMessage()))); StackTraceElement[] stackTrace = current.getStackTrace(); if (stackTrace == null) { stackTrace = new StackTraceElement[0]; } enc.format("%d|", stackTrace.length); for (int i = 0; i < stackTrace.length; i++) { StackTraceElement frame = stackTrace[i]; if (frame != null) { enc.format("%s|%s|%s|%d|", frame.getClassName(), frame.getMethodName(), encodedString(frame.getFileName()), frame.getLineNumber()); } } } return enc.toString(); } catch (Throwable e) { assert printStackTrace(e); try { return e.getClass().getName() + "|" + encodedString(e.getMessage()) + "|0|"; } catch (Throwable e2) { assert printStackTrace(e2); return "java.lang.Throwable|too many errors during encoding|0|"; } } } /** * Gets the stack of the current thread without the frames between this call and the one just * below the frame of the first method in {@link CompilerToVM}. The chopped frames are specific * to the implementation of {@link HotSpotJVMCIRuntime#decodeThrowable(String)}. */ private static StackTraceElement[] getStackTraceSuffix() { StackTraceElement[] stack = new Exception().getStackTrace(); for (int i = 0; i < stack.length; i++) { StackTraceElement e = stack[i]; if (e.getClassName().equals(CompilerToVM.class.getName())) { return Arrays.copyOfRange(stack, i, stack.length); } } // This should never happen but since we're in exception handling // code, just return a safe value instead raising a nested exception. return new StackTraceElement[0]; } /** * Decodes {@code encodedThrowable} into a {@link TranslatedException}. * * @param encodedThrowable an encoded exception in the format specified by * {@link #encodeThrowable} */ @VMEntryPoint static Throwable decodeThrowable(String encodedThrowable) { try { int i = 0; String[] parts = encodedThrowable.split("\\|"); Throwable cause = null; Throwable throwable = null; while (i != parts.length) { String exceptionClassName = parts[i++]; String exceptionMessage = decodeMessage(parts[i++]); throwable = create(exceptionClassName, exceptionMessage, cause); int stackTraceDepth = Integer.parseInt(parts[i++]); StackTraceElement[] suffix = getStackTraceSuffix(); StackTraceElement[] stackTrace = new StackTraceElement[stackTraceDepth + suffix.length]; for (int j = 0; j < stackTraceDepth; j++) { String className = parts[i++]; String methodName = parts[i++]; String fileName = parts[i++]; int lineNumber = Integer.parseInt(parts[i++]); if (fileName.isEmpty()) { fileName = null; } stackTrace[j] = new StackTraceElement(className, methodName, fileName, lineNumber); } System.arraycopy(suffix, 0, stackTrace, stackTraceDepth, suffix.length); throwable.setStackTrace(stackTrace); cause = throwable; } return throwable; } catch (Throwable translationFailure) { assert printStackTrace(translationFailure); return new TranslatedException("Error decoding exception: " + encodedThrowable, translationFailure); } } }