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 } else { 178 methodName = stripMethodName(methodName); 179 } 180 181 filtered.add(new StackTraceElement(className, methodName, 182 st.getFileName(), st.getLineNumber())); 183 } 184 } 185 return filtered.toArray(new StackTraceElement[0]); 186 } 187 188 private static String stripMethodName(final String methodName) { 189 String name = methodName; 190 191 final int nestedSeparator = name.lastIndexOf(CompilerConstants.NESTED_FUNCTION_SEPARATOR.symbolName()); 192 if (nestedSeparator >= 0) { 193 name = name.substring(nestedSeparator + 1); 194 } 195 196 final int idSeparator = name.indexOf(CompilerConstants.ID_FUNCTION_SEPARATOR.symbolName()); 197 if (idSeparator >= 0) { 198 name = name.substring(0, idSeparator); 199 } 200 201 return name.contains(CompilerConstants.ANON_FUNCTION_PREFIX.symbolName()) ? "<anonymous>" : name; 202 } 203 204 /** 205 * Return a formatted script stack trace string with frames information separated by '\n' 206 * 207 * @param exception exception for which script stack string is returned 208 * @return formatted stack trace string 209 */ 210 public static String getScriptStackString(final Throwable exception) { 211 final StringBuilder buf = new StringBuilder(); 212 final StackTraceElement[] frames = getScriptFrames(exception); 213 for (final StackTraceElement st : frames) { 214 buf.append("\tat "); 215 buf.append(st.getMethodName()); 216 buf.append(" ("); 217 buf.append(st.getFileName()); 218 buf.append(':'); 219 buf.append(st.getLineNumber()); 220 buf.append(")\n"); 221 } 222 final int len = buf.length(); 223 // remove trailing '\n' 224 if (len > 0) { 225 assert buf.charAt(len - 1) == '\n'; 226 buf.deleteCharAt(len - 1); 227 } 228 return buf.toString(); 229 } 230 231 /** 232 * Get the thrown object. Subclass responsibility 233 * @return thrown object 234 */ 235 protected Object getThrown() { 236 return null; 237 } 238 239 /** 240 * Initialization function for ECMA errors. Stores the error 241 * in the ecmaError field of this class. It is only initialized 242 * once, and then reused 243 * 244 * @param global the global 245 * @return initialized exception 246 */ 247 NashornException initEcmaError(final ScriptObject global) { 248 if (ecmaError != null) { 249 return this; // initialized already! 250 } 251 252 final Object thrown = getThrown(); 253 if (thrown instanceof ScriptObject) { 254 setEcmaError(ScriptObjectMirror.wrap(thrown, global)); 255 } else { 256 setEcmaError(thrown); 257 } 258 259 return this; 260 } 261 262 /** 263 * Return the underlying ECMA error object, if available. 264 * 265 * @return underlying ECMA Error object's mirror or whatever was thrown 266 * from script such as a String, Number or a Boolean. 267 */ 268 public Object getEcmaError() { 269 return ecmaError; 270 } 271 272 /** 273 * Return the underlying ECMA error object, if available. 274 * 275 * @param ecmaError underlying ECMA Error object's mirror or whatever was thrown 276 * from script such as a String, Number or a Boolean. 277 */ 278 public void setEcmaError(final Object ecmaError) { 279 this.ecmaError = ecmaError; 280 } 281 282 private void ensureLineAndFileName() { 283 if (lineAndFileNameUnknown) { 284 for (final StackTraceElement ste : getStackTrace()) { 285 if (ECMAErrors.isScriptFrame(ste)) { 286 // Whatever here is compiled from JavaScript code 287 fileName = ste.getFileName(); 288 line = ste.getLineNumber(); 289 return; 290 } 291 } 292 293 lineAndFileNameUnknown = false; 294 } 295 } 296 }