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 }