diff options
Diffstat (limited to 'js/src/jsapi-tests/testResolveRecursion.cpp')
-rw-r--r-- | js/src/jsapi-tests/testResolveRecursion.cpp | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testResolveRecursion.cpp b/js/src/jsapi-tests/testResolveRecursion.cpp new file mode 100644 index 000000000..9c38e38c7 --- /dev/null +++ b/js/src/jsapi-tests/testResolveRecursion.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + */ +/* 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 "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, // add + nullptr, // delete + nullptr, // get + nullptr, // set + nullptr, // enumerate + my_resolve + }; + + static const JSClass my_resolve_class = { + "MyResolve", + JSCLASS_HAS_PRIVATE, + &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_SetPrivate(obj1, this); + JS_SetPrivate(obj2, 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; +} + +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(JSID_IS_STRING(id)); + + JSFlatString* str = JS_FlattenString(cx, JSID_TO_STRING(id)); + CHECK(str); + JS::RootedValue v(cx); + if (JS_FlatStringEqualsAscii(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_FlatStringEqualsAscii(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) +{ + return static_cast<cls_testResolveRecursion*>(JS_GetPrivate(obj))-> + 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_InitStandardClasses(cx, global)); + return true; +} + +const JSClass* getGlobalClass() override { + static const JSClassOps myGlobalClassOps = { + nullptr, // add + nullptr, // delete + nullptr, // get + nullptr, // set + nullptr, // enumerate + my_resolve, + nullptr, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // hasInstance + nullptr, // construct + JS_GlobalObjectTraceHook + }; + + 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) |