blob: 0d667faa1d8f8409b21ab0b20cf1460324d2891d [file] [log] [blame]
/*
* 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2,1 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 JSCContextPrivate.h to be able to run garbage collector for testing.
#define JSC_COMPILATION 1
#include "jsc/JSCContextPrivate.h"
#undef JSC_COMPILATION
#include <jsc/jsc.h>
#include <wtf/HashSet.h>
#include <wtf/Threading.h>
#include <wtf/Vector.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/GUniquePtr.h>
class LeakChecker {
public:
LeakChecker() = default;
~LeakChecker()
{
if (m_watchedObjects.isEmpty())
return;
g_print("Leaked objects:");
for (auto* object : m_watchedObjects)
g_print(" [%s(%p) %u refs]", g_type_name_from_instance(reinterpret_cast<GTypeInstance*>(object)), object, G_OBJECT(object)->ref_count);
g_print("\n");
g_assert_true(m_watchedObjects.isEmpty());
}
void watch(void* object)
{
g_assert_true(G_IS_OBJECT(object));
m_watchedObjects.add(object);
g_object_weak_ref(G_OBJECT(object), [](gpointer userData, GObject* object) {
static_cast<LeakChecker*>(userData)->m_watchedObjects.remove(object);
}, this);
}
private:
HashSet<void*> m_watchedObjects;
};
class ExceptionHandler {
public:
ExceptionHandler(JSCContext* context)
: m_context(context)
{
jsc_context_push_exception_handler(m_context, [](JSCContext*, JSCException* exception, gpointer) {
g_error("UNEXPECTED EXCEPTION: %s", jsc_exception_get_message(exception));
}, nullptr, nullptr);
}
~ExceptionHandler()
{
pop();
}
void push(JSCExceptionHandler handler, gpointer userData, GDestroyNotify destroyFunction = nullptr)
{
jsc_context_push_exception_handler(m_context, handler, userData, destroyFunction);
}
void pop()
{
jsc_context_pop_exception_handler(m_context);
}
private:
JSCContext* m_context;
};
#define g_assert_throw_begin(handler, didThrow) \
didThrow = false; \
handler.push([](JSCContext*, JSCException*, gpointer userData) { \
*static_cast<bool*>(userData) = true; \
}, &didThrow, nullptr);
#define g_assert_did_throw(handler, didThrow) \
handler.pop(); \
g_assert_true(didThrow); \
didThrow = false;
extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
static void jscContextGarbageCollect(JSCContext* context)
{
JSSynchronousGarbageCollectForDebugging(jscContextGetJSContext(context));
}
static void testJSCBasic()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
auto* defaultVM = jsc_context_get_virtual_machine(context.get());
g_assert_nonnull(defaultVM);
checker.watch(defaultVM);
GRefPtr<JSCVirtualMachine> vm = adoptGRef(jsc_virtual_machine_new());
checker.watch(vm.get());
g_assert_false(vm.get() == defaultVM);
GRefPtr<JSCContext> context2 = adoptGRef(jsc_context_new_with_virtual_machine(vm.get()));
checker.watch(context2.get());
ExceptionHandler exceptionHandler2(context.get());
g_assert_true(jsc_context_get_virtual_machine(context2.get()) == vm.get());
GRefPtr<JSCContext> context3 = adoptGRef(jsc_context_new_with_virtual_machine(defaultVM));
checker.watch(context3.get());
ExceptionHandler exceptionHandler3(context.get());
g_assert_true(jsc_context_get_virtual_machine(context3.get()) == jsc_context_get_virtual_machine(context.get()));
GRefPtr<JSCValue> value1 = adoptGRef(jsc_value_new_number(context.get(), 25));
checker.watch(value1.get());
g_assert_true(jsc_value_get_context(value1.get()) == context.get());
g_assert_true(jsc_value_is_number(value1.get()));
g_assert_cmpint(jsc_value_to_int32(value1.get()), ==, 25);
jsc_context_set_value(context.get(), "value1", value1.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "value1"));
checker.watch(result.get());
g_assert_true(result.get() == value1.get());
result = adoptGRef(jsc_context_evaluate(context2.get(), "value1"));
checker.watch(result.get());
g_assert_true(jsc_value_get_context(result.get()) == context2.get());
g_assert_true(jsc_value_is_undefined(result.get()));
result = adoptGRef(jsc_context_get_value(context3.get(), "value1"));
checker.watch(result.get());
g_assert_true(jsc_value_get_context(result.get()) == context3.get());
g_assert_true(jsc_value_is_undefined(result.get()));
jsc_context_set_value(context3.get(), "value1", value1.get());
result = adoptGRef(jsc_context_evaluate(context3.get(), "value1"));
checker.watch(result.get());
g_assert_true(jsc_value_get_context(result.get()) == context3.get());
g_assert_false(result.get() == value1.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 25);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "2 + 2"));
checker.watch(result.get());
g_assert_true(jsc_value_get_context(result.get()) == context.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 4);
GRefPtr<JSCValue> result2 = adoptGRef(jsc_context_evaluate(context.get(), "2 + 2"));
checker.watch(result2.get());
g_assert_true(jsc_value_get_context(result2.get()) == context.get());
g_assert_true(result.get() == result2.get());
GRefPtr<JSCValue> result3 = adoptGRef(jsc_context_evaluate(context.get(), "3 + 1"));
checker.watch(result3.get());
g_assert_true(jsc_value_get_context(result3.get()) == context.get());
g_assert_true(result.get() == result3.get());
auto* resultPtr = result.get();
result = nullptr;
result2 = nullptr;
result3 = nullptr;
GRefPtr<JSCValue> result4 = adoptGRef(jsc_context_evaluate(context.get(), "2 + 2"));
checker.watch(result4.get());
g_assert_true(jsc_value_get_context(result4.get()) == context.get());
g_assert_true(jsc_value_is_number(result4.get()));
g_assert_cmpint(jsc_value_to_int32(result4.get()), ==, 4);
g_assert_false(result4.get() == resultPtr);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "2 + 2"));
checker.watch(result.get());
g_assert_true(jsc_value_get_context(result.get()) == context.get());
jsc_context_set_value(context.get(), "four", result.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_get_value(context.get(), "four"));
checker.watch(value.get());
g_assert_true(jsc_value_get_context(value.get()) == context.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 4);
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "four"));
checker.watch(value.get());
g_assert_true(result.get() == value.get());
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "two = 2; four = two + two"));
checker.watch(result.get());
g_assert_true(jsc_value_get_context(result.get()) == context.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 4);
GRefPtr<JSCValue> value = adoptGRef(jsc_context_get_value(context.get(), "two"));
checker.watch(value.get());
g_assert_true(jsc_value_get_context(value.get()) == context.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 2);
value = adoptGRef(jsc_context_get_value(context.get(), "four"));
checker.watch(value.get());
g_assert_true(result.get() == value.get());
GRefPtr<JSCValue> result2 = adoptGRef(jsc_context_evaluate(context.get(), "five = 4"));
checker.watch(result2.get());
g_assert_true(result2.get() == value.get());
}
}
static void testJSCTypes()
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_value_new_number(context.get(), 125));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpfloat(jsc_value_to_double(value.get()), ==, 125.);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 125);
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
GUniquePtr<char> valueString(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "125");
jsc_context_set_value(context.get(), "i", value.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_get_value(context.get(), "i"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_number(context.get(), 12.5));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpfloat(jsc_value_to_double(value.get()), ==, 12.5);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 12);
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "12.5");
jsc_context_set_value(context.get(), "d", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "d"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_boolean(context.get(), TRUE));
checker.watch(value.get());
g_assert_true(jsc_value_is_boolean(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 1);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "true");
jsc_context_set_value(context.get(), "b1", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "b1"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_boolean(context.get(), FALSE));
checker.watch(value.get());
g_assert_true(jsc_value_is_boolean(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == FALSE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "false");
jsc_context_set_value(context.get(), "b2", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "b2"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_string(context.get(), "Hello World"));
checker.watch(value.get());
g_assert_true(jsc_value_is_string(value.get()));
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "Hello World");
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
jsc_context_set_value(context.get(), "s1", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "s1"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_string(context.get(), nullptr));
checker.watch(value.get());
g_assert_true(jsc_value_is_string(value.get()));
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "");
g_assert_true(jsc_value_to_boolean(value.get()) == FALSE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
jsc_context_set_value(context.get(), "s2", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "s2"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_string(context.get(), "12.5"));
checker.watch(value.get());
g_assert_true(jsc_value_is_string(value.get()));
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "12.5");
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
g_assert_cmpfloat(jsc_value_to_double(value.get()), ==, 12.5);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 12);
jsc_context_set_value(context.get(), "s3", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "s3"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_array(context.get(), G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(jsc_value_is_array(value.get()));
g_assert_true(jsc_value_is_object(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "");
jsc_context_set_value(context.get(), "a1", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "a1"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
GRefPtr<JSCValue> arrayLength = adoptGRef(jsc_value_object_get_property(value.get(), "length"));
checker.watch(arrayLength.get());
g_assert_true(jsc_value_is_number(arrayLength.get()));
g_assert_cmpint(jsc_value_to_int32(arrayLength.get()), ==, 0);
GRefPtr<JSCValue> arrayItem = adoptGRef(jsc_value_new_number(context.get(), 1));
checker.watch(arrayItem.get());
jsc_value_object_set_property_at_index(value.get(), jsc_value_to_int32(arrayLength.get()), arrayItem.get());
arrayLength = adoptGRef(jsc_value_object_get_property(value.get(), "length"));
checker.watch(arrayLength.get());
g_assert_true(jsc_value_is_number(arrayLength.get()));
g_assert_cmpint(jsc_value_to_int32(arrayLength.get()), ==, 1);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "1");
arrayItem = adoptGRef(jsc_value_new_number(context.get(), 5));
checker.watch(arrayItem.get());
jsc_value_object_set_property_at_index(value.get(), jsc_value_to_int32(arrayLength.get()), arrayItem.get());
arrayLength = adoptGRef(jsc_value_object_get_property(value.get(), "length"));
checker.watch(arrayLength.get());
g_assert_true(jsc_value_is_number(arrayLength.get()));
g_assert_cmpint(jsc_value_to_int32(arrayLength.get()), ==, 2);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "1,5");
arrayItem = adoptGRef(jsc_value_new_number(context.get(), 10));
checker.watch(arrayItem.get());
jsc_value_object_set_property_at_index(value.get(), 3, arrayItem.get());
arrayLength = adoptGRef(jsc_value_object_get_property(value.get(), "length"));
checker.watch(arrayLength.get());
g_assert_true(jsc_value_is_number(arrayLength.get()));
g_assert_cmpint(jsc_value_to_int32(arrayLength.get()), ==, 4);
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(value.get(), 0));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_number(arrayItem.get()));
g_assert_cmpint(jsc_value_to_int32(arrayItem.get()), ==, 1);
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(value.get(), 1));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_number(arrayItem.get()));
g_assert_cmpint(jsc_value_to_int32(arrayItem.get()), ==, 5);
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(value.get(), 2));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_undefined(arrayItem.get()));
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(value.get(), 3));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_number(arrayItem.get()));
g_assert_cmpint(jsc_value_to_int32(arrayItem.get()), ==, 10);
arrayLength = adoptGRef(jsc_value_object_get_property(value.get(), "length"));
checker.watch(arrayLength.get());
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(value.get(), jsc_value_to_int32(arrayLength.get()) + 1));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_undefined(arrayItem.get()));
GRefPtr<JSCValue> array = adoptGRef(jsc_value_new_array(context.get(), G_TYPE_INT, 1, G_TYPE_STRING, "two", G_TYPE_BOOLEAN, TRUE, JSC_TYPE_VALUE, value.get(), G_TYPE_NONE));
checker.watch(array.get());
g_assert_true(jsc_value_is_array(array.get()));
g_assert_true(jsc_value_is_object(array.get()));
g_assert_true(jsc_value_to_boolean(array.get()) == TRUE);
g_assert_cmpint(jsc_value_to_int32(array.get()), ==, 0);
valueString.reset(jsc_value_to_string(array.get()));
g_assert_cmpstr(valueString.get(), ==, "1,two,true,1,5,,10");
arrayLength = adoptGRef(jsc_value_object_get_property(array.get(), "length"));
checker.watch(arrayLength.get());
g_assert_true(jsc_value_is_number(arrayLength.get()));
g_assert_cmpint(jsc_value_to_int32(arrayLength.get()), ==, 4);
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(array.get(), 0));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_number(arrayItem.get()));
g_assert_cmpint(jsc_value_to_int32(arrayItem.get()), ==, 1);
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(array.get(), 1));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_string(arrayItem.get()));
valueString.reset(jsc_value_to_string(arrayItem.get()));
g_assert_cmpstr(valueString.get(), ==, "two");
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(array.get(), 2));
checker.watch(arrayItem.get());
g_assert_true(jsc_value_is_boolean(arrayItem.get()));
g_assert_true(jsc_value_to_boolean(arrayItem.get()) == TRUE);
arrayItem = adoptGRef(jsc_value_object_get_property_at_index(array.get(), 3));
checker.watch(arrayItem.get());
g_assert_true(arrayItem.get() == value.get());
GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_with_free_func(g_object_unref));
g_ptr_array_add(gArray.get(), jsc_value_new_number(context.get(), 1));
g_ptr_array_add(gArray.get(), jsc_value_new_string(context.get(), "two"));
g_ptr_array_add(gArray.get(), jsc_value_new_boolean(context.get(), TRUE));
g_ptr_array_add(gArray.get(), g_object_ref(value.get()));
array = adoptGRef(jsc_value_new_array_from_garray(context.get(), gArray.get()));
checker.watch(array.get());
g_assert_true(jsc_value_is_array(array.get()));
g_assert_true(jsc_value_is_object(array.get()));
g_assert_true(jsc_value_to_boolean(array.get()) == TRUE);
g_assert_cmpint(jsc_value_to_int32(array.get()), ==, 0);
valueString.reset(jsc_value_to_string(array.get()));
g_assert_cmpstr(valueString.get(), ==, "1,two,true,1,5,,10");
arrayLength = adoptGRef(jsc_value_object_get_property(array.get(), "length"));
checker.watch(arrayLength.get());
g_assert_true(jsc_value_is_number(arrayLength.get()));
g_assert_cmpint(jsc_value_to_int32(arrayLength.get()), ==, gArray->len);
value = adoptGRef(jsc_value_new_object(context.get(), nullptr, nullptr));
checker.watch(value.get());
g_assert_true(jsc_value_is_object(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "[object Object]");
jsc_context_set_value(context.get(), "o", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "o"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_undefined(context.get()));
checker.watch(value.get());
g_assert_true(jsc_value_is_undefined(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == FALSE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "undefined");
jsc_context_set_value(context.get(), "u", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "u"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
value = adoptGRef(jsc_value_new_null(context.get()));
checker.watch(value.get());
g_assert_true(jsc_value_is_null(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == FALSE);
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
valueString.reset(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "null");
jsc_context_set_value(context.get(), "n", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "n"));
checker.watch(result.get());
g_assert_true(result.get() == value.get());
}
static int foo(int n)
{
return n * 2;
}
static void callback(JSCValue* function)
{
GRefPtr<JSCValue> value = adoptGRef(jsc_value_function_call(function, G_TYPE_INT, 200, G_TYPE_NONE));
g_assert_true(jsc_value_is_undefined(value.get()));
}
static void doubleAndSetInResult(int n)
{
GRefPtr<JSCValue> value = adoptGRef(jsc_value_new_number(jsc_context_get_current(), n * 2));
jsc_context_set_value(jsc_context_get_current(), "result", value.get());
}
static int sumFunction(GPtrArray* array)
{
int retval = 0;
g_ptr_array_foreach(array, [](gpointer data, gpointer userData) {
g_assert_true(JSC_IS_VALUE(data));
JSCValue* item = JSC_VALUE(data);
g_assert_true(jsc_value_is_number(item));
*static_cast<int*>(userData) += jsc_value_to_int32(item);
}, &retval);
return retval;
}
static void testJSCFunction()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> function = adoptGRef(jsc_value_new_function(context.get(), "foo", G_CALLBACK(foo), nullptr, nullptr, G_TYPE_INT, 1, G_TYPE_INT));
checker.watch(function.get());
jsc_context_set_value(context.get(), "foo", function.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "foo(200)"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 400);
GRefPtr<JSCValue> value = adoptGRef(jsc_value_function_call(function.get(), G_TYPE_INT, 200, G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(value.get() == result.get());
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> function = adoptGRef(jsc_context_evaluate(context.get(), "foo = function(n) { return n * 2; }"));
checker.watch(function.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "foo(200)"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 400);
GRefPtr<JSCValue> value = adoptGRef(jsc_value_function_call(function.get(), G_TYPE_INT, 200, G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(value.get() == result.get());
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> function = adoptGRef(jsc_value_new_function(context.get(), "callback", G_CALLBACK(callback), nullptr, nullptr, G_TYPE_NONE, 1, JSC_TYPE_VALUE));
checker.watch(function.get());
jsc_context_set_value(context.get(), "callback", function.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "var result = 0; callback(function(n){ result = n * 2; }); result"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 400);
result = adoptGRef(jsc_context_evaluate(context.get(), "result = 0"));
checker.watch(result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "result"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 0);
GRefPtr<JSCValue> dbl = adoptGRef(jsc_value_new_function(context.get(), "doubleAndSetInResult", G_CALLBACK(doubleAndSetInResult), nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT));
checker.watch(dbl.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_value_function_call(function.get(), JSC_TYPE_VALUE, dbl.get(), G_TYPE_NONE));
checker.watch(value.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "result"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 400);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> function = adoptGRef(jsc_context_evaluate(context.get(),
"sumFunction = function(array) {\n"
" var result = 0;\n"
" for (var i in array) { result += array[i]; }\n"
" return result;\n"
"}"));
checker.watch(function.get());
g_assert_true(jsc_value_is_object(function.get()));
GRefPtr<JSCValue> array = adoptGRef(jsc_value_new_array(context.get(), G_TYPE_INT, 2, G_TYPE_INT, 4, G_TYPE_INT, 6, G_TYPE_NONE));
checker.watch(array.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_value_function_call(function.get(), JSC_TYPE_VALUE, array.get(), G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 12);
GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_with_free_func(g_object_unref));
g_ptr_array_add(gArray.get(), jsc_value_new_number(context.get(), 1));
g_ptr_array_add(gArray.get(), jsc_value_new_number(context.get(), 3));
g_ptr_array_add(gArray.get(), jsc_value_new_number(context.get(), 5));
value = adoptGRef(jsc_value_function_call(function.get(), G_TYPE_PTR_ARRAY, gArray.get(), G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 9);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> function = adoptGRef(jsc_value_new_function(context.get(), "sumFunction", G_CALLBACK(sumFunction), nullptr, nullptr, G_TYPE_INT, 1, G_TYPE_PTR_ARRAY));
checker.watch(function.get());
jsc_context_set_value(context.get(), "sumFunction", function.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "sumFunction([2,4,6])"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 12);
GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_with_free_func(g_object_unref));
g_ptr_array_add(gArray.get(), jsc_value_new_number(context.get(), 1));
g_ptr_array_add(gArray.get(), jsc_value_new_number(context.get(), 3));
g_ptr_array_add(gArray.get(), jsc_value_new_number(context.get(), 5));
value = adoptGRef(jsc_value_function_call(function.get(), G_TYPE_PTR_ARRAY, gArray.get(), G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 9);
}
}
static void testJSCObject()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> foo = adoptGRef(jsc_context_evaluate(context.get(), "class Foo { foo(n) { return n * 2; } }; foo = new Foo;"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), "Foo"));
GRefPtr<JSCValue> result = adoptGRef(jsc_value_object_invoke_method(foo.get(), "foo", G_TYPE_INT, 200, G_TYPE_NONE));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 400);
bool didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
result = adoptGRef(jsc_value_object_invoke_method(foo.get(), "bar", G_TYPE_INT, 200, G_TYPE_NONE));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_did_throw(exceptionHandler, didThrow);
GRefPtr<JSCValue> constructor = adoptGRef(jsc_context_evaluate(context.get(), "Foo"));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
foo = adoptGRef(jsc_value_constructor_call(constructor.get(), G_TYPE_NONE));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), "Foo"));
result = adoptGRef(jsc_value_object_invoke_method(foo.get(), "foo", G_TYPE_INT, 300, G_TYPE_NONE));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 600);
jsc_context_set_value(context.get(), "foo2", foo.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "foo2 instanceof Foo"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "foo2.foo(500)"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 1000);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> object = adoptGRef(jsc_value_new_object(context.get(), nullptr, nullptr));
checker.watch(object.get());
g_assert_true(jsc_value_is_object(object.get()));
g_assert_true(jsc_value_object_is_instance_of(object.get(), "Object"));
GRefPtr<JSCValue> property = adoptGRef(jsc_value_new_number(context.get(), 25));
checker.watch(property.get());
jsc_value_object_define_property_data(object.get(), "val", static_cast<JSCValuePropertyFlags>(0), property.get());
jsc_context_set_value(context.get(), "f", object.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "f.val;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 25);
bool didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
value = adoptGRef(jsc_context_evaluate(context.get(), "'use strict'; f.val = 32;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_undefined(value.get()));
g_assert_did_throw(exceptionHandler, didThrow);
value = adoptGRef(jsc_context_evaluate(context.get(), "f.propertyIsEnumerable('val');"));
checker.watch(value.get());
g_assert_true(jsc_value_is_boolean(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == FALSE);
value = adoptGRef(jsc_context_evaluate(context.get(), "delete f.val;"));
checker.watch(value.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 25);
property = adoptGRef(jsc_value_new_number(context.get(), 52));
checker.watch(property.get());
g_assert_throw_begin(exceptionHandler, didThrow);
jsc_value_object_define_property_data(object.get(), "val", static_cast<JSCValuePropertyFlags>(0), property.get());
g_assert_did_throw(exceptionHandler, didThrow);
property = adoptGRef(jsc_value_new_number(context.get(), 32));
checker.watch(property.get());
jsc_value_object_define_property_data(object.get(), "val2", static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_ENUMERABLE | JSC_VALUE_PROPERTY_WRITABLE), property.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val2;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 32);
value = adoptGRef(jsc_context_evaluate(context.get(), "'use strict'; f.val2 = 45;"));
checker.watch(value.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val2;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 45);
value = adoptGRef(jsc_context_evaluate(context.get(), "f.propertyIsEnumerable('val2');"));
checker.watch(value.get());
g_assert_true(jsc_value_is_boolean(value.get()));
g_assert_true(jsc_value_to_boolean(value.get()) == TRUE);
property = adoptGRef(jsc_value_new_number(context.get(), 125));
checker.watch(property.get());
jsc_value_object_define_property_data(object.get(), "val3", static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE), property.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val3;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 125);
property = adoptGRef(jsc_value_new_number(context.get(), 150));
checker.watch(property.get());
jsc_value_object_define_property_data(object.get(), "val3", static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE), property.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val3;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 150);
value = adoptGRef(jsc_context_evaluate(context.get(), "delete f.val3;"));
checker.watch(value.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val3;"));
checker.watch(value.get());
g_assert_true(jsc_value_is_undefined(value.get()));
GRefPtr<JSCValue> function = adoptGRef(jsc_value_new_function(context.get(), "foo", G_CALLBACK(foo), nullptr, nullptr, G_TYPE_INT, 1, G_TYPE_INT));
checker.watch(function.get());
jsc_value_object_define_property_data(object.get(), "foo", static_cast<JSCValuePropertyFlags>(0), function.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_value_object_invoke_method(object.get(), "foo", G_TYPE_INT, 200, G_TYPE_NONE));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 400);
g_assert_throw_begin(exceptionHandler, didThrow);
result = adoptGRef(jsc_value_object_invoke_method(object.get(), "foo", G_TYPE_POINTER, "200", G_TYPE_NONE));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_did_throw(exceptionHandler, didThrow);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_value_new_number(context.get(), 125));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_false(jsc_value_is_object(value.get()));
g_assert_false(jsc_value_object_is_instance_of(value.get(), "Object"));
bool didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
GRefPtr<JSCValue> result = adoptGRef(jsc_value_object_invoke_method(value.get(), "foo", G_TYPE_INT, 200, G_TYPE_NONE));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_throw_begin(exceptionHandler, didThrow);
}
}
typedef struct _Foo Foo;
struct _Foo {
int foo;
Foo* sibling;
};
static Foo* fooCreate()
{
return g_new0(Foo, 1);
}
static Foo* fooCreateWithFoo(int value)
{
auto* f = fooCreate();
f->foo = value;
return f;
}
static void fooFree(Foo* foo)
{
g_free(foo);
}
static void setFoo(Foo* foo, int value)
{
foo->foo = value;
}
static int getFoo(Foo* foo)
{
return foo->foo;
}
static void setSibling(Foo* foo, Foo* sibling)
{
foo->sibling = sibling;
}
static Foo* getSibling(Foo* foo)
{
return foo->sibling;
}
static void multiplyFoo(Foo* foo, int multiplier)
{
foo->foo *= multiplier;
}
struct PromiseData {
Foo* foo;
int multiplier;
LeakChecker* checker;
};
static void getMultiplyFoo(JSCValue* resolve, JSCValue* reject, PromiseData* data)
{
data->checker->watch(resolve);
g_assert_true(JSC_IS_VALUE(resolve));
g_assert_true(jsc_value_is_function(resolve));
data->checker->watch(reject);
g_assert_true(JSC_IS_VALUE(reject));
g_assert_true(jsc_value_is_function(reject));
GRefPtr<JSCValue> result;
if (data->multiplier > 0)
result = adoptGRef(jsc_value_function_call(resolve, G_TYPE_INT, data->foo->foo * data->multiplier, G_TYPE_NONE));
else {
GRefPtr<JSCException> exception = adoptGRef(jsc_exception_new(jsc_context_get_current(), "Multiplier must be positive greater than 0"));
result = adoptGRef(jsc_value_function_call(reject, JSC_TYPE_EXCEPTION, exception.get(), G_TYPE_NONE));
}
data->checker->watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
}
static JSCValue* getMultiplyFooAsync(Foo* foo, int multiplier, LeakChecker* checker)
{
auto* jsContext = jsc_context_get_current();
g_assert_true(JSC_IS_CONTEXT(jsContext));
GRefPtr<JSCValue> function = adoptGRef(jsc_value_new_function(jsContext, nullptr, G_CALLBACK(getMultiplyFoo), new PromiseData { foo, multiplier, checker},
[](gpointer data) { delete static_cast<PromiseData*>(data); }, G_TYPE_NONE, 2, JSC_TYPE_VALUE, JSC_TYPE_VALUE));
checker->watch(function.get());
GRefPtr<JSCValue> promise = adoptGRef(jsc_context_get_value(jsContext, "Promise"));
checker->watch(promise.get());
g_assert_true(jsc_value_is_constructor(promise.get()));
auto* returnValue = jsc_value_constructor_call(promise.get(), JSC_TYPE_VALUE, function.get(), G_TYPE_NONE);
checker->watch(returnValue);
return returnValue;
}
typedef struct {
int baz;
} Baz;
static Baz* bazCreate()
{
return g_new0(Baz, 1);
}
static void testJSCClass()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
g_assert_false(jsc_class_get_parent(jscClass));
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
GRefPtr<JSCValue> foo = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo();"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(jscClass)));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof Foo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
jsc_class_add_method(jscClass, "getFoo", G_CALLBACK(getFoo), nullptr, nullptr, G_TYPE_INT, 0, G_TYPE_NONE);
GRefPtr<JSCValue> value = adoptGRef(jsc_value_object_invoke_method(foo.get(), "getFoo", G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 0);
GRefPtr<JSCValue> value2 = adoptGRef(jsc_context_evaluate(context.get(), "f.getFoo()"));
checker.watch(value2.get());
g_assert_true(value.get() == value2.get());
jsc_class_add_method(jscClass, "setFoo", G_CALLBACK(setFoo), nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT);
result = adoptGRef(jsc_value_object_invoke_method(foo.get(), "setFoo", G_TYPE_INT, 25, G_TYPE_NONE));
checker.watch(result.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.getFoo()"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 25);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.setFoo(45)"));
checker.watch(result.get());
value = adoptGRef(jsc_value_object_invoke_method(foo.get(), "getFoo", G_TYPE_NONE));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 45);
GRefPtr<JSCValue> constructor2 = adoptGRef(jsc_class_add_constructor(jscClass, "CreateWithFoo", G_CALLBACK(fooCreateWithFoo), nullptr, nullptr, G_TYPE_POINTER, 1, G_TYPE_INT));
checker.watch(constructor2.get());
g_assert_true(jsc_value_is_constructor(constructor2.get()));
jsc_value_object_set_property(constructor.get(), "CreateWithFoo", constructor2.get());
GRefPtr<JSCValue> foo2 = adoptGRef(jsc_context_evaluate(context.get(), "f2 = new Foo.CreateWithFoo(42);"));
checker.watch(foo2.get());
g_assert_true(jsc_value_is_object(foo2.get()));
g_assert_true(jsc_value_object_is_instance_of(foo2.get(), jsc_class_get_name(jscClass)));
g_assert_false(foo.get() == foo2.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f2 instanceof Foo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f2 instanceof Foo.CreateWithFoo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
jsc_class_add_property(jscClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
value = adoptGRef(jsc_context_evaluate(context.get(), "f2.foo"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 42);
result = adoptGRef(jsc_context_evaluate(context.get(), "f2.foo = 52"));
checker.watch(result.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f2.foo"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 52);
JSCClass* otherClass = jsc_context_register_class(context.get(), "Baz", nullptr, g_free);
checker.watch(otherClass);
g_assert_false(jsc_class_get_parent(otherClass));
GRefPtr<JSCValue> constructor3 = adoptGRef(jsc_class_add_constructor(otherClass, nullptr, G_CALLBACK(bazCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor3.get());
g_assert_true(jsc_value_is_constructor(constructor3.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(otherClass), constructor3.get());
g_assert_false(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(otherClass)));
g_assert_false(jsc_value_object_is_instance_of(foo2.get(), jsc_class_get_name(otherClass)));
GRefPtr<JSCValue> baz = adoptGRef(jsc_value_constructor_call(constructor3.get(), G_TYPE_NONE));
checker.watch(baz.get());
g_assert_true(jsc_value_is_object(baz.get()));
g_assert_true(jsc_value_object_is_instance_of(baz.get(), jsc_class_get_name(otherClass)));
g_assert_false(jsc_value_object_is_instance_of(baz.get(), jsc_class_get_name(jscClass)));
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
jsc_class_add_property(jscClass, "sibling", G_TYPE_POINTER, G_CALLBACK(getSibling), G_CALLBACK(setSibling), nullptr, nullptr);
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f1 = new Foo(); f2 = new Foo(); f2.sibling = f1"));
checker.watch(result.get());
GRefPtr<JSCValue> f1 = adoptGRef(jsc_context_get_value(context.get(), "f1"));
checker.watch(f1.get());
g_assert_true(jsc_value_is_object(f1.get()));
GRefPtr<JSCValue> f2 = adoptGRef(jsc_context_get_value(context.get(), "f2"));
checker.watch(f2.get());
g_assert_true(jsc_value_is_object(f2.get()));
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "f2.sibling"));
checker.watch(value.get());
g_assert_true(value.get() == f1.get());
jsc_value_object_set_property(f1.get(), "sibling", f2.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f1.sibling"));
checker.watch(value.get());
g_assert_true(value.get() == f2.get());
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
jsc_class_add_property(jscClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
Foo* f = fooCreateWithFoo(25);
GRefPtr<JSCValue> foo = adoptGRef(jsc_value_new_object(context.get(), f, jscClass));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(jscClass)));
jsc_context_set_value(context.get(), "f", foo.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof Foo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f.foo"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 25);
setFoo(f, 42);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.foo"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 42);
JSCClass* bazClass = jsc_context_register_class(context.get(), "Baz", nullptr, g_free);
checker.watch(bazClass);
GRefPtr<JSCValue> constructor2 = adoptGRef(jsc_class_add_constructor(bazClass, nullptr, G_CALLBACK(bazCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor2.get());
g_assert_true(jsc_value_is_constructor(constructor2.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(bazClass), constructor2.get());
Baz* bz = bazCreate();
GRefPtr<JSCValue> baz = adoptGRef(jsc_value_new_object(context.get(), bz, bazClass));
checker.watch(baz.get());
g_assert_true(jsc_value_is_object(baz.get()));
g_assert_true(jsc_value_object_is_instance_of(baz.get(), jsc_class_get_name(bazClass)));
g_assert_false(jsc_value_object_is_instance_of(baz.get(), jsc_class_get_name(jscClass)));
g_assert_false(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(bazClass)));
jsc_context_set_value(context.get(), "bz", baz.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "bz instanceof Baz;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "bz instanceof Foo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_false(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof Baz;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_false(jsc_value_to_boolean(result.get()));
jsc_value_object_set_property(baz.get(), "foo", foo.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "bz.foo"));
checker.watch(result.get());
g_assert_true(result.get() == foo.get());
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
jsc_class_add_property(jscClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
Foo* f = fooCreateWithFoo(25);
GRefPtr<JSCValue> foo = adoptGRef(jsc_value_new_object(context.get(), f, jscClass));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(jscClass)));
jsc_context_set_value(context.get(), "f1", foo.get());
jsc_context_set_value(context.get(), "f2", foo.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f1 === f2;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
GRefPtr<JSCValue> property = adoptGRef(jsc_value_new_number(context.get(), 50));
checker.watch(property.get());
jsc_value_object_set_property(foo.get(), "n", property.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f1.n"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 50);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
g_assert_false(jsc_class_get_parent(jscClass));
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
jsc_class_add_property(jscClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
GRefPtr<JSCValue> foo = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo();"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(jscClass)));
GRefPtr<JSCValue> value = adoptGRef(jsc_value_new_number(context.get(), 125));
checker.watch(value.get());
jsc_value_object_set_property(foo.get(), "foo", value.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f.__lookupGetter__('foo').call(f)"));
checker.watch(result.get());
g_assert_true(value.get() == result.get());
bool didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.__lookupGetter__('foo').call({})"));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_did_throw(exceptionHandler, didThrow);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
g_assert_false(jsc_class_get_parent(jscClass));
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_NONE, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
bool didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
GRefPtr<JSCValue> foo = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo();"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_undefined(foo.get()));
g_assert_did_throw(exceptionHandler, didThrow);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> jsNamespace = adoptGRef(jsc_value_new_object(context.get(), nullptr, nullptr));
checker.watch(jsNamespace.get());
g_assert_true(jsc_value_is_object(jsNamespace.get()));
jsc_context_set_value(context.get(), "wk", jsNamespace.get());
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_value_object_set_property(jsNamespace.get(), jsc_class_get_name(jscClass), constructor.get());
jsc_class_add_property(jscClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
bool didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
GRefPtr<JSCValue> foo = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo();"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_undefined(foo.get()));
g_assert_did_throw(exceptionHandler, didThrow);
foo = adoptGRef(jsc_context_evaluate(context.get(), "f = new wk.Foo();"));
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), "wk.Foo"));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof wk.Foo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
GRefPtr<JSCValue> constructor2 = adoptGRef(jsc_class_add_constructor(jscClass, "CreateWithFoo", G_CALLBACK(fooCreateWithFoo), nullptr, nullptr, G_TYPE_POINTER, 1, G_TYPE_INT));
checker.watch(constructor2.get());
g_assert_true(jsc_value_is_constructor(constructor2.get()));
jsc_value_object_set_property(constructor.get(), "CreateWithFoo", constructor2.get());
g_assert_throw_begin(exceptionHandler, didThrow);
GRefPtr<JSCValue> foo2 = adoptGRef(jsc_context_evaluate(context.get(), "f2 = new Foo.CreateWithFoo(42);"));
checker.watch(foo2.get());
g_assert_true(jsc_value_is_undefined(foo2.get()));
g_assert_did_throw(exceptionHandler, didThrow);
foo2 = adoptGRef(jsc_context_evaluate(context.get(), "f2 = new wk.Foo.CreateWithFoo(42);"));
checker.watch(foo2.get());
g_assert_true(jsc_value_is_object(foo2.get()));
g_assert_true(jsc_value_object_is_instance_of(foo2.get(), "wk.Foo"));
g_assert_true(jsc_value_object_is_instance_of(foo2.get(), "wk.Foo.CreateWithFoo"));
g_assert_false(foo.get() == foo2.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f2 instanceof wk.Foo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f2 instanceof wk.Foo.CreateWithFoo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f2.foo"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 42);
GRefPtr<JSCValue> foo3 = adoptGRef(jsc_value_constructor_call(constructor2.get(), G_TYPE_INT, 62, G_TYPE_NONE));
checker.watch(foo3.get());
g_assert_true(jsc_value_is_object(foo3.get()));
g_assert_true(jsc_value_object_is_instance_of(foo3.get(), "wk.Foo"));
g_assert_true(jsc_value_object_is_instance_of(foo3.get(), "wk.Foo.CreateWithFoo"));
g_assert_false(foo2.get() == foo3.get());
jsc_context_set_value(context.get(), "f3", foo3.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f3 instanceof wk.Foo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f3 instanceof wk.Foo.CreateWithFoo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f3.foo"));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 62);
}
}
typedef struct {
Foo parent;
int bar;
} Bar;
static Bar* barCreate()
{
return g_new0(Bar, 1);
}
static void barFree(Bar* bar)
{
g_free(bar);
}
static void setBar(Bar* bar, int value)
{
bar->bar = value;
}
static int getBar(Bar* bar)
{
return bar->bar;
}
static void testJSCPrototypes()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* fooClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(fooClass);
g_assert_false(jsc_class_get_parent(fooClass));
GRefPtr<JSCValue> fooConstructor = adoptGRef(jsc_class_add_constructor(fooClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(fooConstructor.get());
g_assert_true(jsc_value_is_constructor(fooConstructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(fooClass), fooConstructor.get());
jsc_class_add_method(fooClass, "multiply", G_CALLBACK(multiplyFoo), nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT);
jsc_class_add_property(fooClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
JSCClass* barClass = jsc_context_register_class(context.get(), "Bar", fooClass, reinterpret_cast<GDestroyNotify>(barFree));
checker.watch(barClass);
g_assert_true(jsc_class_get_parent(barClass) == fooClass);
GRefPtr<JSCValue> barConstructor = adoptGRef(jsc_class_add_constructor(barClass, nullptr, G_CALLBACK(barCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(barConstructor.get());
g_assert_true(jsc_value_is_constructor(barConstructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(barClass), barConstructor.get());
jsc_class_add_property(barClass, "bar", G_TYPE_INT, G_CALLBACK(getBar), G_CALLBACK(setBar), nullptr, nullptr);
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo(); b = new Bar();"));
checker.watch(result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f.__proto__ == Foo.prototype"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "b.__proto__ == Bar.prototype"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "b.__proto__.__proto__ == Foo.prototype"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
GRefPtr<JSCValue> foo = adoptGRef(jsc_context_get_value(context.get(), "f"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(fooClass)));
g_assert_false(jsc_value_object_is_instance_of(foo.get(), jsc_class_get_name(barClass)));
result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof Foo"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof Bar"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_false(jsc_value_to_boolean(result.get()));
GRefPtr<JSCValue> bar = adoptGRef(jsc_context_get_value(context.get(), "b"));
checker.watch(bar.get());
g_assert_true(jsc_value_is_object(bar.get()));
g_assert_true(jsc_value_object_is_instance_of(bar.get(), jsc_class_get_name(barClass)));
g_assert_true(jsc_value_object_is_instance_of(bar.get(), jsc_class_get_name(fooClass)));
result = adoptGRef(jsc_context_evaluate(context.get(), "b instanceof Bar"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "b instanceof Foo"));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "b.bar = 25; b.foo = 42;"));
checker.watch(result.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "b.bar"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 25);
value = adoptGRef(jsc_context_evaluate(context.get(), "b.foo"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 42);
result = adoptGRef(jsc_context_evaluate(context.get(), "b.multiply(2)"));
checker.watch(result.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "b.foo"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 84);
}
}
static void createError()
{
jsc_context_throw(jsc_context_get_current(), "API exception");
}
static void testJSCExceptions()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
g_assert_false(jsc_context_get_exception(context.get()));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "foo"));
checker.watch(result.get());
// By default exceptions are not caught.
g_assert_true(jsc_value_is_undefined(result.get()));
auto* exception = jsc_context_get_exception(context.get());
g_assert_true(JSC_IS_EXCEPTION(exception));
checker.watch(exception);
g_assert_cmpstr(jsc_exception_get_message(exception), ==, "Can't find variable: foo");
g_assert_cmpuint(jsc_exception_get_line_number(exception), ==, 1);
g_assert_false(jsc_exception_get_source_uri(exception));
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
g_assert_false(jsc_context_get_exception(context.get()));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f = 25;\nfoo;"));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
auto* exception = jsc_context_get_exception(context.get());
g_assert_true(JSC_IS_EXCEPTION(exception));
checker.watch(exception);
g_assert_cmpuint(jsc_exception_get_line_number(exception), ==, 2);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
g_assert_false(jsc_context_get_exception(context.get()));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate_with_source_uri(context.get(), "foo", "file:///foo/script.js"));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
auto* exception = jsc_context_get_exception(context.get());
g_assert_true(JSC_IS_EXCEPTION(exception));
checker.watch(exception);
g_assert_cmpstr(jsc_exception_get_source_uri(exception), ==, "file:///foo/script.js");
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
g_assert_false(jsc_context_get_exception(context.get()));
GRefPtr<JSCValue> function = adoptGRef(jsc_value_new_function(context.get(), "createError", G_CALLBACK(createError), nullptr, nullptr, G_TYPE_NONE, 0, G_TYPE_NONE));
checker.watch(function.get());
jsc_context_set_value(context.get(), "createError", function.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "var result; try { createError(); } catch (e) { result = 'Caught exception'; }"));
checker.watch(result.get());
g_assert_true(jsc_value_is_string(result.get()));
GUniquePtr<char> resultString(jsc_value_to_string(result.get()));
g_assert_cmpstr(resultString.get(), ==, "Caught exception");
g_assert_false(jsc_context_get_exception(context.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "var result; createError(); result = 'No exception';"));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
auto* exception = jsc_context_get_exception(context.get());
g_assert_true(JSC_IS_EXCEPTION(exception));
checker.watch(exception);
g_assert_cmpstr(jsc_exception_get_message(exception), ==, "API exception");
g_assert_cmpuint(jsc_exception_get_line_number(exception), ==, 1);
g_assert_false(jsc_exception_get_source_uri(exception));
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
g_assert_false(jsc_context_get_exception(context.get()));
struct Test {
JSCContext* context;
GRefPtr<JSCException> exception;
bool wasDeleted;
};
Test test = { context.get(), nullptr, false };
jsc_context_push_exception_handler(context.get(), [](JSCContext* context, JSCException* exception, gpointer userData) {
auto* test = static_cast<Test*>(userData);
g_assert_true(context == test->context);
g_assert_false(test->exception);
g_assert_false(test->wasDeleted);
test->exception = exception;
}, &test, [](gpointer userData) {
static_cast<Test*>(userData)->wasDeleted = true;
});
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "foo"));
checker.watch(result.get());
// Exception was caught by the user handler.
g_assert_false(jsc_context_get_exception(context.get()));
g_assert_true(JSC_IS_EXCEPTION(test.exception.get()));
checker.watch(test.exception.get());
g_assert_cmpstr(jsc_exception_get_message(test.exception.get()), ==, "Can't find variable: foo");
g_assert_cmpuint(jsc_exception_get_line_number(test.exception.get()), ==, 1);
g_assert_false(jsc_exception_get_source_uri(test.exception.get()));
g_assert_false(test.wasDeleted);
jsc_context_pop_exception_handler(context.get());
g_assert_true(test.wasDeleted);
test.exception = nullptr;
test.wasDeleted = false;
jsc_context_push_exception_handler(context.get(), [](JSCContext* context, JSCException* exception, gpointer userData) {
auto* test = static_cast<Test*>(userData);
g_assert_true(context == test->context);
g_assert_false(test->exception);
g_assert_false(test->wasDeleted);
test->exception = exception;
jsc_context_throw_exception(context, exception);
}, &test, [](gpointer userData) {
static_cast<Test*>(userData)->wasDeleted = true;
});
result = adoptGRef(jsc_context_evaluate(context.get(), "foo"));
checker.watch(result.get());
// Exception was handled by the user handler, but not caught.
auto* exception = jsc_context_get_exception(context.get());
g_assert_true(JSC_IS_EXCEPTION(exception));
checker.watch(exception);
g_assert_true(exception == test.exception.get());
g_assert_false(test.wasDeleted);
jsc_context_pop_exception_handler(context.get());
g_assert_true(test.wasDeleted);
}
}
static void testJSCPromises()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> promise = adoptGRef(jsc_context_get_value(context.get(), "Promise"));
checker.watch(promise.get());
g_assert_true(jsc_value_is_function(promise.get()));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "typeof Promise"));
checker.watch(result.get());
g_assert_true(jsc_value_is_string(result.get()));
GUniquePtr<char> resultString(jsc_value_to_string(result.get()));
g_assert_cmpstr(resultString.get(), ==, "function");
result = adoptGRef(jsc_context_evaluate(context.get(), "result = 0; Promise.resolve(42).then(function (value) { result = value; });"));
checker.watch(result.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_get_value(context.get(), "result"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 42);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
JSCClass* fooClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(fooClass);
g_assert_false(jsc_class_get_parent(fooClass));
GRefPtr<JSCValue> fooConstructor = adoptGRef(jsc_class_add_constructor(fooClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(fooConstructor.get());
g_assert_true(jsc_value_is_constructor(fooConstructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(fooClass), fooConstructor.get());
jsc_class_add_method(fooClass, "getMultiplyFooAsync", G_CALLBACK(getMultiplyFooAsync), &checker, nullptr, JSC_TYPE_VALUE, 1, G_TYPE_INT);
jsc_class_add_property(fooClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "result = 0; f = new Foo(); f.foo = 5; f.getMultiplyFooAsync(2).then(function (value) { result = value; }, function (error) { result = -1; });"));
checker.watch(result.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_get_value(context.get(), "result"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 10);
result = adoptGRef(jsc_context_evaluate(context.get(), "result = 0; f.getMultiplyFooAsync(0).then(function (value) { result = value; }, function (error) { result = -1; });"));
checker.watch(result.get());
value = adoptGRef(jsc_context_get_value(context.get(), "result"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, -1);
}
}
static bool s_fooWasFreed;
static void fooFreeAndLog(Foo* foo)
{
fooFree(foo);
s_fooWasFreed = true;
}
static void testJSCGarbageCollector()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> object = adoptGRef(jsc_value_new_object(context.get(), nullptr, nullptr));
checker.watch(object.get());
GRefPtr<JSCValue> foo = adoptGRef(jsc_value_new_number(context.get(), 25));
checker.watch(foo.get());
g_assert_true(jsc_value_is_number(foo.get()));
g_assert_cmpint(jsc_value_to_int32(foo.get()), ==, 25);
jsc_value_object_set_property(object.get(), "foo", foo.get());
jsc_context_set_value(context.get(), "f", object.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f"));
checker.watch(result.get());
g_assert_true(object.get() == result.get());
object = nullptr;
foo = nullptr;
result = nullptr;
jscContextGarbageCollect(context.get());
object = adoptGRef(jsc_context_get_value(context.get(), "f"));
checker.watch(object.get());
g_assert_true(jsc_value_is_object(object.get()));
foo = adoptGRef(jsc_context_evaluate(context.get(), "f.foo"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_number(foo.get()));
g_assert_cmpint(jsc_value_to_int32(foo.get()), ==, 25);
result = adoptGRef(jsc_context_evaluate(context.get(), "f = undefined"));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
jscContextGarbageCollect(context.get());
g_assert_true(jsc_value_is_object(object.get()));
jsc_context_set_value(context.get(), "f", object.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f"));
checker.watch(result.get());
g_assert_true(object.get() == result.get());
foo = adoptGRef(jsc_context_evaluate(context.get(), "f.foo"));
checker.watch(foo.get());
g_assert_true(jsc_value_is_number(foo.get()));
g_assert_cmpint(jsc_value_to_int32(foo.get()), ==, 25);
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
s_fooWasFreed = false;
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFreeAndLog));
checker.watch(jscClass);
g_assert_false(jsc_class_get_parent(jscClass));
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
GRefPtr<JSCValue> object = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo();"));
checker.watch(object.get());
g_assert_true(jsc_value_is_object(object.get()));
g_assert_true(jsc_value_object_is_instance_of(object.get(), jsc_class_get_name(jscClass)));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f"));
checker.watch(result.get());
g_assert_true(object.get() == result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f = undefined"));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_false(s_fooWasFreed);
jscContextGarbageCollect(context.get());
g_assert_false(s_fooWasFreed);
object = nullptr;
g_assert_false(s_fooWasFreed);
jscContextGarbageCollect(context.get());
g_assert_true(s_fooWasFreed);
}
}
static void testsJSCVirtualMachine()
{
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
auto thread = Thread::create("JSCVirtualMachineTest", [&] {
JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFreeAndLog));
checker.watch(jscClass);
g_assert_false(jsc_class_get_parent(jscClass));
GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
checker.watch(constructor.get());
g_assert_true(jsc_value_is_constructor(constructor.get()));
jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
GRefPtr<JSCValue> object = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo();"));
checker.watch(object.get());
g_assert_true(jsc_value_get_context(object.get()) == context.get());
g_assert_true(jsc_value_is_object(object.get()));
g_assert_true(jsc_value_object_is_instance_of(object.get(), jsc_class_get_name(jscClass)));
});
thread->waitForCompletion();
thread->detach();
GRefPtr<JSCValue> object = adoptGRef(jsc_context_get_value(context.get(), "f"));
checker.watch(object.get());
g_assert_true(jsc_value_is_object(object.get()));
jscContextGarbageCollect(context.get());
}
{
Vector<Ref<Thread>, 5> threads;
bool ok = true;
for (unsigned i = 0; i < 5; ++i) {
threads.append(Thread::create("JSCVirtualMachineTest", [&ok] {
GRefPtr<JSCVirtualMachine> vm = adoptGRef(jsc_virtual_machine_new());
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new_with_virtual_machine(vm.get()));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(),
"var array = [{}];\n"
"for (var i = 0; i < 20; ++i) {\n"
" var newArray = new Array(array.length * 2);\n"
" for (var j = 0; j < newArray.length; ++j)\n"
" newArray[j] = {parent: array[j / 2]};\n"
" array = newArray;\n"
"}\n"
));
g_assert_true(jsc_value_get_context(result.get()) == context.get());
if (auto* exception = jsc_context_get_exception(context.get())) {
g_message("Uncaught exception: %s", jsc_exception_get_message(exception));
ok = false;
}
GRefPtr<JSCValue> value = adoptGRef(jsc_context_get_value(context.get(), "array"));
g_assert_true(jsc_value_get_context(value.get()) == context.get());
if (!jsc_value_is_object(value.get())) {
g_message("Did not find \"array\" variable");
ok = false;
}
jscContextGarbageCollect(context.get());
}));
}
for (auto& thread : threads) {
thread->waitForCompletion();
thread->detach();
}
g_assert_true(ok);
}
}
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
static void testsJSCAutocleanups()
{
LeakChecker checker;
g_autoptr(JSCVirtualMachine) vm = jsc_virtual_machine_new();
checker.watch(vm);
g_assert_true(JSC_IS_VIRTUAL_MACHINE(vm));
g_autoptr(JSCContext) context = jsc_context_new_with_virtual_machine(vm);
checker.watch(context);
g_assert_true(JSC_IS_CONTEXT(context));
g_autoptr(JSCValue) value = jsc_context_evaluate(context, "v = 25");
checker.watch(value);
g_assert_true(JSC_IS_VALUE(value));
g_assert_true(jsc_value_is_number(value));
g_assert_cmpint(jsc_value_to_int32(value), ==, 25);
g_autoptr(JSCException) exception = jsc_exception_new(context, "API error");
checker.watch(exception);
g_assert_true(JSC_IS_EXCEPTION(exception));
jsc_context_throw_exception(context, exception);
}
#endif
int main(int argc, char** argv)
{
g_test_init(&argc, &argv, nullptr);
g_test_add_func("/jsc/basic", testJSCBasic);
g_test_add_func("/jsc/types", testJSCTypes);
g_test_add_func("/jsc/function", testJSCFunction);
g_test_add_func("/jsc/object", testJSCObject);
g_test_add_func("/jsc/class", testJSCClass);
g_test_add_func("/jsc/prototypes", testJSCPrototypes);
g_test_add_func("/jsc/exceptions", testJSCExceptions);
g_test_add_func("/jsc/promises", testJSCPromises);
g_test_add_func("/jsc/garbage-collector", testJSCGarbageCollector);
g_test_add_func("/jsc/vm", testsJSCVirtualMachine);
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
g_test_add_func("/jsc/autocleanups", testsJSCAutocleanups);
#endif
return g_test_run();
}