View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import java.io.BufferedReader;
18  import java.io.IOException;
19  import java.io.PrintStream;
20  import java.io.PrintWriter;
21  import java.io.StringReader;
22  import java.io.StringWriter;
23  import java.util.StringTokenizer;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.htmlunit.corejs.javascript.EcmaError;
28  import org.htmlunit.corejs.javascript.JavaScriptException;
29  import org.htmlunit.corejs.javascript.RhinoException;
30  import org.htmlunit.corejs.javascript.WrappedException;
31  import org.htmlunit.html.HtmlPage;
32  
33  /**
34   * An exception that will be thrown if an error occurs during the processing of
35   * a script.
36   *
37   * @author Mike Bowler
38   * @author Marc Guillemot
39   * @author Frank Danek
40   * @author Ronald Brill
41   */
42  public class ScriptException extends RuntimeException {
43  
44      /** Logging support. */
45      private static final Log LOG = LogFactory.getLog(ScriptException.class);
46  
47      private final String scriptSourceCode_;
48      private final HtmlPage page_;
49  
50      /**
51       * Creates an instance.
52       * @param page the page in which the script causing this exception was executed
53       * @param throwable the exception that was thrown from the script engine
54       * @param scriptSourceCode the code that was being executed when this exception
55       *        was thrown. This may be null if the exception was not caused by execution
56       *        of JavaScript.
57       */
58      public ScriptException(final HtmlPage page, final Throwable throwable,
59              final String scriptSourceCode) {
60          super(getMessageFrom(throwable), throwable);
61          scriptSourceCode_ = scriptSourceCode;
62          page_ = page;
63      }
64  
65      private static String getMessageFrom(final Throwable throwable) {
66          if (throwable == null) {
67              return "null";
68          }
69          return throwable.getMessage();
70      }
71  
72      /**
73       * Creates an instance.
74       * @param page the page in which the script causing this exception was executed
75       * @param throwable the exception that was thrown from the script engine
76       */
77      public ScriptException(final HtmlPage page, final Throwable throwable) {
78          this(page, throwable, null);
79      }
80  
81      /**
82       * Prints the stack trace to System.out. If this exception contains another
83       * exception then the stack traces for both will be printed.
84       */
85      @Override
86      public void printStackTrace() {
87          printStackTrace(System.out);
88      }
89  
90      /**
91       * Prints the stack trace. If this exception contains another exception then
92       * the stack traces for both will be printed.
93       *
94       * @param writer Where the stack trace will be written
95       */
96      @Override
97      public void printStackTrace(final PrintWriter writer) {
98          writer.write(createPrintableStackTrace());
99      }
100 
101     /**
102      * Prints the stack trace. If this exception contains another exception then
103      * the stack traces for both will be printed.
104      *
105      * @param stream Where the stack trace will be written
106      */
107     @Override
108     public void printStackTrace(final PrintStream stream) {
109         stream.print(createPrintableStackTrace());
110     }
111 
112     private String createPrintableStackTrace() {
113         final StringWriter stringWriter = new StringWriter();
114         final PrintWriter printWriter = new PrintWriter(stringWriter);
115 
116         printWriter.println("======= EXCEPTION START ========");
117 
118         if (getCause() != null) {
119             if (getCause() instanceof EcmaError ecmaError) {
120                 printWriter.print("EcmaError: ");
121                 printWriter.print("lineNumber=[");
122                 printWriter.print(ecmaError.lineNumber());
123                 printWriter.print("] column=[");
124                 printWriter.print(ecmaError.columnNumber());
125                 printWriter.print("] lineSource=[");
126                 printWriter.print(getFailingLine());
127                 printWriter.print("] name=[");
128                 printWriter.print(ecmaError.getName());
129                 printWriter.print("] sourceName=[");
130                 printWriter.print(ecmaError.sourceName());
131                 printWriter.print("] message=[");
132                 printWriter.print(ecmaError.getMessage());
133                 printWriter.print("]");
134                 printWriter.println();
135             }
136             else {
137                 printWriter.println("Exception class=[" + getCause().getClass().getName() + "]");
138             }
139         }
140 
141         super.printStackTrace(printWriter);
142         if (getCause() instanceof JavaScriptException) {
143             final Object value = ((JavaScriptException) getCause()).getValue();
144 
145             printWriter.print("JavaScriptException value = ");
146             if (value instanceof Throwable throwable) {
147                 throwable.printStackTrace(printWriter);
148             }
149             else {
150                 printWriter.println(value);
151             }
152         }
153         else if (getCause() instanceof WrappedException wrappedException) {
154             printWriter.print("WrappedException: ");
155             wrappedException.printStackTrace(printWriter);
156 
157             final Throwable innerException = wrappedException.getWrappedException();
158             if (innerException == null) {
159                 printWriter.println("Inside wrapped exception: null");
160             }
161             else {
162                 printWriter.println("Inside wrapped exception:");
163                 innerException.printStackTrace(printWriter);
164             }
165         }
166         else if (getCause() != null) {
167             printWriter.println("Enclosed exception: ");
168             getCause().printStackTrace(printWriter);
169         }
170 
171         if (scriptSourceCode_ != null && !scriptSourceCode_.isEmpty()) {
172             printWriter.println("== CALLING JAVASCRIPT ==");
173             printWriter.println(scriptSourceCode_);
174         }
175         printWriter.println("======= EXCEPTION END ========");
176 
177         return stringWriter.toString();
178     }
179 
180     /**
181      * Returns the source code line that failed.
182      * @return the source code line that failed
183      */
184     public String getScriptSourceCode() {
185         return scriptSourceCode_;
186     }
187 
188     /**
189      * Returns the line of source that was being executed when this exception was
190      * thrown.
191      *
192      * @return the line of source or an empty string if the exception was not thrown
193      *         due to the execution of a script.
194      */
195     public String getFailingLine() {
196         final int lineNumber = getFailingLineNumber();
197         if (lineNumber == -1 || scriptSourceCode_ == null) {
198             return "<no source>";
199         }
200 
201         try (BufferedReader reader = new BufferedReader(new StringReader(scriptSourceCode_))) {
202             for (int i = 0; i < lineNumber - 1; i++) {
203                 reader.readLine();
204             }
205             return reader.readLine();
206         }
207         catch (final IOException e) {
208             // Theoretically impossible
209             LOG.error("Reading scriptSourceCode failed.", e);
210         }
211 
212         return "";
213     }
214 
215     /**
216      * Returns the line number of the source that was executing at the time of the exception.
217      *
218      * @return the line number or -1 if the exception was not thrown due to the
219      *         execution of a script.
220      */
221     public int getFailingLineNumber() {
222         if (getCause() instanceof RhinoException cause) {
223             return cause.lineNumber();
224         }
225 
226         return -1;
227     }
228 
229     /**
230      * Returns the column number of the source that was executing at the time of the exception.
231      *
232      * @return the column number or -1 if the exception was not thrown due to the
233      *         execution of a script.
234      */
235     public int getFailingColumnNumber() {
236         if (getCause() instanceof RhinoException cause) {
237             return cause.columnNumber();
238         }
239 
240         return -1;
241     }
242 
243     /**
244      * Gets the HTML page in which the script error occurred.<br>
245      * Caution: this page may be only partially parsed if the exception occurred in a script
246      * executed at parsing time.
247      * @return the page
248      */
249     public HtmlPage getPage() {
250         return page_;
251     }
252 
253     /**
254      * Prints the script stack trace.
255      * This represents only the script calls.
256      * @param writer where the stack trace will be written
257      */
258     public void printScriptStackTrace(final PrintWriter writer) {
259         final StringWriter stringWriter = new StringWriter();
260         final PrintWriter printWriter = new PrintWriter(stringWriter);
261 
262         getCause().printStackTrace(printWriter);
263 
264         writer.print(getCause().getMessage());
265         final StringTokenizer st = new StringTokenizer(stringWriter.toString(), "\r\n");
266         while (st.hasMoreTokens()) {
267             final String line = st.nextToken();
268             if (line.contains("at script")) {
269                 writer.println();
270                 writer.print(line.replaceFirst("at script\\.?", "at "));
271             }
272         }
273     }
274 
275 }