1 | /* |
2 | * Copyright (C) 2018 Igalia S.L. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Library General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Library General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Library General Public License |
15 | * along with this library; see the file COPYING.LIB. If not, write to |
16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 | * Boston, MA 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "config.h" |
21 | #include "JSCException.h" |
22 | |
23 | #include "APICast.h" |
24 | #include "JSCContextPrivate.h" |
25 | #include "JSCExceptionPrivate.h" |
26 | #include "JSCInlines.h" |
27 | #include "JSRetainPtr.h" |
28 | #include "StrongInlines.h" |
29 | #include <glib/gprintf.h> |
30 | #include <wtf/glib/GUniquePtr.h> |
31 | #include <wtf/glib/WTFGType.h> |
32 | |
33 | /** |
34 | * SECTION: JSCException |
35 | * @short_description: JavaScript exception |
36 | * @title: JSCException |
37 | * @see_also: JSCContext |
38 | * |
39 | * JSCException represents a JavaScript exception. |
40 | */ |
41 | |
42 | struct _JSCExceptionPrivate { |
43 | JSCContext* context; |
44 | JSC::Strong<JSC::JSObject> jsException; |
45 | bool cached; |
46 | GUniquePtr<char> errorName; |
47 | GUniquePtr<char> message; |
48 | unsigned lineNumber; |
49 | unsigned columnNumber; |
50 | GUniquePtr<char> sourceURI; |
51 | GUniquePtr<char> backtrace; |
52 | }; |
53 | |
54 | WEBKIT_DEFINE_TYPE(JSCException, jsc_exception, G_TYPE_OBJECT) |
55 | |
56 | static void jscExceptionDispose(GObject* object) |
57 | { |
58 | JSCExceptionPrivate* priv = JSC_EXCEPTION(object)->priv; |
59 | if (priv->context) { |
60 | g_object_remove_weak_pointer(G_OBJECT(priv->context), reinterpret_cast<void**>(&priv->context)); |
61 | priv->context = nullptr; |
62 | } |
63 | |
64 | G_OBJECT_CLASS(jsc_exception_parent_class)->dispose(object); |
65 | } |
66 | |
67 | static void jsc_exception_class_init(JSCExceptionClass* klass) |
68 | { |
69 | GObjectClass* objClass = G_OBJECT_CLASS(klass); |
70 | objClass->dispose = jscExceptionDispose; |
71 | } |
72 | |
73 | GRefPtr<JSCException> jscExceptionCreate(JSCContext* context, JSValueRef jsException) |
74 | { |
75 | GRefPtr<JSCException> exception = adoptGRef(JSC_EXCEPTION(g_object_new(JSC_TYPE_EXCEPTION, nullptr))); |
76 | auto* jsContext = jscContextGetJSContext(context); |
77 | JSC::ExecState* exec = toJS(jsContext); |
78 | JSC::VM& vm = exec->vm(); |
79 | JSC::JSLockHolder locker(vm); |
80 | exception->priv->jsException.set(vm, toJS(JSValueToObject(jsContext, jsException, nullptr))); |
81 | // The context has a strong reference to the exception, so we can't ref the context. We use a weak |
82 | // pointer instead to invalidate the exception if the context is destroyed before. |
83 | exception->priv->context = context; |
84 | g_object_add_weak_pointer(G_OBJECT(context), reinterpret_cast<void**>(&exception->priv->context)); |
85 | return exception; |
86 | } |
87 | |
88 | JSValueRef jscExceptionGetJSValue(JSCException* exception) |
89 | { |
90 | return toRef(exception->priv->jsException.get()); |
91 | } |
92 | |
93 | void jscExceptionEnsureProperties(JSCException* exception) |
94 | { |
95 | JSCExceptionPrivate* priv = exception->priv; |
96 | if (priv->cached) |
97 | return; |
98 | |
99 | priv->cached = true; |
100 | |
101 | auto value = jscContextGetOrCreateValue(priv->context, toRef(priv->jsException.get())); |
102 | auto propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "name" )); |
103 | if (!jsc_value_is_undefined(propertyValue.get())) |
104 | priv->errorName.reset(jsc_value_to_string(propertyValue.get())); |
105 | propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "message" )); |
106 | if (!jsc_value_is_undefined(propertyValue.get())) |
107 | priv->message.reset(jsc_value_to_string(propertyValue.get())); |
108 | propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "line" )); |
109 | if (!jsc_value_is_undefined(propertyValue.get())) |
110 | priv->lineNumber = jsc_value_to_int32(propertyValue.get()); |
111 | propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "column" )); |
112 | if (!jsc_value_is_undefined(propertyValue.get())) |
113 | priv->columnNumber = jsc_value_to_int32(propertyValue.get()); |
114 | propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "sourceURL" )); |
115 | if (!jsc_value_is_undefined(propertyValue.get())) |
116 | priv->sourceURI.reset(jsc_value_to_string(propertyValue.get())); |
117 | propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "stack" )); |
118 | if (!jsc_value_is_undefined(propertyValue.get())) |
119 | priv->backtrace.reset(jsc_value_to_string(propertyValue.get())); |
120 | } |
121 | |
122 | /** |
123 | * jsc_exception_new: |
124 | * @context: a #JSCContext |
125 | * @message: the error message |
126 | * |
127 | * Create a new #JSCException in @context with @message. |
128 | * |
129 | * Returns: (transfer full): a new #JSCException. |
130 | */ |
131 | JSCException* jsc_exception_new(JSCContext* context, const char* message) |
132 | { |
133 | return jsc_exception_new_with_name(context, nullptr, message); |
134 | } |
135 | |
136 | /** |
137 | * jsc_exception_new_printf: |
138 | * @context: a #JSCContext |
139 | * @format: the string format |
140 | * @...: the parameters to insert into the format string |
141 | * |
142 | * Create a new #JSCException in @context using a formatted string |
143 | * for the message. |
144 | * |
145 | * Returns: (transfer full): a new #JSCException. |
146 | */ |
147 | JSCException* jsc_exception_new_printf(JSCContext* context, const char* format, ...) |
148 | { |
149 | va_list args; |
150 | va_start(args, format); |
151 | auto* exception = jsc_exception_new_vprintf(context, format, args); |
152 | va_end(args); |
153 | |
154 | return exception; |
155 | } |
156 | |
157 | /** |
158 | * jsc_exception_new_vprintf: |
159 | * @context: a #JSCContext |
160 | * @format: the string format |
161 | * @args: the parameters to insert into the format string |
162 | * |
163 | * Create a new #JSCException in @context using a formatted string |
164 | * for the message. This is similar to jsc_exception_new_printf() |
165 | * except that the arguments to the format string are passed as a va_list. |
166 | * |
167 | * Returns: (transfer full): a new #JSCException. |
168 | */ |
169 | JSCException* jsc_exception_new_vprintf(JSCContext* context, const char* format, va_list args) |
170 | { |
171 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
172 | |
173 | GUniqueOutPtr<char> buffer; |
174 | g_vasprintf(&buffer.outPtr(), format, args); |
175 | return jsc_exception_new(context, buffer.get()); |
176 | } |
177 | |
178 | /** |
179 | * jsc_exception_new_with_name: |
180 | * @context: a #JSCContext |
181 | * @name: the error name |
182 | * @message: the error message |
183 | * |
184 | * Create a new #JSCException in @context with @name and @message. |
185 | * |
186 | * Returns: (transfer full): a new #JSCException. |
187 | */ |
188 | JSCException* jsc_exception_new_with_name(JSCContext* context, const char* name, const char* message) |
189 | { |
190 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
191 | |
192 | auto* jsContext = jscContextGetJSContext(context); |
193 | JSValueRef jsMessage = nullptr; |
194 | if (message) { |
195 | JSRetainPtr<JSStringRef> jsMessageString(Adopt, JSStringCreateWithUTF8CString(message)); |
196 | jsMessage = JSValueMakeString(jsContext, jsMessageString.get()); |
197 | } |
198 | |
199 | auto exception = jscExceptionCreate(context, JSObjectMakeError(jsContext, jsMessage ? 1 : 0, &jsMessage, nullptr)); |
200 | if (name) { |
201 | auto value = jscContextGetOrCreateValue(context, toRef(exception->priv->jsException.get())); |
202 | GRefPtr<JSCValue> nameValue = adoptGRef(jsc_value_new_string(context, name)); |
203 | jsc_value_object_set_property(value.get(), "name" , nameValue.get()); |
204 | } |
205 | |
206 | return exception.leakRef(); |
207 | } |
208 | |
209 | /** |
210 | * jsc_exception_new_with_name_printf: |
211 | * @context: a #JSCContext |
212 | * @name: the error name |
213 | * @format: the string format |
214 | * @...: the parameters to insert into the format string |
215 | * |
216 | * Create a new #JSCException in @context with @name and using a formatted string |
217 | * for the message. |
218 | * |
219 | * Returns: (transfer full): a new #JSCException. |
220 | */ |
221 | JSCException* jsc_exception_new_with_name_printf(JSCContext* context, const char* name, const char* format, ...) |
222 | { |
223 | va_list args; |
224 | va_start(args, format); |
225 | auto* exception = jsc_exception_new_with_name_vprintf(context, name, format, args); |
226 | va_end(args); |
227 | |
228 | return exception; |
229 | } |
230 | |
231 | /** |
232 | * jsc_exception_new_with_name_vprintf: |
233 | * @context: a #JSCContext |
234 | * @name: the error name |
235 | * @format: the string format |
236 | * @args: the parameters to insert into the format string |
237 | * |
238 | * Create a new #JSCException in @context with @name and using a formatted string |
239 | * for the message. This is similar to jsc_exception_new_with_name_printf() |
240 | * except that the arguments to the format string are passed as a va_list. |
241 | * |
242 | * Returns: (transfer full): a new #JSCException. |
243 | */ |
244 | JSCException* jsc_exception_new_with_name_vprintf(JSCContext* context, const char* name, const char* format, va_list args) |
245 | { |
246 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
247 | |
248 | GUniqueOutPtr<char> buffer; |
249 | g_vasprintf(&buffer.outPtr(), format, args); |
250 | return jsc_exception_new_with_name(context, name, buffer.get()); |
251 | } |
252 | |
253 | /** |
254 | * jsc_exception_get_name: |
255 | * @exception: a #JSCException |
256 | * |
257 | * Get the error name of @exception |
258 | * |
259 | * Returns: the @exception error name. |
260 | */ |
261 | const char* jsc_exception_get_name(JSCException* exception) |
262 | { |
263 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); |
264 | |
265 | JSCExceptionPrivate* priv = exception->priv; |
266 | g_return_val_if_fail(priv->context, nullptr); |
267 | |
268 | jscExceptionEnsureProperties(exception); |
269 | return priv->errorName.get(); |
270 | } |
271 | |
272 | /** |
273 | * jsc_exception_get_message: |
274 | * @exception: a #JSCException |
275 | * |
276 | * Get the error message of @exception. |
277 | * |
278 | * Returns: the @exception error message. |
279 | */ |
280 | const char* jsc_exception_get_message(JSCException* exception) |
281 | { |
282 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); |
283 | |
284 | JSCExceptionPrivate* priv = exception->priv; |
285 | g_return_val_if_fail(priv->context, nullptr); |
286 | |
287 | jscExceptionEnsureProperties(exception); |
288 | return priv->message.get(); |
289 | } |
290 | |
291 | /** |
292 | * jsc_exception_get_line_number: |
293 | * @exception: a #JSCException |
294 | * |
295 | * Get the line number at which @exception happened. |
296 | * |
297 | * Returns: the line number of @exception. |
298 | */ |
299 | guint jsc_exception_get_line_number(JSCException* exception) |
300 | { |
301 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), 0); |
302 | |
303 | JSCExceptionPrivate* priv = exception->priv; |
304 | g_return_val_if_fail(priv->context, 0); |
305 | |
306 | jscExceptionEnsureProperties(exception); |
307 | return priv->lineNumber; |
308 | } |
309 | |
310 | /** |
311 | * jsc_exception_get_column_number: |
312 | * @exception: a #JSCException |
313 | * |
314 | * Get the column number at which @exception happened. |
315 | * |
316 | * Returns: the column number of @exception. |
317 | */ |
318 | guint jsc_exception_get_column_number(JSCException* exception) |
319 | { |
320 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), 0); |
321 | |
322 | JSCExceptionPrivate* priv = exception->priv; |
323 | g_return_val_if_fail(priv->context, 0); |
324 | |
325 | jscExceptionEnsureProperties(exception); |
326 | return priv->columnNumber; |
327 | } |
328 | |
329 | /** |
330 | * jsc_exception_get_source_uri: |
331 | * @exception: a #JSCException |
332 | * |
333 | * Get the source URI of @exception. |
334 | * |
335 | * Returns: (nullable): the the source URI of @exception, or %NULL. |
336 | */ |
337 | const char* jsc_exception_get_source_uri(JSCException* exception) |
338 | { |
339 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); |
340 | |
341 | JSCExceptionPrivate* priv = exception->priv; |
342 | g_return_val_if_fail(priv->context, nullptr); |
343 | |
344 | jscExceptionEnsureProperties(exception); |
345 | return priv->sourceURI.get(); |
346 | } |
347 | |
348 | /** |
349 | * jsc_exception_get_backtrace_string: |
350 | * @exception: a #JSCException |
351 | * |
352 | * Get a string with the exception backtrace. |
353 | * |
354 | * Returns: (nullable): the exception backtrace string or %NULL. |
355 | */ |
356 | const char* jsc_exception_get_backtrace_string(JSCException* exception) |
357 | { |
358 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); |
359 | |
360 | JSCExceptionPrivate* priv = exception->priv; |
361 | g_return_val_if_fail(priv->context, nullptr); |
362 | |
363 | jscExceptionEnsureProperties(exception); |
364 | return priv->backtrace.get(); |
365 | } |
366 | |
367 | /** |
368 | * jsc_exception_to_string: |
369 | * @exception: a #JSCException |
370 | * |
371 | * Get the string representation of @exception error. |
372 | * |
373 | * Returns: (transfer full): the string representation of @exception. |
374 | */ |
375 | char* jsc_exception_to_string(JSCException* exception) |
376 | { |
377 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); |
378 | |
379 | JSCExceptionPrivate* priv = exception->priv; |
380 | g_return_val_if_fail(priv->context, nullptr); |
381 | |
382 | auto value = jscContextGetOrCreateValue(priv->context, toRef(priv->jsException.get())); |
383 | return jsc_value_to_string(value.get()); |
384 | } |
385 | |
386 | /** |
387 | * jsc_exception_report: |
388 | * @exception: a #JSCException |
389 | * |
390 | * Return a report message of @exception, containing all the possible details such us |
391 | * source URI, line, column and backtrace, and formatted to be printed. |
392 | * |
393 | * Returns: (transfer full): a new string with the exception report |
394 | */ |
395 | char* jsc_exception_report(JSCException* exception) |
396 | { |
397 | g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); |
398 | |
399 | JSCExceptionPrivate* priv = exception->priv; |
400 | g_return_val_if_fail(priv->context, nullptr); |
401 | |
402 | jscExceptionEnsureProperties(exception); |
403 | GString* report = g_string_new(nullptr); |
404 | if (priv->sourceURI) |
405 | report = g_string_append(report, priv->sourceURI.get()); |
406 | if (priv->lineNumber) |
407 | g_string_append_printf(report, ":%d" , priv->lineNumber); |
408 | if (priv->columnNumber) |
409 | g_string_append_printf(report, ":%d" , priv->columnNumber); |
410 | report = g_string_append_c(report, ' '); |
411 | GUniquePtr<char> errorMessage(jsc_exception_to_string(exception)); |
412 | if (errorMessage) |
413 | report = g_string_append(report, errorMessage.get()); |
414 | report = g_string_append_c(report, '\n'); |
415 | |
416 | if (priv->backtrace) { |
417 | GUniquePtr<char*> lines(g_strsplit(priv->backtrace.get(), "\n" , 0)); |
418 | for (unsigned i = 0; lines.get()[i]; ++i) |
419 | g_string_append_printf(report, " %s\n" , lines.get()[i]); |
420 | } |
421 | |
422 | return g_string_free(report, FALSE); |
423 | } |
424 | |