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