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