NashornException.java revision 1612:0da44ab8c417
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
26package jdk.nashorn.api.scripting;
27
28import java.util.ArrayList;
29import java.util.List;
30import jdk.nashorn.internal.codegen.CompilerConstants;
31import jdk.nashorn.internal.runtime.ECMAErrors;
32import 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 * @since 1.8u40
46 */
47@SuppressWarnings("serial")
48public abstract class NashornException extends RuntimeException {
49    // script file name
50    private String fileName;
51    // script line number
52    private int line;
53    // are the line and fileName unknown?
54    private boolean lineAndFileNameUnknown;
55    // script column number
56    private int column;
57    // underlying ECMA error object - lazily initialized
58    private Object ecmaError;
59
60    /**
61     * Constructor
62     *
63     * @param msg       exception message
64     * @param fileName  file name
65     * @param line      line number
66     * @param column    column number
67     */
68    protected NashornException(final String msg, final String fileName, final int line, final int column) {
69        this(msg, null, fileName, line, column);
70    }
71
72    /**
73     * Constructor
74     *
75     * @param msg       exception message
76     * @param cause     exception cause
77     * @param fileName  file name
78     * @param line      line number
79     * @param column    column number
80     */
81    protected NashornException(final String msg, final Throwable cause, final String fileName, final int line, final int column) {
82        super(msg, cause == null ? null : cause);
83        this.fileName = fileName;
84        this.line = line;
85        this.column = column;
86    }
87
88    /**
89     * Constructor
90     *
91     * @param msg       exception message
92     * @param cause     exception cause
93     */
94    protected NashornException(final String msg, final Throwable cause) {
95        super(msg, cause == null ? null : cause);
96        // Hard luck - no column number info
97        this.column = -1;
98        // We can retrieve the line number and file name from the stack trace if needed
99        this.lineAndFileNameUnknown = true;
100    }
101
102    /**
103     * Get the source file name for this {@code NashornException}
104     *
105     * @return the file name
106     */
107    public final String getFileName() {
108        ensureLineAndFileName();
109        return fileName;
110    }
111
112    /**
113     * Set the source file name for this {@code NashornException}
114     *
115     * @param fileName the file name
116     */
117    public final void setFileName(final String fileName) {
118        this.fileName = fileName;
119        lineAndFileNameUnknown = false;
120    }
121
122    /**
123     * Get the line number for this {@code NashornException}
124     *
125     * @return the line number
126     */
127    public final int getLineNumber() {
128        ensureLineAndFileName();
129        return line;
130    }
131
132    /**
133     * Set the line number for this {@code NashornException}
134     *
135     * @param line the line number
136     */
137    public final void setLineNumber(final int line) {
138        lineAndFileNameUnknown = false;
139        this.line = line;
140    }
141
142    /**
143     * Get the column for this {@code NashornException}
144     *
145     * @return the column number
146     */
147    public final int getColumnNumber() {
148        return column;
149    }
150
151    /**
152     * Set the column for this {@code NashornException}
153     *
154     * @param column the column number
155     */
156    public final void setColumnNumber(final int column) {
157        this.column = column;
158    }
159
160    /**
161     * Returns array javascript stack frames from the given exception object.
162     *
163     * @param exception exception from which stack frames are retrieved and filtered
164     * @return array of javascript stack frames
165     */
166    public static StackTraceElement[] getScriptFrames(final Throwable exception) {
167        final StackTraceElement[] frames = exception.getStackTrace();
168        final List<StackTraceElement> filtered = new ArrayList<>();
169        for (final StackTraceElement st : frames) {
170            if (ECMAErrors.isScriptFrame(st)) {
171                final String className = "<" + st.getFileName() + ">";
172                String methodName = st.getMethodName();
173                if (methodName.equals(CompilerConstants.PROGRAM.symbolName())) {
174                    methodName = "<program>";
175                }
176
177                if (methodName.contains(CompilerConstants.ANON_FUNCTION_PREFIX.symbolName())) {
178                    methodName = "<anonymous>";
179                }
180
181                filtered.add(new StackTraceElement(className, methodName,
182                        st.getFileName(), st.getLineNumber()));
183            }
184        }
185        return filtered.toArray(new StackTraceElement[0]);
186    }
187
188    /**
189     * Return a formatted script stack trace string with frames information separated by '\n'
190     *
191     * @param exception exception for which script stack string is returned
192     * @return formatted stack trace string
193     */
194    public static String getScriptStackString(final Throwable exception) {
195        final StringBuilder buf = new StringBuilder();
196        final StackTraceElement[] frames = getScriptFrames(exception);
197        for (final StackTraceElement st : frames) {
198            buf.append("\tat ");
199            buf.append(st.getMethodName());
200            buf.append(" (");
201            buf.append(st.getFileName());
202            buf.append(':');
203            buf.append(st.getLineNumber());
204            buf.append(")\n");
205        }
206        final int len = buf.length();
207        // remove trailing '\n'
208        if (len > 0) {
209            assert buf.charAt(len - 1) == '\n';
210            buf.deleteCharAt(len - 1);
211        }
212        return buf.toString();
213    }
214
215    /**
216     * Get the thrown object. Subclass responsibility
217     * @return thrown object
218     */
219    protected Object getThrown() {
220        return null;
221    }
222
223    /**
224     * Initialization function for ECMA errors. Stores the error
225     * in the ecmaError field of this class. It is only initialized
226     * once, and then reused
227     *
228     * @param global the global
229     * @return initialized exception
230     */
231    protected NashornException initEcmaError(final ScriptObject global) {
232        if (ecmaError != null) {
233            return this; // initialized already!
234        }
235
236        final Object thrown = getThrown();
237        if (thrown instanceof ScriptObject) {
238            setEcmaError(ScriptObjectMirror.wrap(thrown, global));
239        } else {
240            setEcmaError(thrown);
241        }
242
243        return this;
244    }
245
246    /**
247     * Return the underlying ECMA error object, if available.
248     *
249     * @return underlying ECMA Error object's mirror or whatever was thrown
250     *         from script such as a String, Number or a Boolean.
251     */
252    public Object getEcmaError() {
253        return ecmaError;
254    }
255
256    /**
257     * Return the underlying ECMA error object, if available.
258     *
259     * @param ecmaError underlying ECMA Error object's mirror or whatever was thrown
260     *         from script such as a String, Number or a Boolean.
261     */
262    public void setEcmaError(final Object ecmaError) {
263        this.ecmaError = ecmaError;
264    }
265
266    private void ensureLineAndFileName() {
267        if (lineAndFileNameUnknown) {
268            for (final StackTraceElement ste : getStackTrace()) {
269                if (ECMAErrors.isScriptFrame(ste)) {
270                    // Whatever here is compiled from JavaScript code
271                    fileName = ste.getFileName();
272                    line = ste.getLineNumber();
273                    return;
274                }
275            }
276
277            lineAndFileNameUnknown = false;
278        }
279    }
280}
281