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 } | 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.lang.reflect.InvocationTargetException; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.Formatter; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * Support for translating exceptions between different runtime heaps. 35 */ 36 @SuppressWarnings("serial") 37 final class TranslatedException extends Exception { 38 39 private TranslatedException(String message, Throwable translationFailure) { 40 super("[" + translationFailure + "]" + Objects.toString(message, "")); 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 /** 53 * Prints a stack trace for {@code throwable} and returns {@code true}. Used to print stack 54 * traces only when assertions are enabled. 55 */ 56 private static boolean printStackTrace(Throwable throwable) { 57 throwable.printStackTrace(); 58 return true; 59 } 60 61 private static Throwable initCause(Throwable throwable, Throwable cause) { 62 if (cause != null) { 63 try { 64 throwable.initCause(cause); 65 } catch (IllegalStateException e) { 66 // Cause could not be set or overwritten. 67 assert printStackTrace(e); 68 } 69 } 70 return throwable; 71 } 72 73 private static Throwable create(String className, String message, Throwable cause) { 74 // Try create with reflection first. 75 try { 76 Class<?> cls = Class.forName(className); 77 if (cause != null) { 78 // Handle known exception types whose cause must be set in the constructor 79 if (cls == InvocationTargetException.class) { 80 return new InvocationTargetException(cause, message); 81 } 82 if (cls == ExceptionInInitializerError.class) { 83 return new ExceptionInInitializerError(cause); 84 } 85 } 86 if (message == null) { 87 return initCause((Throwable) cls.getConstructor().newInstance(), cause); 88 } 89 cls.getDeclaredConstructor(String.class); 90 return initCause((Throwable) cls.getConstructor(String.class).newInstance(message), cause); 91 } catch (Throwable translationFailure) { 92 if (className.equals(TranslatedException.class.getName())) { 93 // Chop the class name when boxing another TranslatedException 94 return initCause(new TranslatedException(message, translationFailure), cause); 95 } 96 return initCause(new TranslatedException(null, translationFailure), cause); 97 } 98 } 99 100 /** 101 * Encodes an exception message to distinguish a null message from an empty message. 102 * 103 * @return {@code value} with a space prepended iff {@code value != null} 104 */ 105 private static String encodeMessage(String value) { 106 return value != null ? ' ' + value : value; 107 } 108 109 private static String decodeMessage(String value) { 110 if (value.length() == 0) { 111 return null; 112 } 113 return value.substring(1); 114 } 115 116 private static String encodedString(String value) { 117 return Objects.toString(value, "").replace('|', '_'); 118 } 119 120 /** 121 * Encodes {@code throwable} including its stack and causes as a string. The encoding format of 122 * a single exception is: 123 * 124 * <pre> 125 * <exception class name> '|' <exception message> '|' <stack size> '|' [<class> '|' <method> '|' <file> '|' <line> '|' ]* 126 * </pre> 127 * 128 * Each exception is encoded before the exception it causes. 129 */ 130 @VMEntryPoint 131 static String encodeThrowable(Throwable throwable) throws Throwable { 132 try { 133 Formatter enc = new Formatter(); 134 List<Throwable> throwables = new ArrayList<>(); 135 for (Throwable current = throwable; current != null; current = current.getCause()) { 136 throwables.add(current); 137 } 138 139 // Encode from inner most cause outwards 140 Collections.reverse(throwables); 141 142 for (Throwable current : throwables) { 143 enc.format("%s|%s|", current.getClass().getName(), encodedString(encodeMessage(current.getMessage()))); 144 StackTraceElement[] stackTrace = current.getStackTrace(); 145 if (stackTrace == null) { 146 stackTrace = new StackTraceElement[0]; 147 } 148 enc.format("%d|", stackTrace.length); 149 for (int i = 0; i < stackTrace.length; i++) { 150 StackTraceElement frame = stackTrace[i]; 151 if (frame != null) { 152 enc.format("%s|%s|%s|%d|", frame.getClassName(), frame.getMethodName(), 153 encodedString(frame.getFileName()), frame.getLineNumber()); 154 } 155 } 156 } 157 return enc.toString(); 158 } catch (Throwable e) { 159 assert printStackTrace(e); 160 try { 161 return e.getClass().getName() + "|" + encodedString(e.getMessage()) + "|0|"; 162 } catch (Throwable e2) { 163 assert printStackTrace(e2); 164 return "java.lang.Throwable|too many errors during encoding|0|"; 165 } 166 } 167 } 168 169 /** 170 * Gets the stack of the current thread without the frames between this call and the one just 171 * below the frame of the first method in {@link CompilerToVM}. The chopped frames are specific 172 * to the implementation of {@link HotSpotJVMCIRuntime#decodeThrowable(String)}. 173 */ 174 private static StackTraceElement[] getStackTraceSuffix() { 175 StackTraceElement[] stack = new Exception().getStackTrace(); 176 for (int i = 0; i < stack.length; i++) { 177 StackTraceElement e = stack[i]; 178 if (e.getClassName().equals(CompilerToVM.class.getName())) { 179 return Arrays.copyOfRange(stack, i, stack.length); 180 } 181 } 182 // This should never happen but since we're in exception handling 183 // code, just return a safe value instead raising a nested exception. 184 return new StackTraceElement[0]; 185 } 186 187 /** 188 * Decodes {@code encodedThrowable} into a {@link TranslatedException}. 189 * 190 * @param encodedThrowable an encoded exception in the format specified by 191 * {@link #encodeThrowable} 192 */ 193 @VMEntryPoint 194 static Throwable decodeThrowable(String encodedThrowable) { 195 try { 196 int i = 0; 197 String[] parts = encodedThrowable.split("\\|"); 198 Throwable cause = null; 199 Throwable throwable = null; 200 while (i != parts.length) { 201 String exceptionClassName = parts[i++]; 202 String exceptionMessage = decodeMessage(parts[i++]); 203 throwable = create(exceptionClassName, exceptionMessage, cause); 204 int stackTraceDepth = Integer.parseInt(parts[i++]); 205 206 StackTraceElement[] suffix = getStackTraceSuffix(); 207 StackTraceElement[] stackTrace = new StackTraceElement[stackTraceDepth + suffix.length]; 208 for (int j = 0; j < stackTraceDepth; j++) { 209 String className = parts[i++]; 210 String methodName = parts[i++]; 211 String fileName = parts[i++]; 212 int lineNumber = Integer.parseInt(parts[i++]); 213 if (fileName.isEmpty()) { 214 fileName = null; 215 } 216 stackTrace[j] = new StackTraceElement(className, methodName, fileName, lineNumber); 217 } 218 System.arraycopy(suffix, 0, stackTrace, stackTraceDepth, suffix.length); 219 throwable.setStackTrace(stackTrace); 220 cause = throwable; 221 } 222 return throwable; 223 } catch (Throwable translationFailure) { 224 assert printStackTrace(translationFailure); 225 return new TranslatedException("Error decoding exception: " + encodedThrowable, translationFailure); 226 } 227 } 228 } |