diff options
Diffstat (limited to 'js/src/jsapi-tests/testNewObject.cpp')
-rw-r--r-- | js/src/jsapi-tests/testNewObject.cpp | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testNewObject.cpp b/js/src/jsapi-tests/testNewObject.cpp new file mode 100644 index 0000000000..bdddede03b --- /dev/null +++ b/js/src/jsapi-tests/testNewObject.cpp @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject +#include "js/CallAndConstruct.h" // JS::Construct +#include "js/Object.h" // JS::GetClass +#include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement +#include "jsapi-tests/tests.h" +#include "vm/PlainObject.h" // js::PlainObject::class_ + +#include "vm/NativeObject-inl.h" + +using namespace js; + +static bool constructHook(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + // Check that arguments were passed properly from JS_New. + + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + JS_ReportErrorASCII(cx, "test failed, could not construct object"); + return false; + } + if (strcmp(JS::GetClass(obj)->name, "Object") != 0) { + JS_ReportErrorASCII(cx, "test failed, wrong class for 'this'"); + return false; + } + if (args.length() != 3) { + JS_ReportErrorASCII(cx, "test failed, argc == %d", args.length()); + return false; + } + if (!args[0].isInt32() || args[2].toInt32() != 2) { + JS_ReportErrorASCII(cx, "test failed, wrong value in args[2]"); + return false; + } + if (!args.isConstructing()) { + JS_ReportErrorASCII(cx, "test failed, not constructing"); + return false; + } + + // Perform a side-effect to indicate that this hook was actually called. + JS::RootedValue value(cx, args[0]); + JS::RootedObject callee(cx, &args.callee()); + if (!JS_SetElement(cx, callee, 0, value)) { + return false; + } + + args.rval().setObject(*obj); + + // trash the argv, perversely + args[0].setUndefined(); + args[1].setUndefined(); + args[2].setUndefined(); + + return true; +} + +BEGIN_TEST(testNewObject_1) { + static const size_t N = 1000; + JS::RootedValueVector argv(cx); + CHECK(argv.resize(N)); + + JS::RootedValue Array(cx); + EVAL("Array", &Array); + + bool isArray; + + // With no arguments. + JS::RootedObject obj(cx); + CHECK(JS::Construct(cx, Array, JS::HandleValueArray::empty(), &obj)); + CHECK(JS::IsArrayObject(cx, obj, &isArray)); + CHECK(isArray); + uint32_t len; + CHECK(JS::GetArrayLength(cx, obj, &len)); + CHECK_EQUAL(len, 0u); + + // With one argument. + argv[0].setInt32(4); + CHECK(JS::Construct(cx, Array, JS::HandleValueArray::subarray(argv, 0, 1), + &obj)); + CHECK(JS::IsArrayObject(cx, obj, &isArray)); + CHECK(isArray); + CHECK(JS::GetArrayLength(cx, obj, &len)); + CHECK_EQUAL(len, 4u); + + // With N arguments. + for (size_t i = 0; i < N; i++) { + argv[i].setInt32(i); + } + CHECK(JS::Construct(cx, Array, JS::HandleValueArray::subarray(argv, 0, N), + &obj)); + CHECK(JS::IsArrayObject(cx, obj, &isArray)); + CHECK(isArray); + CHECK(JS::GetArrayLength(cx, obj, &len)); + CHECK_EQUAL(len, N); + JS::RootedValue v(cx); + CHECK(JS_GetElement(cx, obj, N - 1, &v)); + CHECK(v.isInt32(N - 1)); + + // With JSClass.construct. + static const JSClassOps clsOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + nullptr, // finalize + nullptr, // call + constructHook, // construct + nullptr, // trace + }; + static const JSClass cls = {"testNewObject_1", 0, &clsOps}; + JS::RootedObject ctor(cx, JS_NewObject(cx, &cls)); + CHECK(ctor); + JS::RootedValue ctorVal(cx, JS::ObjectValue(*ctor)); + CHECK(JS::Construct(cx, ctorVal, JS::HandleValueArray::subarray(argv, 0, 3), + &obj)); + CHECK(JS_GetElement(cx, ctor, 0, &v)); + CHECK(v.isInt32(0)); + + return true; +} +END_TEST(testNewObject_1) + +BEGIN_TEST(testNewObject_IsMapObject) { + // Test IsMapObject and IsSetObject + + JS::RootedValue vMap(cx); + EVAL("Map", &vMap); + + bool isMap = false; + bool isSet = false; + JS::RootedObject mapObj(cx); + CHECK(JS::Construct(cx, vMap, JS::HandleValueArray::empty(), &mapObj)); + CHECK(JS::IsMapObject(cx, mapObj, &isMap)); + CHECK(isMap); + CHECK(JS::IsSetObject(cx, mapObj, &isSet)); + CHECK(!isSet); + + JS::RootedValue vSet(cx); + EVAL("Set", &vSet); + + JS::RootedObject setObj(cx); + CHECK(JS::Construct(cx, vSet, JS::HandleValueArray::empty(), &setObj)); + CHECK(JS::IsMapObject(cx, setObj, &isMap)); + CHECK(!isMap); + CHECK(JS::IsSetObject(cx, setObj, &isSet)); + CHECK(isSet); + + return true; +} +END_TEST(testNewObject_IsMapObject) + +static const JSClass Base_class = { + "Base", + JSCLASS_HAS_RESERVED_SLOTS(8), // flags +}; + +BEGIN_TEST(testNewObject_Subclassing) { + JSObject* proto = + JS_InitClass(cx, global, nullptr, nullptr, "Base", Base_constructor, 0, + nullptr, nullptr, nullptr, nullptr); + if (!proto) { + return false; + } + + CHECK_EQUAL(JS::GetClass(proto), &PlainObject::class_); + + // Calling Base without `new` should fail with a TypeError. + JS::RootedValue expectedError(cx); + EVAL("TypeError", &expectedError); + JS::RootedValue actualError(cx); + EVAL( + "try {\n" + " Base();\n" + "} catch (e) {\n" + " e.constructor;\n" + "}\n", + &actualError); + CHECK_SAME(actualError, expectedError); + + // Check prototype chains when a JS class extends a base class that's + // implemented in C++ using JS_NewObjectForConstructor. + EXEC( + "class MyClass extends Base {\n" + " ok() { return true; }\n" + "}\n" + "let myObj = new MyClass();\n"); + + JS::RootedValue result(cx); + EVAL("myObj.ok()", &result); + CHECK_SAME(result, JS::TrueValue()); + + EVAL("myObj.__proto__ === MyClass.prototype", &result); + CHECK_SAME(result, JS::TrueValue()); + EVAL("myObj.__proto__.__proto__ === Base.prototype", &result); + CHECK_SAME(result, JS::TrueValue()); + + EVAL("myObj", &result); + CHECK_EQUAL(JS::GetClass(&result.toObject()), &Base_class); + + // All reserved slots are initialized to undefined. + for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(&Base_class); i++) { + CHECK_SAME(JS::GetReservedSlot(&result.toObject(), i), + JS::UndefinedValue()); + } + + return true; +} + +static bool Base_constructor(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, JS_NewObjectForConstructor(cx, &Base_class, args)); + if (!obj) { + return false; + } + args.rval().setObject(*obj); + return true; +} + +END_TEST(testNewObject_Subclassing) + +static const JSClass TestClass = {"TestObject", JSCLASS_HAS_RESERVED_SLOTS(0)}; + +BEGIN_TEST(testNewObject_elements) { + Rooted<NativeObject*> obj( + cx, NewBuiltinClassInstance(cx, &TestClass, GenericObject)); + CHECK(obj); + CHECK(!obj->isTenured()); + CHECK(obj->hasEmptyElements()); + CHECK(!obj->hasFixedElements()); + CHECK(!obj->hasDynamicElements()); + + CHECK(obj->ensureElements(cx, 1)); + CHECK(!obj->hasEmptyElements()); + CHECK(!obj->hasFixedElements()); + CHECK(obj->hasDynamicElements()); + + RootedObject array(cx, NewArrayObject(cx, 1)); + CHECK(array); + obj = &array->as<NativeObject>(); + CHECK(!obj->isTenured()); + CHECK(!obj->hasEmptyElements()); + CHECK(obj->hasFixedElements()); + CHECK(!obj->hasDynamicElements()); + + return true; +} +END_TEST(testNewObject_elements) |