/
githubmirror
/
jdk22
Обзор
Документация
Войти
/
githubmirror
/
jdk22
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/jdk/internal/vm/TranslatedException.java
318 строк
13 KB
Doug Simon
8313899: JVMCI exception Translation can fail in TranslatedException.<clinit>
10 авг 2023, 21:53
10 авг 2023, 21:53
6f5c903
Код
Авторство
О чём код?
/* * Copyright (c) 2018, 2022, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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.internal.vm; import jdk.internal.misc.VM; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * Support for translating exceptions between the HotSpot heap and libjvmci heap. */ @SuppressWarnings("serial") final class TranslatedException extends Exception { /** * The value returned by {@link #encodeThrowable(Throwable)} when encoding * fails due to an {@link OutOfMemoryError}. */ private static final byte[] FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES; /** * The value returned by {@link #encodeThrowable(Throwable)} when encoding * fails for any reason other than {@link OutOfMemoryError}. */ private static final byte[] FALLBACK_ENCODED_THROWABLE_BYTES; static { maybeFailClinit(); try { FALLBACK_ENCODED_THROWABLE_BYTES = encodeThrowable(new TranslatedException("error during encoding", "<unknown>"), false); FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES = encodeThrowable(new OutOfMemoryError(), false); } catch (IOException e) { throw new InternalError(e); } } /** * Helper to test exception translation. */ private static void maybeFailClinit() { String className = VM.getSavedProperty("test.jvmci.TranslatedException.clinit.throw"); if (className != null) { try { throw (Throwable) Class.forName(className).getDeclaredConstructor().newInstance(); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new InternalError(e); } } } /** * Class name of exception that could not be instantiated. */ private String originalExceptionClassName; private TranslatedException(String message, String originalExceptionClassName) { super(message); this.originalExceptionClassName = originalExceptionClassName; } /** * No need to record an initial stack trace since * it will be manually overwritten. */ @SuppressWarnings("sync-override") @Override public Throwable fillInStackTrace() { return this; } @Override public String toString() { String s; if (originalExceptionClassName.equals(TranslatedException.class.getName())) { s = getClass().getName(); } else { s = getClass().getName() + "[" + originalExceptionClassName + "]"; } String message = getMessage(); return (message != null) ? (s + ": " + message) : s; } /** * Prints a stack trace for {@code throwable} if the system property * {@code "jdk.internal.vm.TranslatedException.debug"} is true. */ private static void debugPrintStackTrace(Throwable throwable) { if (Boolean.getBoolean("jdk.internal.vm.TranslatedException.debug")) { System.err.print("DEBUG: "); throwable.printStackTrace(); } } 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. debugPrintStackTrace(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) { Constructor<?> cons = cls.getConstructor(); return initCause((Throwable) cons.newInstance(), cause); } Constructor<?> cons = cls.getDeclaredConstructor(String.class); return initCause((Throwable) cons.newInstance(message), cause); } catch (Throwable translationFailure) { debugPrintStackTrace(translationFailure); return initCause(new TranslatedException(message, className), cause); } } private static String emptyIfNull(String value) { return value == null ? "" : value; } private static String emptyAsNull(String value) { return value.isEmpty() ? null : value; } /** * Encodes {@code throwable} including its stack and causes as a {@linkplain GZIPOutputStream * compressed} byte array that can be decoded by {@link #decodeThrowable}. */ static byte[] encodeThrowable(Throwable throwable) { try { return encodeThrowable(throwable, true); } catch (OutOfMemoryError e) { return FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES; } catch (Throwable e) { return FALLBACK_ENCODED_THROWABLE_BYTES; } } private static byte[] encodeThrowable(Throwable throwable, boolean withCauseAndStack) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(baos))) { List<Throwable> throwables = new ArrayList<>(); for (Throwable current = throwable; current != null; current = current.getCause()) { throwables.add(current); if (!withCauseAndStack) { break; } } // Encode from inner most cause outwards Collections.reverse(throwables); for (Throwable current : throwables) { dos.writeUTF(current.getClass().getName()); dos.writeUTF(emptyIfNull(current.getMessage())); StackTraceElement[] stackTrace = withCauseAndStack ? current.getStackTrace() : null; if (stackTrace == null) { stackTrace = new StackTraceElement[0]; } dos.writeInt(stackTrace.length); for (int i = 0; i < stackTrace.length; i++) { StackTraceElement frame = stackTrace[i]; if (frame != null) { dos.writeUTF(emptyIfNull(frame.getClassLoaderName())); dos.writeUTF(emptyIfNull(frame.getModuleName())); dos.writeUTF(emptyIfNull(frame.getModuleVersion())); dos.writeUTF(emptyIfNull(frame.getClassName())); dos.writeUTF(emptyIfNull(frame.getMethodName())); dos.writeUTF(emptyIfNull(frame.getFileName())); dos.writeInt(frame.getLineNumber()); } } } } return baos.toByteArray(); } /** * Gets the stack of the current thread as of the first native method. The chopped * frames are for the VM call to {@link VMSupport#decodeAndThrowThrowable}. */ private static StackTraceElement[] getMyStackTrace() { Exception ex = new Exception(); StackTraceElement[] stack = ex.getStackTrace(); for (int i = 0; i < stack.length; i++) { StackTraceElement e = stack[i]; if (e.isNativeMethod()) { return Arrays.copyOfRange(stack, i, stack.length); } } // This should never happen but since this is exception handling // code, be defensive 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} */ static Throwable decodeThrowable(byte[] encodedThrowable) { ByteArrayInputStream bais = new ByteArrayInputStream(encodedThrowable); try (DataInputStream dis = new DataInputStream(new GZIPInputStream(bais))) { Throwable cause = null; Throwable throwable = null; StackTraceElement[] myStack = getMyStackTrace(); while (dis.available() != 0) { String exceptionClassName = dis.readUTF(); String exceptionMessage = emptyAsNull(dis.readUTF()); throwable = create(exceptionClassName, exceptionMessage, cause); int stackTraceDepth = dis.readInt(); StackTraceElement[] stackTrace = new StackTraceElement[stackTraceDepth + myStack.length]; int stackTraceIndex = 0; int myStackIndex = 0; for (int j = 0; j < stackTraceDepth; j++) { String classLoaderName = emptyAsNull(dis.readUTF()); String moduleName = emptyAsNull(dis.readUTF()); String moduleVersion = emptyAsNull(dis.readUTF()); String className = emptyAsNull(dis.readUTF()); String methodName = emptyAsNull(dis.readUTF()); String fileName = emptyAsNull(dis.readUTF()); int lineNumber = dis.readInt(); StackTraceElement ste = new StackTraceElement(classLoaderName, moduleName, moduleVersion, className, methodName, fileName, lineNumber); if (ste.isNativeMethod()) { // Best effort attempt to weave stack traces from two heaps into // a single stack trace using native method frames as stitching points. // This is not 100% reliable as there's no guarantee that native method // frames only exist for calls between HotSpot and libjvmci. while (myStackIndex < myStack.length) { StackTraceElement suffixSTE = myStack[myStackIndex++]; if (suffixSTE.isNativeMethod()) { break; } stackTrace[stackTraceIndex++] = suffixSTE; } } stackTrace[stackTraceIndex++] = ste; } while (myStackIndex < myStack.length) { stackTrace[stackTraceIndex++] = myStack[myStackIndex++]; } if (stackTraceIndex != stackTrace.length) { // Remove null entries at end of stackTrace stackTrace = Arrays.copyOf(stackTrace, stackTraceIndex); } throwable.setStackTrace(stackTrace); cause = throwable; } return throwable; } catch (Throwable translationFailure) { debugPrintStackTrace(translationFailure); return new TranslatedException("Error decoding exception: " + encodedThrowable, translationFailure.getClass().getName()); } } }