testNewObject.cpp (7502B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 */ 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject 9 #include "js/CallAndConstruct.h" // JS::Construct 10 #include "js/Object.h" // JS::GetClass 11 #include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement 12 #include "jsapi-tests/tests.h" 13 #include "vm/PlainObject.h" // js::PlainObject::class_ 14 15 #include "vm/NativeObject-inl.h" 16 17 using namespace js; 18 19 static bool constructHook(JSContext* cx, unsigned argc, JS::Value* vp) { 20 JS::CallArgs args = CallArgsFromVp(argc, vp); 21 22 // Check that arguments were passed properly from JS_New. 23 24 JS::RootedObject obj(cx, JS_NewPlainObject(cx)); 25 if (!obj) { 26 JS_ReportErrorASCII(cx, "test failed, could not construct object"); 27 return false; 28 } 29 if (strcmp(JS::GetClass(obj)->name, "Object") != 0) { 30 JS_ReportErrorASCII(cx, "test failed, wrong class for 'this'"); 31 return false; 32 } 33 if (args.length() != 3) { 34 JS_ReportErrorASCII(cx, "test failed, argc == %d", args.length()); 35 return false; 36 } 37 if (!args[0].isInt32() || args[2].toInt32() != 2) { 38 JS_ReportErrorASCII(cx, "test failed, wrong value in args[2]"); 39 return false; 40 } 41 if (!args.isConstructing()) { 42 JS_ReportErrorASCII(cx, "test failed, not constructing"); 43 return false; 44 } 45 46 // Perform a side-effect to indicate that this hook was actually called. 47 JS::RootedValue value(cx, args[0]); 48 JS::RootedObject callee(cx, &args.callee()); 49 if (!JS_SetElement(cx, callee, 0, value)) { 50 return false; 51 } 52 53 args.rval().setObject(*obj); 54 55 // trash the argv, perversely 56 args[0].setUndefined(); 57 args[1].setUndefined(); 58 args[2].setUndefined(); 59 60 return true; 61 } 62 63 BEGIN_TEST(testNewObject_1) { 64 static const size_t N = 1000; 65 JS::RootedValueVector argv(cx); 66 CHECK(argv.resize(N)); 67 68 JS::RootedValue Array(cx); 69 EVAL("Array", &Array); 70 71 bool isArray; 72 73 // With no arguments. 74 JS::RootedObject obj(cx); 75 CHECK(JS::Construct(cx, Array, JS::HandleValueArray::empty(), &obj)); 76 CHECK(JS::IsArrayObject(cx, obj, &isArray)); 77 CHECK(isArray); 78 uint32_t len; 79 CHECK(JS::GetArrayLength(cx, obj, &len)); 80 CHECK_EQUAL(len, 0u); 81 82 // With one argument. 83 argv[0].setInt32(4); 84 CHECK(JS::Construct(cx, Array, JS::HandleValueArray::subarray(argv, 0, 1), 85 &obj)); 86 CHECK(JS::IsArrayObject(cx, obj, &isArray)); 87 CHECK(isArray); 88 CHECK(JS::GetArrayLength(cx, obj, &len)); 89 CHECK_EQUAL(len, 4u); 90 91 // With N arguments. 92 for (size_t i = 0; i < N; i++) { 93 argv[i].setInt32(i); 94 } 95 CHECK(JS::Construct(cx, Array, JS::HandleValueArray::subarray(argv, 0, N), 96 &obj)); 97 CHECK(JS::IsArrayObject(cx, obj, &isArray)); 98 CHECK(isArray); 99 CHECK(JS::GetArrayLength(cx, obj, &len)); 100 CHECK_EQUAL(len, N); 101 JS::RootedValue v(cx); 102 CHECK(JS_GetElement(cx, obj, N - 1, &v)); 103 CHECK(v.isInt32(N - 1)); 104 105 // With JSClass.construct. 106 static const JSClassOps clsOps = { 107 nullptr, // addProperty 108 nullptr, // delProperty 109 nullptr, // enumerate 110 nullptr, // newEnumerate 111 nullptr, // resolve 112 nullptr, // mayResolve 113 nullptr, // finalize 114 nullptr, // call 115 constructHook, // construct 116 nullptr, // trace 117 }; 118 static const JSClass cls = { 119 "testNewObject_1", 120 0, 121 &clsOps, 122 }; 123 JS::RootedObject ctor(cx, JS_NewObject(cx, &cls)); 124 CHECK(ctor); 125 JS::RootedValue ctorVal(cx, JS::ObjectValue(*ctor)); 126 CHECK(JS::Construct(cx, ctorVal, JS::HandleValueArray::subarray(argv, 0, 3), 127 &obj)); 128 CHECK(JS_GetElement(cx, ctor, 0, &v)); 129 CHECK(v.isInt32(0)); 130 131 return true; 132 } 133 END_TEST(testNewObject_1) 134 135 BEGIN_TEST(testNewObject_IsMapObject) { 136 // Test IsMapObject and IsSetObject 137 138 JS::RootedValue vMap(cx); 139 EVAL("Map", &vMap); 140 141 bool isMap = false; 142 bool isSet = false; 143 JS::RootedObject mapObj(cx); 144 CHECK(JS::Construct(cx, vMap, JS::HandleValueArray::empty(), &mapObj)); 145 CHECK(JS::IsMapObject(cx, mapObj, &isMap)); 146 CHECK(isMap); 147 CHECK(JS::IsSetObject(cx, mapObj, &isSet)); 148 CHECK(!isSet); 149 150 JS::RootedValue vSet(cx); 151 EVAL("Set", &vSet); 152 153 JS::RootedObject setObj(cx); 154 CHECK(JS::Construct(cx, vSet, JS::HandleValueArray::empty(), &setObj)); 155 CHECK(JS::IsMapObject(cx, setObj, &isMap)); 156 CHECK(!isMap); 157 CHECK(JS::IsSetObject(cx, setObj, &isSet)); 158 CHECK(isSet); 159 160 return true; 161 } 162 END_TEST(testNewObject_IsMapObject) 163 164 static const JSClass Base_class = { 165 "Base", 166 JSCLASS_HAS_RESERVED_SLOTS(8), // flags 167 }; 168 169 BEGIN_TEST(testNewObject_Subclassing) { 170 JSObject* proto = 171 JS_InitClass(cx, global, nullptr, nullptr, "Base", Base_constructor, 0, 172 nullptr, nullptr, nullptr, nullptr); 173 if (!proto) { 174 return false; 175 } 176 177 CHECK_EQUAL(JS::GetClass(proto), &PlainObject::class_); 178 179 // Calling Base without `new` should fail with a TypeError. 180 JS::RootedValue expectedError(cx); 181 EVAL("TypeError", &expectedError); 182 JS::RootedValue actualError(cx); 183 EVAL( 184 "try {\n" 185 " Base();\n" 186 "} catch (e) {\n" 187 " e.constructor;\n" 188 "}\n", 189 &actualError); 190 CHECK_SAME(actualError, expectedError); 191 192 // Check prototype chains when a JS class extends a base class that's 193 // implemented in C++ using JS_NewObjectForConstructor. 194 EXEC( 195 "class MyClass extends Base {\n" 196 " ok() { return true; }\n" 197 "}\n" 198 "let myObj = new MyClass();\n"); 199 200 JS::RootedValue result(cx); 201 EVAL("myObj.ok()", &result); 202 CHECK_SAME(result, JS::TrueValue()); 203 204 EVAL("myObj.__proto__ === MyClass.prototype", &result); 205 CHECK_SAME(result, JS::TrueValue()); 206 EVAL("myObj.__proto__.__proto__ === Base.prototype", &result); 207 CHECK_SAME(result, JS::TrueValue()); 208 209 EVAL("myObj", &result); 210 CHECK_EQUAL(JS::GetClass(&result.toObject()), &Base_class); 211 212 // All reserved slots are initialized to undefined. 213 for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(&Base_class); i++) { 214 CHECK_SAME(JS::GetReservedSlot(&result.toObject(), i), 215 JS::UndefinedValue()); 216 } 217 218 return true; 219 } 220 221 static bool Base_constructor(JSContext* cx, unsigned argc, JS::Value* vp) { 222 JS::CallArgs args = CallArgsFromVp(argc, vp); 223 JS::RootedObject obj(cx, JS_NewObjectForConstructor(cx, &Base_class, args)); 224 if (!obj) { 225 return false; 226 } 227 args.rval().setObject(*obj); 228 return true; 229 } 230 231 END_TEST(testNewObject_Subclassing) 232 233 static const JSClass TestClass = { 234 "TestObject", 235 JSCLASS_HAS_RESERVED_SLOTS(0), 236 }; 237 238 BEGIN_TEST(testNewObject_elements) { 239 Rooted<NativeObject*> obj( 240 cx, NewBuiltinClassInstance(cx, &TestClass, GenericObject)); 241 CHECK(obj); 242 CHECK(!obj->isTenured()); 243 CHECK(obj->hasEmptyElements()); 244 CHECK(!obj->hasFixedElements()); 245 CHECK(!obj->hasDynamicElements()); 246 247 CHECK(obj->ensureElements(cx, 1)); 248 CHECK(!obj->hasEmptyElements()); 249 CHECK(!obj->hasFixedElements()); 250 CHECK(obj->hasDynamicElements()); 251 252 RootedObject array(cx, NewArrayObject(cx, 1)); 253 CHECK(array); 254 obj = &array->as<NativeObject>(); 255 CHECK(!obj->isTenured()); 256 CHECK(!obj->hasEmptyElements()); 257 CHECK(obj->hasFixedElements()); 258 CHECK(!obj->hasDynamicElements()); 259 260 return true; 261 } 262 END_TEST(testNewObject_elements)