       1 /*
       2  * Copyright (c) 2010, 2016, 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.api.scripting;
      27
      28 import java.util.ArrayList;
      29 import java.util.List;
      30 import jdk.nashorn.internal.codegen.CompilerConstants;
      31 import jdk.nashorn.internal.runtime.ECMAErrors;
      32 import jdk.nashorn.internal.runtime.ScriptObject;
      33
      34 /**
      35  * This is base exception for all Nashorn exceptions. These originate from
      36  * user's ECMAScript code. Example: script parse errors, exceptions thrown from
      37  * scripts. Note that ScriptEngine methods like "eval", "invokeMethod",
      38  * "invokeFunction" will wrap this as ScriptException and throw it. But, there
      39  * are cases where user may need to access this exception (or implementation
      40  * defined subtype of this). For example, if java interface is implemented by a
      41  * script object or Java access to script object properties via java.util.Map
      42  * interface. In these cases, user code will get an instance of this or
      43  * implementation defined subclass.
      44  *
      45  * @deprecated Nashorn JavaScript script engine and APIs, and the jjs tool
      46  * are deprecated with the intent to remove them in a future release.
      47  *
      48  * @since 1.8u40
      49  */
      50 @Deprecated(since="11", forRemoval=true)
      51 @SuppressWarnings("serial")
      52 public abstract class NashornException extends RuntimeException {
      53     private static final long serialVersionUID = 1L;
      54
      55     // script file name
      56     private String fileName;
      57     // script line number
      58     private int line;
      59     // are the line and fileName unknown?
      60     private boolean lineAndFileNameUnknown;
      61     // script column number
      62     private int column;
      63     // underlying ECMA error object - lazily initialized
      64     private Object ecmaError;
      65
      66     /**
      67      * Constructor to initialize error message, file name, line and column numbers.
      68      *
      69      * @param msg       exception message
      70      * @param fileName  file name
      71      * @param line      line number
      72      * @param column    column number
      73      */
      74     protected NashornException(final String msg, final String fileName, final int line, final int column) {
      75         this(msg, null, fileName, line, column);
      76     }
      77
      78     /**
      79      * Constructor to initialize error message, cause exception, file name, line and column numbers.
      80      *
      81      * @param msg       exception message
      82      * @param cause     exception cause
      83      * @param fileName  file name
      84      * @param line      line number
      85      * @param column    column number
      86      */
      87     protected NashornException(final String msg, final Throwable cause, final String fileName, final int line, final int column) {
      88         super(msg, cause == null ? null : cause);
      89         this.fileName = fileName;
      90         this.line = line;
      91         this.column = column;
      92     }
      93
      94     /**
      95      * Constructor to initialize error message and cause exception.
      96      *
      97      * @param msg       exception message
      98      * @param cause     exception cause
      99      */
     100     protected NashornException(final String msg, final Throwable cause) {
     101         super(msg, cause == null ? null : cause);
     102         // Hard luck - no column number info
     103         this.column = -1;
     104         // We can retrieve the line number and file name from the stack trace if needed
     105         this.lineAndFileNameUnknown = true;
     106     }
     107
     108     /**
     109      * Get the source file name for this {@code NashornException}
     110      *
     111      * @return the file name
     112      */
     113     public final String getFileName() {
     114         ensureLineAndFileName();
     115         return fileName;
     116     }
     117
     118     /**
     119      * Set the source file name for this {@code NashornException}
     120      *
     121      * @param fileName the file name
     122      */
     123     public final void setFileName(final String fileName) {
     124         this.fileName = fileName;
     125         lineAndFileNameUnknown = false;
     126     }
     127
     128     /**
     129      * Get the line number for this {@code NashornException}
     130      *
     131      * @return the line number
     132      */
     133     public final int getLineNumber() {
     134         ensureLineAndFileName();
     135         return line;
     136     }
     137
     138     /**
     139      * Set the line number for this {@code NashornException}
     140      *
     141      * @param line the line number
     142      */
     143     public final void setLineNumber(final int line) {
     144         lineAndFileNameUnknown = false;
     145         this.line = line;
     146     }
     147
     148     /**
     149      * Get the column for this {@code NashornException}
     150      *
     151      * @return the column number
     152      */
     153     public final int getColumnNumber() {
     154         return column;
     155     }
     156
     157     /**
     158      * Set the column for this {@code NashornException}
     159      *
     160      * @param column the column number
     161      */
     162     public final void setColumnNumber(final int column) {
     163         this.column = column;
     164     }
     165
     166     /**
     167      * Returns array javascript stack frames from the given exception object.
     168      *
     169      * @param exception exception from which stack frames are retrieved and filtered
     170      * @return array of javascript stack frames
     171      */
     172     public static StackTraceElement[] getScriptFrames(final Throwable exception) {
     173         final StackTraceElement[] frames = exception.getStackTrace();
     174         final List<StackTraceElement> filtered = new ArrayList<>();
     175         for (final StackTraceElement st : frames) {
     176             if (ECMAErrors.isScriptFrame(st)) {
     177                 final String className = "<" + st.getFileName() + ">";
     178                 String methodName = st.getMethodName();
     179                 if (methodName.equals(CompilerConstants.PROGRAM.symbolName())) {
     180                     methodName = "<program>";
     181                 } else {
     182                     methodName = stripMethodName(methodName);
     183                 }
     184
     185                 filtered.add(new StackTraceElement(className, methodName,
     186                         st.getFileName(), st.getLineNumber()));
     187             }
     188         }
     189         return filtered.toArray(new StackTraceElement[0]);
     190     }
     191
     192     private static String stripMethodName(final String methodName) {
     193         String name = methodName;
     194
     195         final int nestedSeparator = name.lastIndexOf(CompilerConstants.NESTED_FUNCTION_SEPARATOR.symbolName());
     196         if (nestedSeparator >= 0) {
     197             name = name.substring(nestedSeparator + 1);
     198         }
     199
     200         final int idSeparator = name.indexOf(CompilerConstants.ID_FUNCTION_SEPARATOR.symbolName());
     201         if (idSeparator >= 0) {
     202             name = name.substring(0, idSeparator);
     203         }
     204
     205         return name.contains(CompilerConstants.ANON_FUNCTION_PREFIX.symbolName()) ? "<anonymous>" : name;
     206     }
     207
     208     /**
     209      * Return a formatted script stack trace string with frames information separated by '\n'
     210      *
     211      * @param exception exception for which script stack string is returned
     212      * @return formatted stack trace string
     213      */
     214     public static String getScriptStackString(final Throwable exception) {
     215         final StringBuilder buf = new StringBuilder();
     216         final StackTraceElement[] frames = getScriptFrames(exception);
     217         for (final StackTraceElement st : frames) {
     218             buf.append("\tat ");
     219             buf.append(st.getMethodName());
     220             buf.append(" (");
     221             buf.append(st.getFileName());
     222             buf.append(':');
     223             buf.append(st.getLineNumber());
     224             buf.append(")\n");
     225         }
     226         final int len = buf.length();
     227         // remove trailing '\n'
     228         if (len > 0) {
     229             assert buf.charAt(len - 1) == '\n';
     230             buf.deleteCharAt(len - 1);
     231         }
     232         return buf.toString();
     233     }
     234
     235     /**
     236      * Get the thrown object. Subclass responsibility
     237      * @return thrown object
     238      */
     239     protected Object getThrown() {
     240         return null;
     241     }
     242
     243     /**
     244      * Initialization function for ECMA errors. Stores the error
     245      * in the ecmaError field of this class. It is only initialized
     246      * once, and then reused
     247      *
     248      * @param global the global
     249      * @return initialized exception
     250      */
     251     NashornException initEcmaError(final ScriptObject global) {
     252         if (ecmaError != null) {
     253             return this; // initialized already!
     254         }
     255
     256         final Object thrown = getThrown();
     257         if (thrown instanceof ScriptObject) {
     258             setEcmaError(ScriptObjectMirror.wrap(thrown, global));
     259         } else {
     260             setEcmaError(thrown);
     261         }
     262
     263         return this;
     264     }
     265
     266     /**
     267      * Return the underlying ECMA error object, if available.
     268      *
     269      * @return underlying ECMA Error object's mirror or whatever was thrown
     270      *         from script such as a String, Number or a Boolean.
     271      */
     272     public Object getEcmaError() {
     273         return ecmaError;
     274     }
     275
     276     /**
     277      * Return the underlying ECMA error object, if available.
     278      *
     279      * @param ecmaError underlying ECMA Error object's mirror or whatever was thrown
     280      *         from script such as a String, Number or a Boolean.
     281      */
     282     public void setEcmaError(final Object ecmaError) {
     283         this.ecmaError = ecmaError;
     284     }
     285
     286     private void ensureLineAndFileName() {
     287         if (lineAndFileNameUnknown) {
     288             for (final StackTraceElement ste : getStackTrace()) {
     289                 if (ECMAErrors.isScriptFrame(ste)) {
     290                     // Whatever here is compiled from JavaScript code
     291                     fileName = ste.getFileName();
     292                     line = ste.getLineNumber();
     293                     return;
     294                 }
     295             }
     296
     297             lineAndFileNameUnknown = false;
     298         }
     299     }
     300 }