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.constructorNoLookup; 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#ECMAException(Object, String, int, int)}, 48 */ 49 public static final Call THROW_INIT = constructorNoLookup(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. This is called from generated code to implement the {@code throw} 61 * instruction from generated script code 62 * 63 * @param thrown object to be thrown 64 * @param fileName script file name 65 * @param line line number of throw 66 * @param column column number of throw 67 */ 68 public ECMAException(final Object thrown, final String fileName, final int line, final int column) { 69 super(ScriptRuntime.safeToString(thrown), asThrowable(thrown), fileName, line, column); 70 this.thrown = thrown; 71 setExceptionToThrown(); 72 } 73 74 /** 75 * Constructor. This is called from runtime code in Nashorn to throw things like 76 * type errors. 77 * 78 * @param thrown object to be thrown 79 * @param cause Java exception that triggered this throw 80 */ 81 public ECMAException(final Object thrown, final Throwable cause) { 82 super(ScriptRuntime.safeToString(thrown), cause); 83 this.thrown = thrown; 84 setExceptionToThrown(); 85 } 86 87 /** 88 * Get the thrown object 89 * @return thrown object 90 */ 91 @Override 92 public Object getThrown() { 93 return thrown; 94 } 95 96 @Override 97 public String toString() { 98 final StringBuilder sb = new StringBuilder(); 99 final String fileName = getFileName(); 100 final int line = getLineNumber(); 101 final int column = getColumnNumber(); 102 103 if (fileName != null) { 104 sb.append(fileName); 105 if (line >= 0) { 106 sb.append(':'); 107 sb.append(line); 108 } 109 if (column >= 0) { 110 sb.append(':'); 111 sb.append(column); 112 } 113 sb.append(' '); 114 } else { 115 sb.append("ECMAScript Exception: "); 116 } 117 118 sb.append(getMessage()); 119 return sb.toString(); 120 } 121 122 /** 123 * Get the {@link ECMAException}, i.e. the underlying Java object for the 124 * JavaScript error object from a {@link ScriptObject} representing an error 125 * 126 * @param errObj the error object 127 * @return a {@link ECMAException} 128 */ 129 public static Object getException(final ScriptObject errObj) { 130 return errObj.get(ECMAException.EXCEPTION_PROPERTY); 131 } 132 133 /** 134 * Print the stack trace for a {@code ScriptObject} representing an error 135 * 136 * @param errObj the error object 137 * @return undefined 138 */ 139 public static Object printStackTrace(final ScriptObject errObj) { 140 final Object exception = getException(errObj); 141 if (exception instanceof Throwable) { 142 ((Throwable)exception).printStackTrace(Context.getCurrentErr()); 143 } else { 144 Context.err("<stack trace not available>"); 145 } 146 return UNDEFINED; 147 } 148 149 /** 150 * Get the line number for a {@code ScriptObject} representing an error 151 * 152 * @param errObj the error object 153 * @return the line number, or undefined if wrapped exception is not a ParserException 154 */ 155 public static Object getLineNumber(final ScriptObject errObj) { 156 final Object e = getException(errObj); 157 if (e instanceof NashornException) { 158 return ((NashornException)e).getLineNumber(); 159 } else if (e instanceof ScriptException) { 160 return ((ScriptException)e).getLineNumber(); 161 } 162 163 return UNDEFINED; 164 } 165 166 /** 167 * Get the column number for a {@code ScriptObject} representing an error 168 * 169 * @param errObj the error object 170 * @return the column number, or undefined if wrapped exception is not a ParserException 171 */ 172 public static Object getColumnNumber(final ScriptObject errObj) { 173 final Object e = getException(errObj); 174 if (e instanceof NashornException) { 175 return ((NashornException)e).getColumnNumber(); 176 } else if (e instanceof ScriptException) { 177 return ((ScriptException)e).getColumnNumber(); 178 } 179 180 return UNDEFINED; 181 } 182 183 /** 184 * Get the file name for a {@code ScriptObject} representing an error 185 * 186 * @param errObj the error object 187 * @return the file name, or undefined if wrapped exception is not a ParserException 188 */ 189 public static Object getFileName(final ScriptObject errObj) { 190 final Object e = getException(errObj); 191 if (e instanceof NashornException) { 192 return ((NashornException)e).getFileName(); 193 } else if (e instanceof ScriptException) { 194 return ((ScriptException)e).getFileName(); 195 } 196 197 return UNDEFINED; 198 } 199 200 /** 201 * Stateless string conversion for an error object 202 * 203 * @param errObj the error object 204 * @return string representation of {@code errObj} 205 */ 206 public static String safeToString(final ScriptObject errObj) { 207 Object name = UNDEFINED; 208 try { 209 name = errObj.get("name"); 210 } catch (final Exception e) { 211 //ignored 212 } 213 214 if (name == UNDEFINED) { 215 name = "Error"; 216 } else { 217 name = ScriptRuntime.safeToString(name); 218 } 219 220 Object msg = UNDEFINED; 221 try { 222 msg = errObj.get("message"); 223 } catch (final Exception e) { 224 //ignored 225 } 226 227 if (msg == UNDEFINED) { 228 msg = ""; 229 } else { 230 msg = ScriptRuntime.safeToString(msg); 231 } 232 233 if (((String)name).isEmpty()) { 234 return (String)msg; 235 } 236 237 if (((String)msg).isEmpty()) { 238 return (String)name; 239 } 240 241 return name + ": " + msg; 242 } 243 244 private static Throwable asThrowable(final Object obj) { 245 return (obj instanceof Throwable)? (Throwable)obj : null; 246 } 247 248 private void setExceptionToThrown() { 249 /* 250 * Nashorn extension: errorObject.nashornException 251 * Expose this exception via "nashornException" property of on the 252 * thrown object. This exception object can be used to print stack 253 * trace and fileName, line number etc. from script code. 254 */ 255 256 if (thrown instanceof ScriptObject) { 257 final ScriptObject sobj = (ScriptObject)thrown; 258 if (!sobj.has(EXCEPTION_PROPERTY)) { 259 sobj.addOwnProperty(EXCEPTION_PROPERTY, Property.NOT_ENUMERABLE, this); 260 } 261 } 262 } 263 }