Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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)