blob: a88ee98eaa1b869852fa86db174209be4c5e90d1 [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", -1));
checker.watch(result.get());
g_assert_true(result.get() == value1.get());
result = adoptGRef(jsc_context_evaluate(context2.get(), "value1", -1));
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", -1));
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", -1));
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", -1));
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", -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", -1));
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", -1));
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", -1));
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", -1));
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", -1));
checker.watch(result2.get());
g_assert_true(result2.get() == value.get());
}
{
LeakChecker checker;
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GUniquePtr<char> scriptFile(g_build_filename(WEBKIT_SRC_DIR, "Tools", "TestWebKitAPI", "Tests", "JavaScriptCore", "glib", "script.js", nullptr));
GUniqueOutPtr<char> contents;
gsize contentsSize;
g_assert_true(g_file_get_contents(scriptFile.get(), &contents.outPtr(), &contentsSize, nullptr));
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), contents.get(), contentsSize));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
GString* expected = g_string_new("String");
expected = g_string_append_c(expected, '\0');
expected = g_string_append(expected, "With");
expected = g_string_append_c(expected, '\0');
expected = g_string_append(expected, "Null");
GRefPtr<GBytes> expectedBytes = adoptGRef(g_string_free_to_bytes(expected));
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "testStringWithNull()", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_string(value.get()));
GUniquePtr<char> valueString(jsc_value_to_string(value.get()));
g_assert_cmpstr(valueString.get(), ==, "String");
GRefPtr<GBytes> valueBytes = adoptGRef(jsc_value_to_string_as_bytes(value.get()));
g_assert_true(g_bytes_equal(valueBytes.get(), expectedBytes.get()));
value = adoptGRef(jsc_value_new_string_from_bytes(context.get(), expectedBytes.get()));
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(), ==, "String");
valueBytes = adoptGRef(jsc_value_to_string_as_bytes(value.get()));
g_assert_true(g_bytes_equal(valueBytes.get(), expectedBytes.get()));
jsc_context_set_value(context.get(), "s", value.get());
result = adoptGRef(jsc_context_get_value(context.get(), "s"));
checker.watch(result.get());
g_assert_true(result.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)", -1));
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; }", -1));
checker.watch(function.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "foo(200)", -1));
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", -1));
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", -1));
checker.watch(result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "result", -1));
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", -1));
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"
"}", -1));
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])", -1));
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;", -1));
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"));
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
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);
g_assert_false(jsc_value_object_has_property(foo.get(), "bar"));
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", -1));
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", -1));
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)", -1));
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"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(object.get()));
g_assert_null(properties.get());
GRefPtr<JSCValue> property = adoptGRef(jsc_value_new_number(context.get(), 25));
checker.watch(property.get());
g_assert_false(jsc_value_object_has_property(object.get(), "val"));
jsc_value_object_define_property_data(object.get(), "val", static_cast<JSCValuePropertyFlags>(0), property.get());
g_assert_true(jsc_value_object_has_property(object.get(), "val"));
properties.reset(jsc_value_object_enumerate_properties(object.get()));
g_assert_null(properties.get());
jsc_context_set_value(context.get(), "f", object.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "f.val;", -1));
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;", -1));
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');", -1));
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;", -1));
checker.watch(value.get());
g_assert_true(jsc_value_object_has_property(object.get(), "val"));
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val;", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 25);
g_assert_false(jsc_value_object_delete_property(object.get(), "val"));
g_assert_true(jsc_value_object_has_property(object.get(), "val"));
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());
g_assert_false(jsc_value_object_has_property(object.get(), "val2"));
jsc_value_object_define_property_data(object.get(), "val2", static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_ENUMERABLE | JSC_VALUE_PROPERTY_WRITABLE), property.get());
g_assert_true(jsc_value_object_has_property(object.get(), "val2"));
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val2;", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 32);
properties.reset(jsc_value_object_enumerate_properties(object.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 1);
g_assert_cmpstr(properties.get()[0], ==, "val2");
g_assert_null(properties.get()[1]);
value = adoptGRef(jsc_context_evaluate(context.get(), "'use strict'; f.val2 = 45;", -1));
checker.watch(value.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val2;", -1));
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');", -1));
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_false(jsc_value_object_delete_property(object.get(), "val2"));
g_assert_true(jsc_value_object_has_property(object.get(), "val2"));
property = adoptGRef(jsc_value_new_number(context.get(), 125));
checker.watch(property.get());
g_assert_false(jsc_value_object_has_property(object.get(), "val3"));
jsc_value_object_define_property_data(object.get(), "val3", static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE), property.get());
g_assert_true(jsc_value_object_has_property(object.get(), "val3"));
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val3;", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 125);
properties.reset(jsc_value_object_enumerate_properties(object.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 1);
g_assert_cmpstr(properties.get()[0], ==, "val2");
g_assert_null(properties.get()[1]);
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());
g_assert_true(jsc_value_object_has_property(object.get(), "val3"));
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val3;", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 150);
properties.reset(jsc_value_object_enumerate_properties(object.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 1);
g_assert_cmpstr(properties.get()[0], ==, "val2");
g_assert_null(properties.get()[1]);
value = adoptGRef(jsc_context_evaluate(context.get(), "delete f.val3;", -1));
checker.watch(value.get());
g_assert_false(jsc_value_object_has_property(object.get(), "val3"));
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val3;", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_undefined(value.get()));
property = adoptGRef(jsc_value_new_number(context.get(), 250));
checker.watch(property.get());
g_assert_false(jsc_value_object_has_property(object.get(), "val4"));
jsc_value_object_define_property_data(object.get(), "val4", static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_ENUMERABLE), property.get());
g_assert_true(jsc_value_object_has_property(object.get(), "val4"));
value = adoptGRef(jsc_context_evaluate(context.get(), "f.val4;", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 250);
properties.reset(jsc_value_object_enumerate_properties(object.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 2);
g_assert_cmpstr(properties.get()[0], ==, "val2");
g_assert_cmpstr(properties.get()[1], ==, "val4");
g_assert_null(properties.get()[2]);
g_assert_true(jsc_value_object_delete_property(object.get(), "val4"));
g_assert_false(jsc_value_object_has_property(object.get(), "val4"));
properties.reset(jsc_value_object_enumerate_properties(object.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 1);
g_assert_cmpstr(properties.get()[0], ==, "val2");
g_assert_null(properties.get()[1]);
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());
g_assert_false(jsc_value_object_has_property(object.get(), "foo"));
jsc_value_object_define_property_data(object.get(), "foo", static_cast<JSCValuePropertyFlags>(0), function.get());
g_assert_true(jsc_value_object_has_property(object.get(), "foo"));
properties.reset(jsc_value_object_enumerate_properties(object.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 1);
g_assert_cmpstr(properties.get()[0], ==, "val2");
g_assert_null(properties.get()[1]);
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;
HashMap<CString, int> properties;
Foo* sibling;
};
static Foo* fooCreate()
{
Foo* foo = g_new0(Foo, 1);
new (foo) Foo();
return foo;
}
static Foo* fooCreateWithFoo(int value)
{
auto* f = fooCreate();
f->foo = value;
return f;
}
static void fooFree(Foo* foo)
{
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;
}
static int fooGetProperty(Foo* foo, const char* name)
{
auto addResult = foo->properties.add(name, 0);
return addResult.iterator->value;
}
static void fooSetProperty(Foo* foo, const char* name, int value)
{
auto addResult = foo->properties.add(name, value);
if (!addResult.isNewEntry)
addResult.iterator->value = value;
}
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 JSCClassVTable fooVTable = {
// get_property
[](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name) -> JSCValue* {
auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
checker->watch(context);
if (!g_str_has_prefix(name, "prop_"))
return nullptr;
if (!g_strcmp0(name, "prop_throw_on_get")) {
jsc_context_throw(context, "Invalid property");
return jsc_value_new_undefined(context);
}
auto* foo = static_cast<Foo*>(instance);
auto* returnValue = jsc_value_new_number(context, fooGetProperty(foo, name));
checker->watch(returnValue);
return returnValue;
},
// set_property
[](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name, JSCValue* value) -> gboolean {
auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
checker->watch(context);
checker->watch(value);
if (!g_str_has_prefix(name, "prop_"))
return FALSE;
if (!jsc_value_is_number(value)) {
jsc_context_throw(context, "Invalid value set: only numbers are allowed");
return TRUE;
}
auto* foo = static_cast<Foo*>(instance);
fooSetProperty(foo, name, jsc_value_to_int32(value));
return true;
},
// has_property
[](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name) -> gboolean {
auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
checker->watch(context);
return g_str_has_prefix(name, "prop_");
},
// delete_property
[](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name) -> gboolean {
auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
checker->watch(context);
if (!g_strcmp0(name, "prop_cant_delete"))
return FALSE;
if (!g_strcmp0(name, "prop_throw_on_delete")) {
jsc_context_throw(context, "Invalid property");
return TRUE;
}
auto* foo = static_cast<Foo*>(instance);
if (!foo->properties.contains(name))
return FALSE;
foo->properties.remove(name);
return TRUE;
},
// enumerate_properties
[](JSCClass* jscClass, JSCContext* context, gpointer instance) -> char** {
auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
checker->watch(context);
auto* foo = static_cast<Foo*>(instance);
GRefPtr<GPtrArray> properties = adoptGRef(g_ptr_array_new_with_free_func(g_free));
Vector<CString> names = copyToVector(foo->properties.keys());
std::sort(names.begin(), names.end());
for (const auto& name : names) {
if (g_str_has_prefix(name.data(), "prop_enum_"))
g_ptr_array_add(properties.get(), g_strdup(name.data()));
}
if (!properties->len)
return nullptr;
g_ptr_array_add(properties.get(), nullptr);
return reinterpret_cast<char**>(g_ptr_array_free(properties.leakRef(), FALSE));
},
// padding
nullptr, nullptr, nullptr, nullptr
};
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, 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();", -1));
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;", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
g_assert_false(jsc_value_object_has_property(foo.get(), "getFoo"));
jsc_class_add_method(jscClass, "getFoo", G_CALLBACK(getFoo), nullptr, nullptr, G_TYPE_INT, 0, G_TYPE_NONE);
g_assert_true(jsc_value_object_has_property(foo.get(), "getFoo"));
properties.reset(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
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()", -1));
checker.watch(value2.get());
g_assert_true(value.get() == value2.get());
g_assert_false(jsc_value_object_has_property(foo.get(), "setFoo"));
jsc_class_add_method(jscClass, "setFoo", G_CALLBACK(setFoo), nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT);
g_assert_true(jsc_value_object_has_property(foo.get(), "setFoo"));
properties.reset(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
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()", -1));
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)", -1));
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);", -1));
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;", -1));
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;", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
g_assert_false(jsc_value_object_has_property(foo.get(), "foo"));
g_assert_false(jsc_value_object_has_property(foo2.get(), "foo"));
jsc_class_add_property(jscClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
g_assert_true(jsc_value_object_has_property(foo2.get(), "foo"));
properties.reset(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f2.foo", -1));
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", -1));
checker.watch(result.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "f2.foo", -1));
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, 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, 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", -1));
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()));
g_assert_true(jsc_value_object_has_property(f1.get(), "sibling"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(f1.get()));
g_assert_null(properties.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()));
g_assert_true(jsc_value_object_has_property(f2.get(), "sibling"));
properties.reset(jsc_value_object_enumerate_properties(f2.get()));
g_assert_null(properties.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "f2.sibling", -1));
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", -1));
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, 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)));
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
jsc_context_set_value(context.get(), "f", foo.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof Foo;", -1));
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", -1));
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", -1));
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, 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)));
g_assert_false(jsc_value_object_has_property(baz.get(), "foo"));
jsc_context_set_value(context.get(), "bz", baz.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "bz instanceof Baz;", -1));
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;", -1));
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;", -1));
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", -1));
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, 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)));
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
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;", -1));
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());
g_assert_false(jsc_value_object_has_property(foo.get(), "n"));
jsc_value_object_set_property(foo.get(), "n", property.get());
g_assert_true(jsc_value_object_has_property(foo.get(), "n"));
result = adoptGRef(jsc_context_evaluate(context.get(), "f1.n", -1));
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, 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();", -1));
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)));
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
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)", -1));
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({})", -1));
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, 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();", -1));
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, 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();", -1));
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();", -1));
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_is_instance_of(foo.get(), "wk.Foo"));
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f instanceof wk.Foo;", -1));
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);", -1));
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);", -1));
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;", -1));
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;", -1));
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", -1));
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;", -1));
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;", -1));
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", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 62);
}
{
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, &fooVTable, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(jscClass);
g_object_set_data(G_OBJECT(jscClass), "leak-checker", &checker);
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();", -1));
checker.watch(foo.get());
g_assert_true(jsc_value_is_object(foo.get()));
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
g_assert_true(jsc_value_object_has_property(foo.get(), "prop_whatever"));
g_assert_false(jsc_value_object_has_property(foo.get(), "whatever_prop"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_1", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 0);
GRefPtr<JSCValue> value = adoptGRef(jsc_value_object_get_property(foo.get(), "prop_1"));
checker.watch(value.get());
g_assert_true(value.get() == result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f.foo", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 0);
value = adoptGRef(jsc_value_object_get_property(foo.get(), "foo"));
checker.watch(value.get());
g_assert_true(value.get() == result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "'foo' in f.__proto__", -1));
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(), "'foo' in f", -1));
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(), "'prop_1' in f.__proto__", -1));
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(), "'prop_1' in f", -1));
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.prop_1 = 25", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 25);
g_assert_true(jsc_value_object_delete_property(foo.get(), "prop_1"));
g_assert_true(jsc_value_object_has_property(foo.get(), "prop_1"));
value = adoptGRef(jsc_value_object_get_property(foo.get(), "prop_1"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 0);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_cant_delete = 125", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 125);
jsc_value_object_delete_property(foo.get(), "prop_cant_delete");
g_assert_true(jsc_value_object_has_property(foo.get(), "prop_cant_delete"));
value = adoptGRef(jsc_value_object_get_property(foo.get(), "prop_cant_delete"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 125);
value = adoptGRef(jsc_value_new_number(context.get(), 42));
checker.watch(value.get());
jsc_value_object_set_property(foo.get(), "prop_1", value.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_1", -1));
checker.watch(result.get());
g_assert_true(value.get() == result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_2 = 35", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 35);
result = adoptGRef(jsc_context_evaluate(context.get(), "'prop_2' in f.__proto__", -1));
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(), "'prop_2' in f", -1));
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.prop_enum_1 = 250", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 250);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_enum_2 = 450", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 450);
properties.reset(jsc_value_object_enumerate_properties(foo.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 2);
g_assert_cmpstr(properties.get()[0], ==, "prop_enum_1");
g_assert_cmpstr(properties.get()[1], ==, "prop_enum_2");
g_assert_null(properties.get()[2]);
g_assert_null(jsc_context_get_exception(context.get()));
bool didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_throw_on_get", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_did_throw(exceptionHandler, didThrow);
g_assert_null(jsc_context_get_exception(context.get()));
didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_3 = 'not a number'", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_did_throw(exceptionHandler, didThrow);
g_assert_null(jsc_context_get_exception(context.get()));
didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
jsc_value_object_delete_property(foo.get(), "prop_throw_on_delete");
g_assert_did_throw(exceptionHandler, didThrow);
g_assert_null(jsc_context_get_exception(context.get()));
jsc_context_throw(context.get(), "Fake exception");
GRefPtr<JSCException> previousException = jsc_context_get_exception(context.get());
checker.watch(previousException.get());
didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_throw_on_get", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_did_throw(exceptionHandler, didThrow);
g_assert_true(jsc_context_get_exception(context.get()) == previousException.get());
didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_3 = 'not a number'", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_undefined(result.get()));
g_assert_did_throw(exceptionHandler, didThrow);
g_assert_true(jsc_context_get_exception(context.get()) == previousException.get());
didThrow = false;
g_assert_throw_begin(exceptionHandler, didThrow);
jsc_value_object_delete_property(foo.get(), "prop_throw_on_delete");
g_assert_did_throw(exceptionHandler, didThrow);
g_assert_true(jsc_context_get_exception(context.get()) == previousException.get());
}
}
typedef struct {
Foo parent;
int bar;
} Bar;
static Bar* barCreate()
{
Bar* bar = g_new0(Bar, 1);
new (bar) Bar();
return bar;
}
static void barFree(Bar* bar)
{
bar->~Bar();
g_free(bar);
}
static void setBar(Bar* bar, int value)
{
bar->bar = value;
}
static int getBar(Bar* bar)
{
return bar->bar;
}
static JSCClassVTable barVTable = {
// get_property
nullptr,
// set_property
nullptr,
// has_property
nullptr,
// delete_property
nullptr,
// enumerate_properties
[](JSCClass* jscClass, JSCContext* context, gpointer instance) -> char** {
auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
checker->watch(context);
auto* properties = static_cast<char**>(g_malloc0(2 * sizeof(char*)));
properties[0] = g_strdup("bar");
return properties;
},
// padding
nullptr, nullptr, nullptr, nullptr
};
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, 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, nullptr, 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();", -1));
checker.watch(result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f.__proto__ == Foo.prototype", -1));
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", -1));
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", -1));
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", -1));
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", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_false(jsc_value_to_boolean(result.get()));
g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
g_assert_false(jsc_value_object_has_property(foo.get(), "bar"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
g_assert_null(properties.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", -1));
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", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_boolean(result.get()));
g_assert_true(jsc_value_to_boolean(result.get()));
g_assert_true(jsc_value_object_has_property(bar.get(), "bar"));
g_assert_true(jsc_value_object_has_property(bar.get(), "foo"));
properties.reset(jsc_value_object_enumerate_properties(bar.get()));
g_assert_null(properties.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "b.bar = 25; b.foo = 42;", -1));
checker.watch(result.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "b.bar", -1));
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", -1));
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)", -1));
checker.watch(result.get());
value = adoptGRef(jsc_context_evaluate(context.get(), "b.foo", -1));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 84);
}
{
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, &fooVTable, reinterpret_cast<GDestroyNotify>(fooFree));
checker.watch(fooClass);
g_object_set_data(G_OBJECT(fooClass), "leak-checker", &checker);
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_property(fooClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
JSCClass* barClass = jsc_context_register_class(context.get(), "Bar", fooClass, &barVTable, reinterpret_cast<GDestroyNotify>(barFree));
checker.watch(barClass);
g_object_set_data(G_OBJECT(barClass), "leak-checker", &checker);
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> bar = adoptGRef(jsc_context_evaluate(context.get(), "b = new Bar();", -1));
checker.watch(bar.get());
g_assert_true(jsc_value_is_object(bar.get()));
g_assert_true(jsc_value_object_has_property(bar.get(), "bar"));
g_assert_true(jsc_value_object_has_property(bar.get(), "foo"));
g_assert_true(jsc_value_object_has_property(bar.get(), "prop_whatever"));
g_assert_false(jsc_value_object_has_property(bar.get(), "whatever_prop"));
GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(bar.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 1);
g_assert_cmpstr(properties.get()[0], ==, "bar");
g_assert_null(properties.get()[1]);
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_1", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 0);
GRefPtr<JSCValue> value = adoptGRef(jsc_value_object_get_property(bar.get(), "prop_1"));
checker.watch(value.get());
g_assert_true(value.get() == result.get());
g_assert_true(jsc_value_object_delete_property(bar.get(), "prop_1"));
g_assert_true(jsc_value_object_has_property(bar.get(), "prop_1"));
value = adoptGRef(jsc_value_object_get_property(bar.get(), "prop_1"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 0);
result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_cant_delete = 125", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 125);
jsc_value_object_delete_property(bar.get(), "prop_cant_delete");
g_assert_true(jsc_value_object_has_property(bar.get(), "prop_cant_delete"));
value = adoptGRef(jsc_value_object_get_property(bar.get(), "prop_cant_delete"));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 125);
result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_enum_1 = 250", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 250);
result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_enum_2 = 450", -1));
checker.watch(result.get());
g_assert_true(jsc_value_is_number(result.get()));
g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 450);
properties.reset(jsc_value_object_enumerate_properties(bar.get()));
g_assert_cmpuint(g_strv_length(properties.get()), ==, 3);
g_assert_cmpstr(properties.get()[0], ==, "bar");
g_assert_cmpstr(properties.get()[1], ==, "prop_enum_1");
g_assert_cmpstr(properties.get()[2], ==, "prop_enum_2");
g_assert_null(properties.get()[3]);
}
}
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", -1));
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));
jsc_context_clear_exception(context.get());
g_assert_null(jsc_context_get_exception(context.get()));
}
{
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;", -1));
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);
jsc_context_clear_exception(context.get());
g_assert_null(jsc_context_get_exception(context.get()));
}
{
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", -1, "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");
jsc_context_clear_exception(context.get());
g_assert_null(jsc_context_get_exception(context.get()));
}
{
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'; }", -1));
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';", -1));
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));
jsc_context_clear_exception(context.get());
g_assert_null(jsc_context_get_exception(context.get()));
}
{
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", -1));
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", -1));
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);
jsc_context_clear_exception(context.get());
g_assert_null(jsc_context_get_exception(context.get()));
}
}
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", -1));
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; });", -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()), ==, 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, 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; });", -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; });", -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", -1));
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", -1));
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", -1));
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", -1));
checker.watch(result.get());
g_assert_true(object.get() == result.get());
foo = adoptGRef(jsc_context_evaluate(context.get(), "f.foo", -1));
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, 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();", -1));
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", -1));
checker.watch(result.get());
g_assert_true(object.get() == result.get());
result = adoptGRef(jsc_context_evaluate(context.get(), "f = undefined", -1));
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 weakValueClearedCallback(JSCWeakValue* weakValue, bool* weakValueCleared)
{
*weakValueCleared = true;
g_assert_null(jsc_weak_value_get_value(weakValue));
}
static void testJSCWeakValue()
{
{
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<JSCWeakValue> weak = adoptGRef(jsc_weak_value_new(object.get()));
checker.watch(weak.get());
bool weakValueCleared = false;
g_signal_connect(weak.get(), "cleared", G_CALLBACK(weakValueClearedCallback), &weakValueCleared);
jsc_context_set_value(context.get(), "foo", object.get());
jscContextGarbageCollect(context.get());
g_assert_false(weakValueCleared);
GRefPtr<JSCValue> foo = adoptGRef(jsc_context_get_value(context.get(), "foo"));
checker.watch(foo.get());
g_assert_true(object.get() == foo.get());
GRefPtr<JSCValue> weakFoo = adoptGRef(jsc_weak_value_get_value(weak.get()));
checker.watch(weakFoo.get());
g_assert_true(foo.get() == weakFoo.get());
GRefPtr<JSCValue> undefinedValue = adoptGRef(jsc_value_new_undefined(context.get()));
checker.watch(undefinedValue.get());
jsc_context_set_value(context.get(), "foo", undefinedValue.get());
weakFoo = nullptr;
foo = nullptr;
object = nullptr;
// The value is still reachable, but unprotected.
g_assert_false(weakValueCleared);
weakFoo = adoptGRef(jsc_weak_value_get_value(weak.get()));
checker.watch(weakFoo.get());
g_assert_true(jsc_value_is_object(weakFoo.get()));
weakFoo = nullptr;
jscContextGarbageCollect(context.get());
g_assert_true(weakValueCleared);
g_assert_null(jsc_weak_value_get_value(weak.get()));
}
{
LeakChecker checker;
GRefPtr<JSCWeakValue> weakObject;
bool weakValueCleared = false;
{
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> object = adoptGRef(jsc_context_evaluate(context.get(), "obj = {};", -1));
checker.watch(object.get());
g_assert_true(JSC_IS_VALUE(object.get()));
g_assert_true(jsc_value_is_object(object.get()));
weakObject = adoptGRef(jsc_weak_value_new(object.get()));
checker.watch(weakObject.get());
g_signal_connect(weakObject.get(), "cleared", G_CALLBACK(weakValueClearedCallback), &weakValueCleared);
object = adoptGRef(jsc_context_evaluate(context.get(), "obj = null", -1));
checker.watch(object.get());
g_assert_false(weakValueCleared);
}
g_assert_true(weakValueCleared);
g_assert_null(jsc_weak_value_get_value(weakObject.get()));
}
{
LeakChecker checker;
GRefPtr<JSCWeakValue> weakObj;
bool weakObjValueCleared = false;
GRefPtr<JSCWeakValue> weakStr;
bool weakStrValueCleared = false;
GRefPtr<JSCWeakValue> weakPrimitive;
bool weakPrimitiveValueCleared = false;
{
GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
checker.watch(context.get());
ExceptionHandler exceptionHandler(context.get());
GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "obj = { 'foo' : 'bar' }; str = 'Hello World'; primitive = 25;", -1));
checker.watch(result.get());
GRefPtr<JSCValue> value = adoptGRef(jsc_context_get_value(context.get(), "obj"));
checker.watch(value.get());
weakObj = adoptGRef(jsc_weak_value_new(value.get()));
checker.watch(weakObj.get());
g_signal_connect(weakObj.get(), "cleared", G_CALLBACK(weakValueClearedCallback), &weakObjValueCleared);
value = adoptGRef(jsc_context_get_value(context.get(), "str"));
checker.watch(value.get());
weakStr = adoptGRef(jsc_weak_value_new(value.get()));
checker.watch(weakStr.get());
g_signal_connect(weakStr.get(), "cleared", G_CALLBACK(weakValueClearedCallback), &weakStrValueCleared);
value = adoptGRef(jsc_context_get_value(context.get(), "primitive"));
checker.watch(value.get());
weakPrimitive = adoptGRef(jsc_weak_value_new(value.get()));
checker.watch(weakPrimitive.get());
g_signal_connect(weakPrimitive.get(), "cleared", G_CALLBACK(weakValueClearedCallback), &weakPrimitiveValueCleared);
value = nullptr;
jscContextGarbageCollect(context.get());
g_assert_false(weakObjValueCleared);
g_assert_false(weakStrValueCleared);
g_assert_false(weakPrimitiveValueCleared);
value = adoptGRef(jsc_weak_value_get_value(weakObj.get()));
checker.watch(value.get());
g_assert_true(jsc_value_is_object(value.get()));
value = adoptGRef(jsc_weak_value_get_value(weakStr.get()));
checker.watch(value.get());
g_assert_true(jsc_value_is_string(value.get()));
value = adoptGRef(jsc_weak_value_get_value(weakPrimitive.get()));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
value = nullptr;
result = adoptGRef(jsc_context_evaluate(context.get(), "str = undefined", -1));
checker.watch(result.get());
jscContextGarbageCollect(context.get());
g_assert_true(weakStrValueCleared);
g_assert_false(weakObjValueCleared);
g_assert_false(weakPrimitiveValueCleared);
g_assert_null(jsc_weak_value_get_value(weakStr.get()));
result = adoptGRef(jsc_context_evaluate(context.get(), "f = undefined", -1));
checker.watch(result.get());
jscContextGarbageCollect(context.get());
// Non-string primitve values are not garbage collected, the weak value
// will be cleared when the global object is destroyed.
g_assert_false(weakPrimitiveValueCleared);
g_assert_false(weakObjValueCleared);
g_assert_true(weakStrValueCleared);
value = adoptGRef(jsc_weak_value_get_value(weakPrimitive.get()));
checker.watch(value.get());
g_assert_true(jsc_value_is_number(value.get()));
value = nullptr;
result = adoptGRef(jsc_context_evaluate(context.get(), "obj = undefined", -1));
checker.watch(result.get());
jscContextGarbageCollect(context.get());
g_assert_true(weakObjValueCleared);
g_assert_true(weakStrValueCleared);
g_assert_false(weakPrimitiveValueCleared);
g_assert_null(jsc_weak_value_get_value(weakObj.get()));
weakObjValueCleared = false;
weakStrValueCleared = false;
}
// Context is now destroyed, only the primitive value should be notified.
g_assert_true(weakPrimitiveValueCleared);
g_assert_false(weakObjValueCleared);
g_assert_false(weakStrValueCleared);
g_assert_null(jsc_weak_value_get_value(weakPrimitive.get()));
}
}
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, 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();", -1));
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();
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", -1
));
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();
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", -1);
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/weak-value", testJSCWeakValue);
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();
}