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
42struct _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
54WEBKIT_DEFINE_TYPE(JSCException, jsc_exception, G_TYPE_OBJECT)
55
56static 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
67static void jsc_exception_class_init(JSCExceptionClass* klass)
68{
69 GObjectClass* objClass = G_OBJECT_CLASS(klass);
70 objClass->dispose = jscExceptionDispose;
71}
72
73GRefPtr<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
88JSValueRef jscExceptionGetJSValue(JSCException* exception)
89{
90 return toRef(exception->priv->jsException.get());
91}
92
93void 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 */
131JSCException* 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 */
147JSCException* 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 */
169JSCException* 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 */
188JSCException* 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 */
221JSCException* 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 */
244JSCException* 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 */
261const 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 */
280const 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 */
299guint 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 */
318guint 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 */
337const 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 */
356const 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 */
375char* 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 */
395char* 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