/* * Copyright (C) 2018 Igalia S.L. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "JSCException.h" #include "APICast.h" #include "JSCContextPrivate.h" #include "JSCExceptionPrivate.h" #include "JSCInlines.h" #include "JSRetainPtr.h" #include "StrongInlines.h" #include #include #include /** * SECTION: JSCException * @short_description: JavaScript exception * @title: JSCException * @see_also: JSCContext * * JSCException represents a JavaScript exception. */ struct _JSCExceptionPrivate { JSCContext* context; JSC::Strong jsException; bool cached; GUniquePtr errorName; GUniquePtr message; unsigned lineNumber; unsigned columnNumber; GUniquePtr sourceURI; GUniquePtr backtrace; }; WEBKIT_DEFINE_TYPE(JSCException, jsc_exception, G_TYPE_OBJECT) static void jscExceptionDispose(GObject* object) { JSCExceptionPrivate* priv = JSC_EXCEPTION(object)->priv; if (priv->context) { g_object_remove_weak_pointer(G_OBJECT(priv->context), reinterpret_cast(&priv->context)); priv->context = nullptr; } G_OBJECT_CLASS(jsc_exception_parent_class)->dispose(object); } static void jsc_exception_class_init(JSCExceptionClass* klass) { GObjectClass* objClass = G_OBJECT_CLASS(klass); objClass->dispose = jscExceptionDispose; } GRefPtr jscExceptionCreate(JSCContext* context, JSValueRef jsException) { GRefPtr exception = adoptGRef(JSC_EXCEPTION(g_object_new(JSC_TYPE_EXCEPTION, nullptr))); auto* jsContext = jscContextGetJSContext(context); JSC::JSGlobalObject* globalObject = toJS(jsContext); JSC::VM& vm = globalObject->vm(); JSC::JSLockHolder locker(vm); exception->priv->jsException.set(vm, toJS(JSValueToObject(jsContext, jsException, nullptr))); // The context has a strong reference to the exception, so we can't ref the context. We use a weak // pointer instead to invalidate the exception if the context is destroyed before. exception->priv->context = context; g_object_add_weak_pointer(G_OBJECT(context), reinterpret_cast(&exception->priv->context)); return exception; } JSValueRef jscExceptionGetJSValue(JSCException* exception) { return toRef(exception->priv->jsException.get()); } void jscExceptionEnsureProperties(JSCException* exception) { JSCExceptionPrivate* priv = exception->priv; if (priv->cached) return; priv->cached = true; auto value = jscContextGetOrCreateValue(priv->context, toRef(priv->jsException.get())); auto propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "name")); if (!jsc_value_is_undefined(propertyValue.get())) priv->errorName.reset(jsc_value_to_string(propertyValue.get())); propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "message")); if (!jsc_value_is_undefined(propertyValue.get())) priv->message.reset(jsc_value_to_string(propertyValue.get())); propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "line")); if (!jsc_value_is_undefined(propertyValue.get())) priv->lineNumber = jsc_value_to_int32(propertyValue.get()); propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "column")); if (!jsc_value_is_undefined(propertyValue.get())) priv->columnNumber = jsc_value_to_int32(propertyValue.get()); propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "sourceURL")); if (!jsc_value_is_undefined(propertyValue.get())) priv->sourceURI.reset(jsc_value_to_string(propertyValue.get())); propertyValue = adoptGRef(jsc_value_object_get_property(value.get(), "stack")); if (!jsc_value_is_undefined(propertyValue.get())) priv->backtrace.reset(jsc_value_to_string(propertyValue.get())); } /** * jsc_exception_new: * @context: a #JSCContext * @message: the error message * * Create a new #JSCException in @context with @message. * * Returns: (transfer full): a new #JSCException. */ JSCException* jsc_exception_new(JSCContext* context, const char* message) { return jsc_exception_new_with_name(context, nullptr, message); } /** * jsc_exception_new_printf: * @context: a #JSCContext * @format: the string format * @...: the parameters to insert into the format string * * Create a new #JSCException in @context using a formatted string * for the message. * * Returns: (transfer full): a new #JSCException. */ JSCException* jsc_exception_new_printf(JSCContext* context, const char* format, ...) { va_list args; va_start(args, format); auto* exception = jsc_exception_new_vprintf(context, format, args); va_end(args); return exception; } /** * jsc_exception_new_vprintf: * @context: a #JSCContext * @format: the string format * @args: the parameters to insert into the format string * * Create a new #JSCException in @context using a formatted string * for the message. This is similar to jsc_exception_new_printf() * except that the arguments to the format string are passed as a va_list. * * Returns: (transfer full): a new #JSCException. */ JSCException* jsc_exception_new_vprintf(JSCContext* context, const char* format, va_list args) { g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); GUniqueOutPtr buffer; g_vasprintf(&buffer.outPtr(), format, args); return jsc_exception_new(context, buffer.get()); } /** * jsc_exception_new_with_name: * @context: a #JSCContext * @name: the error name * @message: the error message * * Create a new #JSCException in @context with @name and @message. * * Returns: (transfer full): a new #JSCException. */ JSCException* jsc_exception_new_with_name(JSCContext* context, const char* name, const char* message) { g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); auto* jsContext = jscContextGetJSContext(context); JSValueRef jsMessage = nullptr; if (message) { JSRetainPtr jsMessageString(Adopt, JSStringCreateWithUTF8CString(message)); jsMessage = JSValueMakeString(jsContext, jsMessageString.get()); } auto exception = jscExceptionCreate(context, JSObjectMakeError(jsContext, jsMessage ? 1 : 0, &jsMessage, nullptr)); if (name) { auto value = jscContextGetOrCreateValue(context, toRef(exception->priv->jsException.get())); GRefPtr nameValue = adoptGRef(jsc_value_new_string(context, name)); jsc_value_object_set_property(value.get(), "name", nameValue.get()); } return exception.leakRef(); } /** * jsc_exception_new_with_name_printf: * @context: a #JSCContext * @name: the error name * @format: the string format * @...: the parameters to insert into the format string * * Create a new #JSCException in @context with @name and using a formatted string * for the message. * * Returns: (transfer full): a new #JSCException. */ JSCException* jsc_exception_new_with_name_printf(JSCContext* context, const char* name, const char* format, ...) { va_list args; va_start(args, format); auto* exception = jsc_exception_new_with_name_vprintf(context, name, format, args); va_end(args); return exception; } /** * jsc_exception_new_with_name_vprintf: * @context: a #JSCContext * @name: the error name * @format: the string format * @args: the parameters to insert into the format string * * Create a new #JSCException in @context with @name and using a formatted string * for the message. This is similar to jsc_exception_new_with_name_printf() * except that the arguments to the format string are passed as a va_list. * * Returns: (transfer full): a new #JSCException. */ JSCException* jsc_exception_new_with_name_vprintf(JSCContext* context, const char* name, const char* format, va_list args) { g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); GUniqueOutPtr buffer; g_vasprintf(&buffer.outPtr(), format, args); return jsc_exception_new_with_name(context, name, buffer.get()); } /** * jsc_exception_get_name: * @exception: a #JSCException * * Get the error name of @exception * * Returns: the @exception error name. */ const char* jsc_exception_get_name(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, nullptr); jscExceptionEnsureProperties(exception); return priv->errorName.get(); } /** * jsc_exception_get_message: * @exception: a #JSCException * * Get the error message of @exception. * * Returns: the @exception error message. */ const char* jsc_exception_get_message(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, nullptr); jscExceptionEnsureProperties(exception); return priv->message.get(); } /** * jsc_exception_get_line_number: * @exception: a #JSCException * * Get the line number at which @exception happened. * * Returns: the line number of @exception. */ guint jsc_exception_get_line_number(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), 0); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, 0); jscExceptionEnsureProperties(exception); return priv->lineNumber; } /** * jsc_exception_get_column_number: * @exception: a #JSCException * * Get the column number at which @exception happened. * * Returns: the column number of @exception. */ guint jsc_exception_get_column_number(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), 0); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, 0); jscExceptionEnsureProperties(exception); return priv->columnNumber; } /** * jsc_exception_get_source_uri: * @exception: a #JSCException * * Get the source URI of @exception. * * Returns: (nullable): the the source URI of @exception, or %NULL. */ const char* jsc_exception_get_source_uri(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, nullptr); jscExceptionEnsureProperties(exception); return priv->sourceURI.get(); } /** * jsc_exception_get_backtrace_string: * @exception: a #JSCException * * Get a string with the exception backtrace. * * Returns: (nullable): the exception backtrace string or %NULL. */ const char* jsc_exception_get_backtrace_string(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, nullptr); jscExceptionEnsureProperties(exception); return priv->backtrace.get(); } /** * jsc_exception_to_string: * @exception: a #JSCException * * Get the string representation of @exception error. * * Returns: (transfer full): the string representation of @exception. */ char* jsc_exception_to_string(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, nullptr); auto value = jscContextGetOrCreateValue(priv->context, toRef(priv->jsException.get())); return jsc_value_to_string(value.get()); } /** * jsc_exception_report: * @exception: a #JSCException * * Return a report message of @exception, containing all the possible details such us * source URI, line, column and backtrace, and formatted to be printed. * * Returns: (transfer full): a new string with the exception report */ char* jsc_exception_report(JSCException* exception) { g_return_val_if_fail(JSC_IS_EXCEPTION(exception), nullptr); JSCExceptionPrivate* priv = exception->priv; g_return_val_if_fail(priv->context, nullptr); jscExceptionEnsureProperties(exception); GString* report = g_string_new(nullptr); if (priv->sourceURI) report = g_string_append(report, priv->sourceURI.get()); if (priv->lineNumber) g_string_append_printf(report, ":%d", priv->lineNumber); if (priv->columnNumber) g_string_append_printf(report, ":%d", priv->columnNumber); report = g_string_append_c(report, ' '); GUniquePtr errorMessage(jsc_exception_to_string(exception)); if (errorMessage) report = g_string_append(report, errorMessage.get()); report = g_string_append_c(report, '\n'); if (priv->backtrace) { GUniquePtr lines(g_strsplit(priv->backtrace.get(), "\n", 0)); for (unsigned i = 0; lines.get()[i]; ++i) g_string_append_printf(report, " %s\n", lines.get()[i]); } return g_string_free(report, FALSE); }