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