michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jsapi-tests/tests.h" michael@0: michael@0: /* michael@0: * Test that resolve hook recursion for the same object and property is michael@0: * prevented. michael@0: */ michael@0: michael@0: BEGIN_TEST(testResolveRecursion) michael@0: { michael@0: static const JSClass my_resolve_class = { michael@0: "MyResolve", michael@0: JSCLASS_NEW_RESOLVE | JSCLASS_HAS_PRIVATE, michael@0: michael@0: JS_PropertyStub, // add michael@0: JS_DeletePropertyStub, // delete michael@0: JS_PropertyStub, // get michael@0: JS_StrictPropertyStub, // set michael@0: JS_EnumerateStub, michael@0: (JSResolveOp) my_resolve, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: obj1 = obj2 = nullptr; michael@0: JS::AddObjectRoot(cx, &obj1); michael@0: JS::AddObjectRoot(cx, &obj2); michael@0: michael@0: obj1 = JS_NewObject(cx, &my_resolve_class, JS::NullPtr(), JS::NullPtr()); michael@0: CHECK(obj1); michael@0: obj2 = JS_NewObject(cx, &my_resolve_class, JS::NullPtr(), JS::NullPtr()); michael@0: CHECK(obj2); michael@0: JS_SetPrivate(obj1, this); michael@0: JS_SetPrivate(obj2, this); michael@0: michael@0: JS::RootedValue obj1Val(cx, ObjectValue(*obj1)); michael@0: JS::RootedValue obj2Val(cx, ObjectValue(*obj2)); michael@0: CHECK(JS_DefineProperty(cx, global, "obj1", obj1Val, 0)); michael@0: CHECK(JS_DefineProperty(cx, global, "obj2", obj2Val, 0)); michael@0: michael@0: resolveEntryCount = 0; michael@0: resolveExitCount = 0; michael@0: michael@0: /* Start the essence of the test via invoking the first resolve hook. */ michael@0: JS::RootedValue v(cx); michael@0: EVAL("obj1.x", &v); michael@0: CHECK_SAME(v, JSVAL_FALSE); michael@0: CHECK_EQUAL(resolveEntryCount, 4); michael@0: CHECK_EQUAL(resolveExitCount, 4); michael@0: michael@0: obj1 = nullptr; michael@0: obj2 = nullptr; michael@0: JS::RemoveObjectRoot(cx, &obj1); michael@0: JS::RemoveObjectRoot(cx, &obj2); michael@0: return true; michael@0: } michael@0: michael@0: JS::Heap obj1; michael@0: JS::Heap obj2; michael@0: unsigned resolveEntryCount; michael@0: unsigned resolveExitCount; michael@0: michael@0: struct AutoIncrCounters { michael@0: michael@0: AutoIncrCounters(cls_testResolveRecursion *t) : t(t) { michael@0: t->resolveEntryCount++; michael@0: } michael@0: michael@0: ~AutoIncrCounters() { michael@0: t->resolveExitCount++; michael@0: } michael@0: michael@0: cls_testResolveRecursion *t; michael@0: }; michael@0: michael@0: bool michael@0: doResolve(JS::HandleObject obj, JS::HandleId id, JS::MutableHandleObject objp) michael@0: { michael@0: CHECK_EQUAL(resolveExitCount, 0); michael@0: AutoIncrCounters incr(this); michael@0: CHECK_EQUAL(obj, obj1 || obj == obj2); michael@0: michael@0: CHECK(JSID_IS_STRING(id)); michael@0: michael@0: JSFlatString *str = JS_FlattenString(cx, JSID_TO_STRING(id)); michael@0: CHECK(str); michael@0: JS::RootedValue v(cx); michael@0: if (JS_FlatStringEqualsAscii(str, "x")) { michael@0: if (obj == obj1) { michael@0: /* First resolve hook invocation. */ michael@0: CHECK_EQUAL(resolveEntryCount, 1); michael@0: EVAL("obj2.y = true", &v); michael@0: CHECK_SAME(v, JSVAL_TRUE); michael@0: CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_FALSE, nullptr, nullptr, 0)); michael@0: objp.set(obj); michael@0: return true; michael@0: } michael@0: if (obj == obj2) { michael@0: CHECK_EQUAL(resolveEntryCount, 4); michael@0: objp.set(nullptr); michael@0: return true; michael@0: } michael@0: } else if (JS_FlatStringEqualsAscii(str, "y")) { michael@0: if (obj == obj2) { michael@0: CHECK_EQUAL(resolveEntryCount, 2); michael@0: CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_NULL, nullptr, nullptr, 0)); michael@0: EVAL("obj1.x", &v); michael@0: CHECK(JSVAL_IS_VOID(v)); michael@0: EVAL("obj1.y", &v); michael@0: CHECK_SAME(v, JSVAL_ZERO); michael@0: objp.set(obj); michael@0: return true; michael@0: } michael@0: if (obj == obj1) { michael@0: CHECK_EQUAL(resolveEntryCount, 3); michael@0: EVAL("obj1.x", &v); michael@0: CHECK(JSVAL_IS_VOID(v)); michael@0: EVAL("obj1.y", &v); michael@0: CHECK(JSVAL_IS_VOID(v)); michael@0: EVAL("obj2.y", &v); michael@0: CHECK(JSVAL_IS_NULL(v)); michael@0: EVAL("obj2.x", &v); michael@0: CHECK(JSVAL_IS_VOID(v)); michael@0: EVAL("obj1.y = 0", &v); michael@0: CHECK_SAME(v, JSVAL_ZERO); michael@0: objp.set(obj); michael@0: return true; michael@0: } michael@0: } michael@0: CHECK(false); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: my_resolve(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleObject objp) michael@0: { michael@0: return static_cast(JS_GetPrivate(obj))-> michael@0: doResolve(obj, id, objp); michael@0: } michael@0: michael@0: END_TEST(testResolveRecursion)