ECMAException.java revision 953:221a84ef44c0
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 26package jdk.nashorn.internal.runtime; 27 28import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; 29import static jdk.nashorn.internal.codegen.CompilerConstants.virtualField; 30import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 31 32import javax.script.ScriptException; 33import jdk.nashorn.api.scripting.NashornException; 34import jdk.nashorn.internal.codegen.CompilerConstants.Call; 35import 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") 45public 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 ScriptObject sobj = (ScriptObject)thrown; 100 final Object exception = getException(sobj); 101 if (exception instanceof ECMAException) { 102 // copy over file name, line number and column number. 103 final ECMAException ee = (ECMAException)exception; 104 ee.setFileName(fileName); 105 ee.setLineNumber(line); 106 ee.setColumnNumber(column); 107 return ee; 108 } 109 } 110 111 return new ECMAException(thrown, fileName, line, column); 112 } 113 114 /** 115 * Get the thrown object 116 * @return thrown object 117 */ 118 @Override 119 public Object getThrown() { 120 return thrown; 121 } 122 123 @Override 124 public String toString() { 125 final StringBuilder sb = new StringBuilder(); 126 final String fileName = getFileName(); 127 final int line = getLineNumber(); 128 final int column = getColumnNumber(); 129 130 if (fileName != null) { 131 sb.append(fileName); 132 if (line >= 0) { 133 sb.append(':'); 134 sb.append(line); 135 } 136 if (column >= 0) { 137 sb.append(':'); 138 sb.append(column); 139 } 140 sb.append(' '); 141 } else { 142 sb.append("ECMAScript Exception: "); 143 } 144 145 sb.append(getMessage()); 146 return sb.toString(); 147 } 148 149 /** 150 * Get the {@link ECMAException}, i.e. the underlying Java object for the 151 * JavaScript error object from a {@link ScriptObject} representing an error 152 * 153 * @param errObj the error object 154 * @return a {@link ECMAException} 155 */ 156 public static Object getException(final ScriptObject errObj) { 157 return errObj.get(ECMAException.EXCEPTION_PROPERTY); 158 } 159 160 /** 161 * Print the stack trace for a {@code ScriptObject} representing an error 162 * 163 * @param errObj the error object 164 * @return undefined 165 */ 166 public static Object printStackTrace(final ScriptObject errObj) { 167 final Object exception = getException(errObj); 168 if (exception instanceof Throwable) { 169 ((Throwable)exception).printStackTrace(Context.getCurrentErr()); 170 } else { 171 Context.err("<stack trace not available>"); 172 } 173 return UNDEFINED; 174 } 175 176 /** 177 * Get the line number for a {@code ScriptObject} representing an error 178 * 179 * @param errObj the error object 180 * @return the line number, or undefined if wrapped exception is not a ParserException 181 */ 182 public static Object getLineNumber(final ScriptObject errObj) { 183 final Object e = getException(errObj); 184 if (e instanceof NashornException) { 185 return ((NashornException)e).getLineNumber(); 186 } else if (e instanceof ScriptException) { 187 return ((ScriptException)e).getLineNumber(); 188 } 189 190 return UNDEFINED; 191 } 192 193 /** 194 * Get the column number for a {@code ScriptObject} representing an error 195 * 196 * @param errObj the error object 197 * @return the column number, or undefined if wrapped exception is not a ParserException 198 */ 199 public static Object getColumnNumber(final ScriptObject errObj) { 200 final Object e = getException(errObj); 201 if (e instanceof NashornException) { 202 return ((NashornException)e).getColumnNumber(); 203 } else if (e instanceof ScriptException) { 204 return ((ScriptException)e).getColumnNumber(); 205 } 206 207 return UNDEFINED; 208 } 209 210 /** 211 * Get the file name for a {@code ScriptObject} representing an error 212 * 213 * @param errObj the error object 214 * @return the file name, or undefined if wrapped exception is not a ParserException 215 */ 216 public static Object getFileName(final ScriptObject errObj) { 217 final Object e = getException(errObj); 218 if (e instanceof NashornException) { 219 return ((NashornException)e).getFileName(); 220 } else if (e instanceof ScriptException) { 221 return ((ScriptException)e).getFileName(); 222 } 223 224 return UNDEFINED; 225 } 226 227 /** 228 * Stateless string conversion for an error object 229 * 230 * @param errObj the error object 231 * @return string representation of {@code errObj} 232 */ 233 public static String safeToString(final ScriptObject errObj) { 234 Object name = UNDEFINED; 235 try { 236 name = errObj.get("name"); 237 } catch (final Exception e) { 238 //ignored 239 } 240 241 if (name == UNDEFINED) { 242 name = "Error"; 243 } else { 244 name = ScriptRuntime.safeToString(name); 245 } 246 247 Object msg = UNDEFINED; 248 try { 249 msg = errObj.get("message"); 250 } catch (final Exception e) { 251 //ignored 252 } 253 254 if (msg == UNDEFINED) { 255 msg = ""; 256 } else { 257 msg = ScriptRuntime.safeToString(msg); 258 } 259 260 if (((String)name).isEmpty()) { 261 return (String)msg; 262 } 263 264 if (((String)msg).isEmpty()) { 265 return (String)name; 266 } 267 268 return name + ": " + msg; 269 } 270 271 private static Throwable asThrowable(final Object obj) { 272 return (obj instanceof Throwable)? (Throwable)obj : null; 273 } 274 275 private void setExceptionToThrown() { 276 /* 277 * Nashorn extension: errorObject.nashornException 278 * Expose this exception via "nashornException" property of on the 279 * thrown object. This exception object can be used to print stack 280 * trace and fileName, line number etc. from script code. 281 */ 282 283 if (thrown instanceof ScriptObject) { 284 final ScriptObject sobj = (ScriptObject)thrown; 285 if (!sobj.has(EXCEPTION_PROPERTY)) { 286 sobj.addOwnProperty(EXCEPTION_PROPERTY, Property.NOT_ENUMERABLE, this); 287 } else { 288 sobj.set(EXCEPTION_PROPERTY, this, false); 289 } 290 } 291 } 292} 293