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.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 }