1 /* 2 * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.vm.ci.hotspot; 24 25 import java.util.Arrays; 26 import java.util.Formatter; 27 import java.util.Objects; 28 29 /** 30 * Support for translating exceptions between different runtime heaps. 31 */ 32 @SuppressWarnings("serial") 33 final class TranslatedException extends Exception { 34 35 private TranslatedException(String message) { 36 super(message); 37 } 38 39 private TranslatedException(String message, Throwable cause) { 40 super(message, cause); 41 } 42 43 /** 44 * No need to record an initial stack trace since it will be manually overwritten. 45 */ 46 @SuppressWarnings("sync-override") 47 @Override 48 public Throwable fillInStackTrace() { 49 return this; 50 } 51 52 private static Throwable create(String className, String message) { 53 // Try create with reflection first. 54 try { 55 Class<?> cls = Class.forName(className); 56 if (message == null) { 57 return (Throwable) cls.getConstructor().newInstance(); 58 } 59 cls.getDeclaredConstructor(String.class); 60 return (Throwable) cls.getConstructor(String.class).newInstance(message); 61 } catch (Throwable ignore) { 62 } 63 64 if (className.equals(TranslatedException.class.getName())) { 65 // Chop the class name when boxing another TranslatedException 66 return new TranslatedException(message); 67 } 68 69 if (message == null) { 70 return new TranslatedException(className); 71 } 72 return new TranslatedException(className + ": " + message); 73 } 74 75 private static String encodedString(String value) { 76 return Objects.toString(value, "").replace('|', '_'); 77 } 78 79 /** 80 * Encodes {@code throwable} including its stack and causes as a string. The encoding format of 81 * a single exception with its cause is: 82 * 83 * <pre> 84 * <exception class name> '|' <exception message> '|' <stack size> '|' [<class> '|' <method> '|' <file> '|' <line> '|' ]* 85 * </pre> 86 * 87 * Each cause is appended after the exception is it the cause of. 88 */ 89 @VMEntryPoint 90 static String encodeThrowable(Throwable throwable) throws Throwable { 91 try { 92 Formatter enc = new Formatter(); 93 Throwable current = throwable; 94 do { 95 enc.format("%s|%s|", current.getClass().getName(), encodedString(current.getMessage())); 96 StackTraceElement[] stackTrace = current.getStackTrace(); 97 if (stackTrace == null) { 98 stackTrace = new StackTraceElement[0]; 99 } 100 enc.format("%d|", stackTrace.length); 101 for (int i = 0; i < stackTrace.length; i++) { 102 StackTraceElement frame = stackTrace[i]; 103 if (frame != null) { 104 enc.format("%s|%s|%s|%d|", frame.getClassName(), frame.getMethodName(), 105 encodedString(frame.getFileName()), frame.getLineNumber()); 106 } 107 } 108 current = current.getCause(); 109 } while (current != null); 110 return enc.toString(); 111 } catch (Throwable e) { 112 try { 113 return e.getClass().getName() + "|" + encodedString(e.getMessage()) + "|0|"; 114 } catch (Throwable e2) { 115 return "java.lang.Throwable|too many errors during encoding|0|"; 116 } 117 } 118 } 119 120 /** 121 * Gets the stack of the current thread without the frames between this call and the one just 122 * below the frame of the first method in {@link CompilerToVM}. The chopped frames are specific 123 * to the implementation of {@link HotSpotJVMCIRuntime#decodeThrowable(String)}. 124 */ 125 private static StackTraceElement[] getStackTraceSuffix() { 126 StackTraceElement[] stack = new Exception().getStackTrace(); 127 for (int i = 0; i < stack.length; i++) { 128 StackTraceElement e = stack[i]; 129 if (e.getClassName().equals(CompilerToVM.class.getName())) { 130 return Arrays.copyOfRange(stack, i, stack.length); 131 } 132 } 133 // This should never happen but since we're in exception handling 134 // code, just return a safe value instead raising a nested exception. 135 return new StackTraceElement[0]; 136 } 137 138 /** 139 * Decodes {@code encodedThrowable} into a {@link TranslatedException}. 140 * 141 * @param encodedThrowable an encoded exception in the format specified by 142 * {@link #encodeThrowable} 143 */ 144 @VMEntryPoint 145 static Throwable decodeThrowable(String encodedThrowable) { 146 try { 147 int i = 0; 148 String[] parts = encodedThrowable.split("\\|"); 149 Throwable parent = null; 150 Throwable result = null; 151 while (i != parts.length) { 152 String exceptionClassName = parts[i++]; 153 String exceptionMessage = parts[i++]; 154 Throwable throwable = create(exceptionClassName, exceptionMessage); 155 int stackTraceDepth = Integer.parseInt(parts[i++]); 156 157 StackTraceElement[] suffix = getStackTraceSuffix(); 158 StackTraceElement[] stackTrace = new StackTraceElement[stackTraceDepth + suffix.length]; 159 for (int j = 0; j < stackTraceDepth; j++) { 160 String className = parts[i++]; 161 String methodName = parts[i++]; 162 String fileName = parts[i++]; 163 int lineNumber = Integer.parseInt(parts[i++]); 164 if (fileName.isEmpty()) { 165 fileName = null; 166 } 167 stackTrace[j] = new StackTraceElement(className, methodName, fileName, lineNumber); 168 } 169 System.arraycopy(suffix, 0, stackTrace, stackTraceDepth, suffix.length); 170 throwable.setStackTrace(stackTrace); 171 if (parent != null) { 172 parent.initCause(throwable); 173 } else { 174 result = throwable; 175 } 176 parent = throwable; 177 } 178 return result; 179 } catch (Throwable t) { 180 return new TranslatedException("Error decoding exception: " + encodedThrowable, t); 181 } 182 } 183 }