1 /* 2 * Copyright (c) 2010, 2013, 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.internal.runtime; 27 28 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.virtualField; 30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 31 32 import javax.script.ScriptException; 33 import jdk.nashorn.api.scripting.NashornException; 34 import jdk.nashorn.internal.codegen.CompilerConstants.Call; 35 import jdk.nashorn.internal.codegen.CompilerConstants.FieldAccess; 36 37 /** 38 * Exception used to implement ECMAScript "throw" from scripts. The actual thrown 39 * object from script need not be a Java exception and so it is wrapped as an 40 * instance field called "thrown" here. This exception class is also used to 41 * represent ECMA errors thrown from runtime code (for example, TypeError, 42 * ReferenceError thrown from Nashorn engine runtime). 43 */ 44 @SuppressWarnings("serial") 45 public final class ECMAException extends NashornException { 46 /** 47 * Method handle pointing to the constructor {@link ECMAException#create(Object, String, int, int)}, 48 */ 49 public static final Call CREATE = staticCallNoLookup(ECMAException.class, "create", ECMAException.class, Object.class, String.class, int.class, int.class); 50 51 /** Field handle to the{@link ECMAException#thrown} field, so that it can be accessed from generated code */ 52 public static final FieldAccess THROWN = virtualField(ECMAException.class, "thrown", Object.class); 53 54 private static final String EXCEPTION_PROPERTY = "nashornException"; 55 56 /** Object thrown. */ 57 public final Object thrown; 58 59 /** 60 * Constructor. Called from the factory method 'create'. 61 * 62 * @param thrown object to be thrown 63 * @param fileName script file name 64 * @param line line number of throw 65 * @param column column number of throw 66 */ 67 private ECMAException(final Object thrown, final String fileName, final int line, final int column) { 68 super(ScriptRuntime.safeToString(thrown), asThrowable(thrown), fileName, line, column); 69 this.thrown = thrown; 70 setExceptionToThrown(); 71 } 72 73 /** 74 * Constructor. This is called from the runtime code. 75 * 76 * @param thrown object to be thrown 77 * @param cause Java exception that triggered this throw 78 */ 79 public ECMAException(final Object thrown, final Throwable cause) { 80 super(ScriptRuntime.safeToString(thrown), cause); 81 this.thrown = thrown; 82 setExceptionToThrown(); 83 } 84 85 /** 86 * Factory method to retrieve the underlying exception or create an exception. 87 * This method is called from the generated code. 88 * 89 * @param thrown object to be thrown 90 * @param fileName script file name 91 * @param line line number of throw 92 * @param column column number of throw 93 * @return ECMAException object 94 */ 95 public static ECMAException create(final Object thrown, final String fileName, final int line, final int column) { 96 // If thrown object is an Error or sub-object like TypeError, then 97 // an ECMAException object has been already initialized at constructor. 98 if (thrown instanceof ScriptObject) { 99 final Object exception = getException((ScriptObject)thrown); 100 if (exception instanceof ECMAException) { 101 final ECMAException ee = (ECMAException)exception; 102 // Make sure exception has correct thrown reference because that's what will end up getting caught. 103 if (ee.getThrown() == thrown) { 104 // copy over file name, line number and column number. 105 ee.setFileName(fileName); 106 ee.setLineNumber(line); 107 ee.setColumnNumber(column); 108 return ee; 109 } 110 } 111 } 112 113 return new ECMAException(thrown, fileName, line, column); 114 } 115 116 /** 117 * Get the thrown object 118 * @return thrown object 119 */ 120 @Override 121 public Object getThrown() { 122 return thrown; 123 } 124 125 @Override 126 public String toString() { 127 final StringBuilder sb = new StringBuilder(); 128 final String fileName = getFileName(); 129 final int line = getLineNumber(); 130 final int column = getColumnNumber(); 131 132 if (fileName != null) { 133 sb.append(fileName); 134 if (line >= 0) { 135 sb.append(':'); 136 sb.append(line); 137 } 138 if (column >= 0) { 139 sb.append(':'); 140 sb.append(column); 141 } 142 sb.append(' '); 143 } else { 144 sb.append("ECMAScript Exception: "); 145 } 146 147 sb.append(getMessage()); 148 return sb.toString(); 149 } 150 151 /** 152 * Get the {@link ECMAException}, i.e. the underlying Java object for the 153 * JavaScript error object from a {@link ScriptObject} representing an error 154 * 155 * @param errObj the error object 156 * @return a {@link ECMAException} 157 */ 158 public static Object getException(final ScriptObject errObj) { 159 // Exclude inherited properties that may belong to errors in the prototype chain. 160 if (errObj.hasOwnProperty(ECMAException.EXCEPTION_PROPERTY)) { 161 return errObj.get(ECMAException.EXCEPTION_PROPERTY); 162 } 163 return null; 164 } 165 166 /** 167 * Print the stack trace for a {@code ScriptObject} representing an error 168 * 169 * @param errObj the error object 170 * @return undefined 171 */ 172 public static Object printStackTrace(final ScriptObject errObj) { 173 final Object exception = getException(errObj); 174 if (exception instanceof Throwable) { 175 ((Throwable)exception).printStackTrace(Context.getCurrentErr()); 176 } else { 177 Context.err("<stack trace not available>"); 178 } 179 return UNDEFINED; 180 } 181 182 /** 183 * Get the line number for a {@code ScriptObject} representing an error 184 * 185 * @param errObj the error object 186 * @return the line number, or undefined if wrapped exception is not a ParserException 187 */ 188 public static Object getLineNumber(final ScriptObject errObj) { 189 final Object e = getException(errObj); 190 if (e instanceof NashornException) { 191 return ((NashornException)e).getLineNumber(); 192 } else if (e instanceof ScriptException) { 193 return ((ScriptException)e).getLineNumber(); 194 } 195 196 return UNDEFINED; 197 } 198 199 /** 200 * Get the column number for a {@code ScriptObject} representing an error 201 * 202 * @param errObj the error object 203 * @return the column number, or undefined if wrapped exception is not a ParserException 204 */ 205 public static Object getColumnNumber(final ScriptObject errObj) { 206 final Object e = getException(errObj); 207 if (e instanceof NashornException) { 208 return ((NashornException)e).getColumnNumber(); 209 } else if (e instanceof ScriptException) { 210 return ((ScriptException)e).getColumnNumber(); 211 } 212 213 return UNDEFINED; 214 } 215 216 /** 217 * Get the file name for a {@code ScriptObject} representing an error 218 * 219 * @param errObj the error object 220 * @return the file name, or undefined if wrapped exception is not a ParserException 221 */ 222 public static Object getFileName(final ScriptObject errObj) { 223 final Object e = getException(errObj); 224 if (e instanceof NashornException) { 225 return ((NashornException)e).getFileName(); 226 } else if (e instanceof ScriptException) { 227 return ((ScriptException)e).getFileName(); 228 } 229 230 return UNDEFINED; 231 } 232 233 /** 234 * Stateless string conversion for an error object 235 * 236 * @param errObj the error object 237 * @return string representation of {@code errObj} 238 */ 239 public static String safeToString(final ScriptObject errObj) { 240 Object name = UNDEFINED; 241 try { 242 name = errObj.get("name"); 243 } catch (final Exception e) { 244 //ignored 245 } 246 247 if (name == UNDEFINED) { 248 name = "Error"; 249 } else { 250 name = ScriptRuntime.safeToString(name); 251 } 252 253 Object msg = UNDEFINED; 254 try { 255 msg = errObj.get("message"); 256 } catch (final Exception e) { 257 //ignored 258 } 259 260 if (msg == UNDEFINED) { 261 msg = ""; 262 } else { 263 msg = ScriptRuntime.safeToString(msg); 264 } 265 266 if (((String)name).isEmpty()) { 267 return (String)msg; 268 } 269 270 if (((String)msg).isEmpty()) { 271 return (String)name; 272 } 273 274 return name + ": " + msg; 275 } 276 277 private static Throwable asThrowable(final Object obj) { 278 return (obj instanceof Throwable)? (Throwable)obj : null; 279 } 280 281 private void setExceptionToThrown() { 282 /* 283 * Nashorn extension: errorObject.nashornException 284 * Expose this exception via "nashornException" property of on the 285 * thrown object. This exception object can be used to print stack 286 * trace and fileName, line number etc. from script code. 287 */ 288 289 if (thrown instanceof ScriptObject) { 290 final ScriptObject sobj = (ScriptObject)thrown; 291 if (!sobj.has(EXCEPTION_PROPERTY)) { 292 sobj.addOwnProperty(EXCEPTION_PROPERTY, Property.NOT_ENUMERABLE, this); 293 } else { 294 sobj.set(EXCEPTION_PROPERTY, this, 0); 295 } 296 } 297 } 298 }