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 * @deprecated Nashorn JavaScript script engine and APIs, and the jjs tool 46 * are deprecated with the intent to remove them in a future release. 47 * 48 * @since 1.8u40 49 */ 50 @Deprecated(since="11", forRemoval=true) 51 @SuppressWarnings("serial") 52 public abstract class NashornException extends RuntimeException { 53 private static final long serialVersionUID = 1L; 54 55 // script file name 56 private String fileName; 57 // script line number 58 private int line; 59 // are the line and fileName unknown? 60 private boolean lineAndFileNameUnknown; 61 // script column number 62 private int column; 63 // underlying ECMA error object - lazily initialized 64 private Object ecmaError; 65 66 /** 67 * Constructor to initialize error message, file name, line and column numbers. 68 * 69 * @param msg exception message 70 * @param fileName file name 71 * @param line line number 72 * @param column column number 73 */ 74 protected NashornException(final String msg, final String fileName, final int line, final int column) { 75 this(msg, null, fileName, line, column); 76 } 77 78 /** 79 * Constructor to initialize error message, cause exception, file name, line and column numbers. 80 * 81 * @param msg exception message 82 * @param cause exception cause 83 * @param fileName file name 84 * @param line line number 85 * @param column column number 86 */ 87 protected NashornException(final String msg, final Throwable cause, final String fileName, final int line, final int column) { 88 super(msg, cause == null ? null : cause); 89 this.fileName = fileName; 90 this.line = line; 91 this.column = column; 92 } 93 94 /** 95 * Constructor to initialize error message and cause exception. 96 * 97 * @param msg exception message 98 * @param cause exception cause 99 */ 100 protected NashornException(final String msg, final Throwable cause) { 101 super(msg, cause == null ? null : cause); 102 // Hard luck - no column number info 103 this.column = -1; 104 // We can retrieve the line number and file name from the stack trace if needed 105 this.lineAndFileNameUnknown = true; 106 } 107 108 /** 109 * Get the source file name for this {@code NashornException} 110 * 111 * @return the file name 112 */ 113 public final String getFileName() { 114 ensureLineAndFileName(); 115 return fileName; 116 } 117 118 /** 119 * Set the source file name for this {@code NashornException} 120 * 121 * @param fileName the file name 122 */ 123 public final void setFileName(final String fileName) { 124 this.fileName = fileName; 125 lineAndFileNameUnknown = false; 126 } 127 128 /** 129 * Get the line number for this {@code NashornException} 130 * 131 * @return the line number 132 */ 133 public final int getLineNumber() { 134 ensureLineAndFileName(); 135 return line; 136 } 137 138 /** 139 * Set the line number for this {@code NashornException} 140 * 141 * @param line the line number 142 */ 143 public final void setLineNumber(final int line) { 144 lineAndFileNameUnknown = false; 145 this.line = line; 146 } 147 148 /** 149 * Get the column for this {@code NashornException} 150 * 151 * @return the column number 152 */ 153 public final int getColumnNumber() { 154 return column; 155 } 156 157 /** 158 * Set the column for this {@code NashornException} 159 * 160 * @param column the column number 161 */ 162 public final void setColumnNumber(final int column) { 163 this.column = column; 164 } 165 166 /** 167 * Returns array javascript stack frames from the given exception object. 168 * 169 * @param exception exception from which stack frames are retrieved and filtered 170 * @return array of javascript stack frames 171 */ 172 public static StackTraceElement[] getScriptFrames(final Throwable exception) { 173 final StackTraceElement[] frames = exception.getStackTrace(); 174 final List<StackTraceElement> filtered = new ArrayList<>(); 175 for (final StackTraceElement st : frames) { 176 if (ECMAErrors.isScriptFrame(st)) { 177 final String className = "<" + st.getFileName() + ">"; 178 String methodName = st.getMethodName(); 179 if (methodName.equals(CompilerConstants.PROGRAM.symbolName())) { 180 methodName = "<program>"; 181 } else { 182 methodName = stripMethodName(methodName); 183 } 184 185 filtered.add(new StackTraceElement(className, methodName, 186 st.getFileName(), st.getLineNumber())); 187 } 188 } 189 return filtered.toArray(new StackTraceElement[0]); 190 } 191 192 private static String stripMethodName(final String methodName) { 193 String name = methodName; 194 195 final int nestedSeparator = name.lastIndexOf(CompilerConstants.NESTED_FUNCTION_SEPARATOR.symbolName()); 196 if (nestedSeparator >= 0) { 197 name = name.substring(nestedSeparator + 1); 198 } 199 200 final int idSeparator = name.indexOf(CompilerConstants.ID_FUNCTION_SEPARATOR.symbolName()); 201 if (idSeparator >= 0) { 202 name = name.substring(0, idSeparator); 203 } 204 205 return name.contains(CompilerConstants.ANON_FUNCTION_PREFIX.symbolName()) ? "<anonymous>" : name; 206 } 207 208 /** 209 * Return a formatted script stack trace string with frames information separated by '\n' 210 * 211 * @param exception exception for which script stack string is returned 212 * @return formatted stack trace string 213 */ 214 public static String getScriptStackString(final Throwable exception) { 215 final StringBuilder buf = new StringBuilder(); 216 final StackTraceElement[] frames = getScriptFrames(exception); 217 for (final StackTraceElement st : frames) { 218 buf.append("\tat "); 219 buf.append(st.getMethodName()); 220 buf.append(" ("); 221 buf.append(st.getFileName()); 222 buf.append(':'); 223 buf.append(st.getLineNumber()); 224 buf.append(")\n"); 225 } 226 final int len = buf.length(); 227 // remove trailing '\n' 228 if (len > 0) { 229 assert buf.charAt(len - 1) == '\n'; 230 buf.deleteCharAt(len - 1); 231 } 232 return buf.toString(); 233 } 234 235 /** 236 * Get the thrown object. Subclass responsibility 237 * @return thrown object 238 */ 239 protected Object getThrown() { 240 return null; 241 } 242 243 /** 244 * Initialization function for ECMA errors. Stores the error 245 * in the ecmaError field of this class. It is only initialized 246 * once, and then reused 247 * 248 * @param global the global 249 * @return initialized exception 250 */ 251 NashornException initEcmaError(final ScriptObject global) { 252 if (ecmaError != null) { 253 return this; // initialized already! 254 } 255 256 final Object thrown = getThrown(); 257 if (thrown instanceof ScriptObject) { 258 setEcmaError(ScriptObjectMirror.wrap(thrown, global)); 259 } else { 260 setEcmaError(thrown); 261 } 262 263 return this; 264 } 265 266 /** 267 * Return the underlying ECMA error object, if available. 268 * 269 * @return underlying ECMA Error object's mirror or whatever was thrown 270 * from script such as a String, Number or a Boolean. 271 */ 272 public Object getEcmaError() { 273 return ecmaError; 274 } 275 276 /** 277 * Return the underlying ECMA error object, if available. 278 * 279 * @param ecmaError underlying ECMA Error object's mirror or whatever was thrown 280 * from script such as a String, Number or a Boolean. 281 */ 282 public void setEcmaError(final Object ecmaError) { 283 this.ecmaError = ecmaError; 284 } 285 286 private void ensureLineAndFileName() { 287 if (lineAndFileNameUnknown) { 288 for (final StackTraceElement ste : getStackTrace()) { 289 if (ECMAErrors.isScriptFrame(ste)) { 290 // Whatever here is compiled from JavaScript code 291 fileName = ste.getFileName(); 292 line = ste.getLineNumber(); 293 return; 294 } 295 } 296 297 lineAndFileNameUnknown = false; 298 } 299 } 300 }