diff options
Diffstat (limited to 'js/src/jsapi-tests/testResolveRecursion.cpp')
-rw-r--r-- | js/src/jsapi-tests/testResolveRecursion.cpp | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testResolveRecursion.cpp b/js/src/jsapi-tests/testResolveRecursion.cpp new file mode 100644 index 0000000000..44b5cd35a3 --- /dev/null +++ b/js/src/jsapi-tests/testResolveRecursion.cpp @@ -0,0 +1,184 @@ +/* -*- 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/Object.h" // JS::GetReservedSlot, JS::SetReservedSlot +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById +#include "jsapi-tests/tests.h" + +/* + * Test that resolve hook recursion for the same object and property is + * prevented. + */ +BEGIN_TEST(testResolveRecursion) { + static const JSClassOps my_resolve_classOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + my_resolve, // resolve + nullptr, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace + }; + + static const JSClass my_resolve_class = { + "MyResolve", JSCLASS_HAS_RESERVED_SLOTS(SlotCount), &my_resolve_classOps}; + + obj1.init(cx, JS_NewObject(cx, &my_resolve_class)); + CHECK(obj1); + obj2.init(cx, JS_NewObject(cx, &my_resolve_class)); + CHECK(obj2); + JS::SetReservedSlot(obj1, TestSlot, JS::PrivateValue(this)); + JS::SetReservedSlot(obj2, TestSlot, JS::PrivateValue(this)); + + JS::RootedValue obj1Val(cx, JS::ObjectValue(*obj1)); + JS::RootedValue obj2Val(cx, JS::ObjectValue(*obj2)); + CHECK(JS_DefineProperty(cx, global, "obj1", obj1Val, 0)); + CHECK(JS_DefineProperty(cx, global, "obj2", obj2Val, 0)); + + resolveEntryCount = 0; + resolveExitCount = 0; + + /* Start the essence of the test via invoking the first resolve hook. */ + JS::RootedValue v(cx); + EVAL("obj1.x", &v); + CHECK(v.isFalse()); + CHECK_EQUAL(resolveEntryCount, 4); + CHECK_EQUAL(resolveExitCount, 4); + + obj1 = nullptr; + obj2 = nullptr; + return true; +} + +enum Slots { TestSlot, SlotCount }; + +JS::PersistentRootedObject obj1; +JS::PersistentRootedObject obj2; +int resolveEntryCount; +int resolveExitCount; + +struct AutoIncrCounters { + explicit AutoIncrCounters(cls_testResolveRecursion* t) : t(t) { + t->resolveEntryCount++; + } + + ~AutoIncrCounters() { t->resolveExitCount++; } + + cls_testResolveRecursion* t; +}; + +bool doResolve(JS::HandleObject obj, JS::HandleId id, bool* resolvedp) { + CHECK_EQUAL(resolveExitCount, 0); + AutoIncrCounters incr(this); + CHECK(obj == obj1 || obj == obj2); + + CHECK(id.isString()); + + JSLinearString* str = JS_EnsureLinearString(cx, id.toString()); + CHECK(str); + JS::RootedValue v(cx); + if (JS_LinearStringEqualsLiteral(str, "x")) { + if (obj == obj1) { + /* First resolve hook invocation. */ + CHECK_EQUAL(resolveEntryCount, 1); + EVAL("obj2.y = true", &v); + CHECK(v.isTrue()); + CHECK(JS_DefinePropertyById(cx, obj, id, JS::FalseHandleValue, + JSPROP_RESOLVING)); + *resolvedp = true; + return true; + } + if (obj == obj2) { + CHECK_EQUAL(resolveEntryCount, 4); + *resolvedp = false; + return true; + } + } else if (JS_LinearStringEqualsLiteral(str, "y")) { + if (obj == obj2) { + CHECK_EQUAL(resolveEntryCount, 2); + CHECK(JS_DefinePropertyById(cx, obj, id, JS::NullHandleValue, + JSPROP_RESOLVING)); + EVAL("obj1.x", &v); + CHECK(v.isUndefined()); + EVAL("obj1.y", &v); + CHECK(v.isInt32(0)); + *resolvedp = true; + return true; + } + if (obj == obj1) { + CHECK_EQUAL(resolveEntryCount, 3); + EVAL("obj1.x", &v); + CHECK(v.isUndefined()); + EVAL("obj1.y", &v); + CHECK(v.isUndefined()); + EVAL("obj2.y", &v); + CHECK(v.isNull()); + EVAL("obj2.x", &v); + CHECK(v.isUndefined()); + EVAL("obj1.y = 0", &v); + CHECK(v.isInt32(0)); + *resolvedp = true; + return true; + } + } + CHECK(false); + return false; +} + +static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, + bool* resolvedp) { + void* p = JS::GetReservedSlot(obj, TestSlot).toPrivate(); + return static_cast<cls_testResolveRecursion*>(p)->doResolve(obj, id, + resolvedp); +} +END_TEST(testResolveRecursion) + +/* + * Test that JS_InitStandardClasses does not cause resolve hooks to be called. + * + * (XPConnect apparently does have global classes, such as the one created by + * nsMessageManagerScriptExecutor::InitChildGlobalInternal(), that have resolve + * hooks which can call back into JS, and on which JS_InitStandardClasses is + * called. Calling back into JS in the middle of resolving `undefined` is bad.) + */ +BEGIN_TEST(testResolveRecursion_InitStandardClasses) { + CHECK(JS::InitRealmStandardClasses(cx)); + return true; +} + +const JSClass* getGlobalClass() override { + static const JSClassOps myGlobalClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + my_resolve, // resolve + nullptr, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // construct + JS_GlobalObjectTraceHook, // trace + }; + + static const JSClass myGlobalClass = { + "testResolveRecursion_InitStandardClasses_myGlobalClass", + JSCLASS_GLOBAL_FLAGS, &myGlobalClassOps}; + + return &myGlobalClass; +} + +static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, + bool* resolvedp) { + MOZ_ASSERT_UNREACHABLE( + "resolve hook should not be called from InitStandardClasses"); + JS_ReportErrorASCII(cx, "FAIL"); + return false; +} +END_TEST(testResolveRecursion_InitStandardClasses) |