/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.virtualField; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import javax.script.ScriptException; import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.codegen.CompilerConstants.FieldAccess; /** * Exception used to implement ECMAScript "throw" from scripts. The actual thrown * object from script need not be a Java exception and so it is wrapped as an * instance field called "thrown" here. This exception class is also used to * represent ECMA errors thrown from runtime code (for example, TypeError, * ReferenceError thrown from Nashorn engine runtime). */ @SuppressWarnings("serial") public final class ECMAException extends NashornException { /** * Method handle pointing to the constructor {@link ECMAException#create(Object, String, int, int)}, */ public static final Call CREATE = staticCallNoLookup(ECMAException.class, "create", ECMAException.class, Object.class, String.class, int.class, int.class); /** Field handle to the{@link ECMAException#thrown} field, so that it can be accessed from generated code */ public static final FieldAccess THROWN = virtualField(ECMAException.class, "thrown", Object.class); private static final String EXCEPTION_PROPERTY = "nashornException"; /** Object thrown. */ public final Object thrown; /** * Constructor. Called from the factory method 'create'. * * @param thrown object to be thrown * @param fileName script file name * @param line line number of throw * @param column column number of throw */ private ECMAException(final Object thrown, final String fileName, final int line, final int column) { super(ScriptRuntime.safeToString(thrown), asThrowable(thrown), fileName, line, column); this.thrown = thrown; setExceptionToThrown(); } /** * Constructor. This is called from the runtime code. * * @param thrown object to be thrown * @param cause Java exception that triggered this throw */ public ECMAException(final Object thrown, final Throwable cause) { super(ScriptRuntime.safeToString(thrown), cause); this.thrown = thrown; setExceptionToThrown(); } /** * Factory method to retrieve the underlying exception or create an exception. * This method is called from the generated code. * * @param thrown object to be thrown * @param fileName script file name * @param line line number of throw * @param column column number of throw * @return ECMAException object */ public static ECMAException create(final Object thrown, final String fileName, final int line, final int column) { // If thrown object is an Error or sub-object like TypeError, then // an ECMAException object has been already initialized at constructor. if (thrown instanceof ScriptObject) { final Object exception = getException((ScriptObject)thrown); if (exception instanceof ECMAException) { final ECMAException ee = (ECMAException)exception; // Make sure exception has correct thrown reference because that's what will end up getting caught. if (ee.getThrown() == thrown) { // copy over file name, line number and column number. ee.setFileName(fileName); ee.setLineNumber(line); ee.setColumnNumber(column); return ee; } } } return new ECMAException(thrown, fileName, line, column); } /** * Get the thrown object * @return thrown object */ @Override public Object getThrown() { return thrown; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); final String fileName = getFileName(); final int line = getLineNumber(); final int column = getColumnNumber(); if (fileName != null) { sb.append(fileName); if (line >= 0) { sb.append(':'); sb.append(line); } if (column >= 0) { sb.append(':'); sb.append(column); } sb.append(' '); } else { sb.append("ECMAScript Exception: "); } sb.append(getMessage()); return sb.toString(); } /** * Get the {@link ECMAException}, i.e. the underlying Java object for the * JavaScript error object from a {@link ScriptObject} representing an error * * @param errObj the error object * @return a {@link ECMAException} */ public static Object getException(final ScriptObject errObj) { // Exclude inherited properties that may belong to errors in the prototype chain. if (errObj.hasOwnProperty(ECMAException.EXCEPTION_PROPERTY)) { return errObj.get(ECMAException.EXCEPTION_PROPERTY); } return null; } /** * Print the stack trace for a {@code ScriptObject} representing an error * * @param errObj the error object * @return undefined */ public static Object printStackTrace(final ScriptObject errObj) { final Object exception = getException(errObj); if (exception instanceof Throwable) { ((Throwable)exception).printStackTrace(Context.getCurrentErr()); } else { Context.err("<stack trace not available>"); } return UNDEFINED; } /** * Get the line number for a {@code ScriptObject} representing an error * * @param errObj the error object * @return the line number, or undefined if wrapped exception is not a ParserException */ public static Object getLineNumber(final ScriptObject errObj) { final Object e = getException(errObj); if (e instanceof NashornException) { return ((NashornException)e).getLineNumber(); } else if (e instanceof ScriptException) { return ((ScriptException)e).getLineNumber(); } return UNDEFINED; } /** * Get the column number for a {@code ScriptObject} representing an error * * @param errObj the error object * @return the column number, or undefined if wrapped exception is not a ParserException */ public static Object getColumnNumber(final ScriptObject errObj) { final Object e = getException(errObj); if (e instanceof NashornException) { return ((NashornException)e).getColumnNumber(); } else if (e instanceof ScriptException) { return ((ScriptException)e).getColumnNumber(); } return UNDEFINED; } /** * Get the file name for a {@code ScriptObject} representing an error * * @param errObj the error object * @return the file name, or undefined if wrapped exception is not a ParserException */ public static Object getFileName(final ScriptObject errObj) { final Object e = getException(errObj); if (e instanceof NashornException) { return ((NashornException)e).getFileName(); } else if (e instanceof ScriptException) { return ((ScriptException)e).getFileName(); } return UNDEFINED; } /** * Stateless string conversion for an error object * * @param errObj the error object * @return string representation of {@code errObj} */ public static String safeToString(final ScriptObject errObj) { Object name = UNDEFINED; try { name = errObj.get("name"); } catch (final Exception e) { //ignored } if (name == UNDEFINED) { name = "Error"; } else { name = ScriptRuntime.safeToString(name); } Object msg = UNDEFINED; try { msg = errObj.get("message"); } catch (final Exception e) { //ignored } if (msg == UNDEFINED) { msg = ""; } else { msg = ScriptRuntime.safeToString(msg); } if (((String)name).isEmpty()) { return (String)msg; } if (((String)msg).isEmpty()) { return (String)name; } return name + ": " + msg; } private static Throwable asThrowable(final Object obj) { return (obj instanceof Throwable)? (Throwable)obj : null; } private void setExceptionToThrown() { /* * Nashorn extension: errorObject.nashornException * Expose this exception via "nashornException" property of on the * thrown object. This exception object can be used to print stack * trace and fileName, line number etc. from script code. */ if (thrown instanceof ScriptObject) { final ScriptObject sobj = (ScriptObject)thrown; if (!sobj.has(EXCEPTION_PROPERTY)) { sobj.addOwnProperty(EXCEPTION_PROPERTY, Property.NOT_ENUMERABLE, this); } else { sobj.set(EXCEPTION_PROPERTY, this, 0); } } } }