1 /* 2 * Copyright (c) 2010, 2016, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.api.scripting; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 import jdk.nashorn.internal.codegen.CompilerConstants; 31 import jdk.nashorn.internal.runtime.ECMAErrors; 32 import jdk.nashorn.internal.runtime.ScriptObject; 33 34 /** 35 * This is base exception for all Nashorn exceptions. These originate from 36 * user's ECMAScript code. Example: script parse errors, exceptions thrown from 37 * scripts. Note that ScriptEngine methods like "eval", "invokeMethod", 38 * "invokeFunction" will wrap this as ScriptException and throw it. But, there 39 * are cases where user may need to access this exception (or implementation 40 * defined subtype of this). For example, if java interface is implemented by a 41 * script object or Java access to script object properties via java.util.Map 42 * interface. In these cases, user code will get an instance of this or 43 * implementation defined subclass. 44 * 45 * @since 1.8u40 46 */ 47 @SuppressWarnings("serial") 48 public abstract class NashornException extends RuntimeException { 49 private static final long serialVersionUID = 1L; 50 51 // script file name 52 private String fileName; 53 // script line number 54 private int line; 55 // are the line and fileName unknown? 56 private boolean lineAndFileNameUnknown; 57 // script column number 58 private int column; 59 // underlying ECMA error object - lazily initialized 60 private Object ecmaError; 61 62 /** 63 * Constructor to initialize error message, file name, line and column numbers. 64 * 65 * @param msg exception message 66 * @param fileName file name 67 * @param line line number 68 * @param column column number 69 */ 70 protected NashornException(final String msg, final String fileName, final int line, final int column) { 71 this(msg, null, fileName, line, column); 72 } 73 74 /** 75 * Constructor to initialize error message, cause exception, file name, line and column numbers. 76 * 77 * @param msg exception message 78 * @param cause exception cause 79 * @param fileName file name 80 * @param line line number 81 * @param column column number 82 */ 83 protected NashornException(final String msg, final Throwable cause, final String fileName, final int line, final int column) { 84 super(msg, cause == null ? null : cause); 85 this.fileName = fileName; 86 this.line = line; 87 this.column = column; 88 } 89 90 /** 91 * Constructor to initialize error message and cause exception. 92 * 93 * @param msg exception message 94 * @param cause exception cause 95 */ 96 protected NashornException(final String msg, final Throwable cause) { 97 super(msg, cause == null ? null : cause); 98 // Hard luck - no column number info 99 this.column = -1; 100 // We can retrieve the line number and file name from the stack trace if needed 101 this.lineAndFileNameUnknown = true; 102 } 103 104 /** 105 * Get the source file name for this {@code NashornException} 106 * 107 * @return the file name 108 */ 109 public final String getFileName() { 110 ensureLineAndFileName(); 111 return fileName; 112 } 113 114 /** 115 * Set the source file name for this {@code NashornException} 116 * 117 * @param fileName the file name 118 */ 119 public final void setFileName(final String fileName) { 120 this.fileName = fileName; 121 lineAndFileNameUnknown = false; 122 } 123 124 /** 125 * Get the line number for this {@code NashornException} 126 * 127 * @return the line number 128 */ 129 public final int getLineNumber() { 130 ensureLineAndFileName(); 131 return line; 132 } 133 134 /** 135 * Set the line number for this {@code NashornException} 136 * 137 * @param line the line number 138 */ 139 public final void setLineNumber(final int line) { 140 lineAndFileNameUnknown = false; 141 this.line = line; 142 } 143 144 /** 145 * Get the column for this {@code NashornException} 146 * 147 * @return the column number 148 */ 149 public final int getColumnNumber() { 150 return column; 151 } 152 153 /** 154 * Set the column for this {@code NashornException} 155 * 156 * @param column the column number 157 */ 158 public final void setColumnNumber(final int column) { 159 this.column = column; 160 } 161 162 /** 163 * Returns array javascript stack frames from the given exception object. 164 * 165 * @param exception exception from which stack frames are retrieved and filtered 166 * @return array of javascript stack frames 167 */ 168 public static StackTraceElement[] getScriptFrames(final Throwable exception) { 169 final StackTraceElement[] frames = exception.getStackTrace(); 170 final List<StackTraceElement> filtered = new ArrayList<>(); 171 for (final StackTraceElement st : frames) { 172 if (ECMAErrors.isScriptFrame(st)) { 173 final String className = "<" + st.getFileName() + ">"; 174 String methodName = st.getMethodName(); 175 if (methodName.equals(CompilerConstants.PROGRAM.symbolName())) { 176 methodName = "<program>"; 177 } 178 179 if (methodName.contains(CompilerConstants.ANON_FUNCTION_PREFIX.symbolName())) { 180 methodName = "<anonymous>"; 181 } 182 183 filtered.add(new StackTraceElement(className, methodName, 184 st.getFileName(), st.getLineNumber())); 185 } 186 } 187 return filtered.toArray(new StackTraceElement[0]); 188 } 189 190 /** 191 * Return a formatted script stack trace string with frames information separated by '\n' 192 * 193 * @param exception exception for which script stack string is returned 194 * @return formatted stack trace string 195 */ 196 public static String getScriptStackString(final Throwable exception) { 197 final StringBuilder buf = new StringBuilder(); 198 final StackTraceElement[] frames = getScriptFrames(exception); 199 for (final StackTraceElement st : frames) { 200 buf.append("\tat "); 201 buf.append(st.getMethodName()); 202 buf.append(" ("); 203 buf.append(st.getFileName()); 204 buf.append(':'); 205 buf.append(st.getLineNumber()); 206 buf.append(")\n"); 207 } 208 final int len = buf.length(); 209 // remove trailing '\n' 210 if (len > 0) { 211 assert buf.charAt(len - 1) == '\n'; 212 buf.deleteCharAt(len - 1); 213 } 214 return buf.toString(); 215 } 216 217 /** 218 * Get the thrown object. Subclass responsibility 219 * @return thrown object 220 */ 221 protected Object getThrown() { 222 return null; 223 } 224 225 /** 226 * Initialization function for ECMA errors. Stores the error 227 * in the ecmaError field of this class. It is only initialized 228 * once, and then reused 229 * 230 * @param global the global 231 * @return initialized exception 232 */ 233 protected NashornException initEcmaError(final ScriptObject global) { 234 if (ecmaError != null) { 235 return this; // initialized already! 236 } 237 238 final Object thrown = getThrown(); 239 if (thrown instanceof ScriptObject) { 240 setEcmaError(ScriptObjectMirror.wrap(thrown, global)); 241 } else { 242 setEcmaError(thrown); 243 } 244 245 return this; 246 } 247 248 /** 249 * Return the underlying ECMA error object, if available. 250 * 251 * @return underlying ECMA Error object's mirror or whatever was thrown 252 * from script such as a String, Number or a Boolean. 253 */ 254 public Object getEcmaError() { 255 return ecmaError; 256 } 257 258 /** 259 * Return the underlying ECMA error object, if available. 260 * 261 * @param ecmaError underlying ECMA Error object's mirror or whatever was thrown 262 * from script such as a String, Number or a Boolean. 263 */ 264 public void setEcmaError(final Object ecmaError) { 265 this.ecmaError = ecmaError; 266 } 267 268 private void ensureLineAndFileName() { 269 if (lineAndFileNameUnknown) { 270 for (final StackTraceElement ste : getStackTrace()) { 271 if (ECMAErrors.isScriptFrame(ste)) { 272 // Whatever here is compiled from JavaScript code 273 fileName = ste.getFileName(); 274 line = ste.getLineNumber(); 275 return; 276 } 277 } 278 279 lineAndFileNameUnknown = false; 280 } 281 } 282 } --- EOF ---