View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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 <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
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) {
120                 final EcmaError ecmaError = (EcmaError) getCause();
121                 printWriter.print("EcmaError: ");
122                 printWriter.print("lineNumber=[");
123                 printWriter.print(ecmaError.lineNumber());
124                 printWriter.print("] column=[");
125                 printWriter.print(ecmaError.columnNumber());
126                 printWriter.print("] lineSource=[");
127                 printWriter.print(getFailingLine());
128                 printWriter.print("] name=[");
129                 printWriter.print(ecmaError.getName());
130                 printWriter.print("] sourceName=[");
131                 printWriter.print(ecmaError.sourceName());
132                 printWriter.print("] message=[");
133                 printWriter.print(ecmaError.getMessage());
134                 printWriter.print("]");
135                 printWriter.println();
136             }
137             else {
138                 printWriter.println("Exception class=[" + getCause().getClass().getName() + "]");
139             }
140         }
141 
142         super.printStackTrace(printWriter);
143         if (getCause() instanceof JavaScriptException) {
144             final Object value = ((JavaScriptException) getCause()).getValue();
145 
146             printWriter.print("JavaScriptException value = ");
147             if (value instanceof Throwable) {
148                 ((Throwable) value).printStackTrace(printWriter);
149             }
150             else {
151                 printWriter.println(value);
152             }
153         }
154         else if (getCause() instanceof WrappedException) {
155             final WrappedException wrappedException = (WrappedException) getCause();
156             printWriter.print("WrappedException: ");
157             wrappedException.printStackTrace(printWriter);
158 
159             final Throwable innerException = wrappedException.getWrappedException();
160             if (innerException == null) {
161                 printWriter.println("Inside wrapped exception: null");
162             }
163             else {
164                 printWriter.println("Inside wrapped exception:");
165                 innerException.printStackTrace(printWriter);
166             }
167         }
168         else if (getCause() != null) {
169             printWriter.println("Enclosed exception: ");
170             getCause().printStackTrace(printWriter);
171         }
172 
173         if (scriptSourceCode_ != null && !scriptSourceCode_.isEmpty()) {
174             printWriter.println("== CALLING JAVASCRIPT ==");
175             printWriter.println(scriptSourceCode_);
176         }
177         printWriter.println("======= EXCEPTION END ========");
178 
179         return stringWriter.toString();
180     }
181 
182     /**
183      * Returns the source code line that failed.
184      * @return the source code line that failed
185      */
186     public String getScriptSourceCode() {
187         return scriptSourceCode_;
188     }
189 
190     /**
191      * Returns the line of source that was being executed when this exception was
192      * thrown.
193      *
194      * @return the line of source or an empty string if the exception was not thrown
195      *         due to the execution of a script.
196      */
197     public String getFailingLine() {
198         final int lineNumber = getFailingLineNumber();
199         if (lineNumber == -1 || scriptSourceCode_ == null) {
200             return "<no source>";
201         }
202 
203         try (BufferedReader reader = new BufferedReader(new StringReader(scriptSourceCode_))) {
204             for (int i = 0; i < lineNumber - 1; i++) {
205                 reader.readLine();
206             }
207             return reader.readLine();
208         }
209         catch (final IOException e) {
210             // Theoretically impossible
211             LOG.error("Reading scriptSourceCode failed.", e);
212         }
213 
214         return "";
215     }
216 
217     /**
218      * Returns the line number of the source that was executing at the time of the exception.
219      *
220      * @return the line number or -1 if the exception was not thrown due to the
221      *         execution of a script.
222      */
223     public int getFailingLineNumber() {
224         if (getCause() instanceof RhinoException) {
225             final RhinoException cause = (RhinoException) getCause();
226             return cause.lineNumber();
227         }
228 
229         return -1;
230     }
231 
232     /**
233      * Returns the column number of the source that was executing at the time of the exception.
234      *
235      * @return the column number or -1 if the exception was not thrown due to the
236      *         execution of a script.
237      */
238     public int getFailingColumnNumber() {
239         if (getCause() instanceof RhinoException) {
240             final RhinoException cause = (RhinoException) getCause();
241             return cause.columnNumber();
242         }
243 
244         return -1;
245     }
246 
247     /**
248      * Gets the HTML page in which the script error occurred.<br>
249      * Caution: this page may be only partially parsed if the exception occurred in a script
250      * executed at parsing time.
251      * @return the page
252      */
253     public HtmlPage getPage() {
254         return page_;
255     }
256 
257     /**
258      * Prints the script stack trace.
259      * This represents only the script calls.
260      * @param writer where the stack trace will be written
261      */
262     public void printScriptStackTrace(final PrintWriter writer) {
263         final StringWriter stringWriter = new StringWriter();
264         final PrintWriter printWriter = new PrintWriter(stringWriter);
265 
266         getCause().printStackTrace(printWriter);
267 
268         writer.print(getCause().getMessage());
269         final StringTokenizer st = new StringTokenizer(stringWriter.toString(), "\r\n");
270         while (st.hasMoreTokens()) {
271             final String line = st.nextToken();
272             if (line.contains("at script")) {
273                 writer.println();
274                 writer.print(line.replaceFirst("at script\\.?", "at "));
275             }
276         }
277     }
278 
279 }