testResolveRecursion.cpp (5914B)
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/Object.h" // JS::GetReservedSlot, JS::SetReservedSlot 9 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById 10 #include "jsapi-tests/tests.h" 11 12 /* 13 * Test that resolve hook recursion for the same object and property is 14 * prevented. 15 */ 16 BEGIN_TEST(testResolveRecursion) { 17 static const JSClassOps my_resolve_classOps = { 18 nullptr, // addProperty 19 nullptr, // delProperty 20 nullptr, // enumerate 21 nullptr, // newEnumerate 22 my_resolve, // resolve 23 nullptr, // mayResolve 24 nullptr, // finalize 25 nullptr, // call 26 nullptr, // construct 27 nullptr, // trace 28 }; 29 30 static const JSClass my_resolve_class = { 31 "MyResolve", 32 JSCLASS_HAS_RESERVED_SLOTS(SlotCount), 33 &my_resolve_classOps, 34 }; 35 36 obj1.init(cx, JS_NewObject(cx, &my_resolve_class)); 37 CHECK(obj1); 38 obj2.init(cx, JS_NewObject(cx, &my_resolve_class)); 39 CHECK(obj2); 40 JS::SetReservedSlot(obj1, TestSlot, JS::PrivateValue(this)); 41 JS::SetReservedSlot(obj2, TestSlot, JS::PrivateValue(this)); 42 43 JS::RootedValue obj1Val(cx, JS::ObjectValue(*obj1)); 44 JS::RootedValue obj2Val(cx, JS::ObjectValue(*obj2)); 45 CHECK(JS_DefineProperty(cx, global, "obj1", obj1Val, 0)); 46 CHECK(JS_DefineProperty(cx, global, "obj2", obj2Val, 0)); 47 48 resolveEntryCount = 0; 49 resolveExitCount = 0; 50 51 /* Start the essence of the test via invoking the first resolve hook. */ 52 JS::RootedValue v(cx); 53 EVAL("obj1.x", &v); 54 CHECK(v.isFalse()); 55 CHECK_EQUAL(resolveEntryCount, 4); 56 CHECK_EQUAL(resolveExitCount, 4); 57 58 obj1 = nullptr; 59 obj2 = nullptr; 60 return true; 61 } 62 63 enum Slots { TestSlot, SlotCount }; 64 65 JS::PersistentRootedObject obj1; 66 JS::PersistentRootedObject obj2; 67 int resolveEntryCount; 68 int resolveExitCount; 69 70 struct AutoIncrCounters { 71 explicit AutoIncrCounters(cls_testResolveRecursion* t) : t(t) { 72 t->resolveEntryCount++; 73 } 74 75 ~AutoIncrCounters() { t->resolveExitCount++; } 76 77 cls_testResolveRecursion* t; 78 }; 79 80 bool doResolve(JS::HandleObject obj, JS::HandleId id, bool* resolvedp) { 81 CHECK_EQUAL(resolveExitCount, 0); 82 AutoIncrCounters incr(this); 83 CHECK(obj == obj1 || obj == obj2); 84 85 CHECK(id.isString()); 86 87 JSLinearString* str = JS_EnsureLinearString(cx, id.toString()); 88 CHECK(str); 89 JS::RootedValue v(cx); 90 if (JS_LinearStringEqualsLiteral(str, "x")) { 91 if (obj == obj1) { 92 /* First resolve hook invocation. */ 93 CHECK_EQUAL(resolveEntryCount, 1); 94 EVAL("obj2.y = true", &v); 95 CHECK(v.isTrue()); 96 CHECK(JS_DefinePropertyById(cx, obj, id, JS::FalseHandleValue, 97 JSPROP_RESOLVING)); 98 *resolvedp = true; 99 return true; 100 } 101 if (obj == obj2) { 102 CHECK_EQUAL(resolveEntryCount, 4); 103 *resolvedp = false; 104 return true; 105 } 106 } else if (JS_LinearStringEqualsLiteral(str, "y")) { 107 if (obj == obj2) { 108 CHECK_EQUAL(resolveEntryCount, 2); 109 CHECK(JS_DefinePropertyById(cx, obj, id, JS::NullHandleValue, 110 JSPROP_RESOLVING)); 111 EVAL("obj1.x", &v); 112 CHECK(v.isUndefined()); 113 EVAL("obj1.y", &v); 114 CHECK(v.isInt32(0)); 115 *resolvedp = true; 116 return true; 117 } 118 if (obj == obj1) { 119 CHECK_EQUAL(resolveEntryCount, 3); 120 EVAL("obj1.x", &v); 121 CHECK(v.isUndefined()); 122 EVAL("obj1.y", &v); 123 CHECK(v.isUndefined()); 124 EVAL("obj2.y", &v); 125 CHECK(v.isNull()); 126 EVAL("obj2.x", &v); 127 CHECK(v.isUndefined()); 128 EVAL("obj1.y = 0", &v); 129 CHECK(v.isInt32(0)); 130 *resolvedp = true; 131 return true; 132 } 133 } 134 CHECK(false); 135 return false; 136 } 137 138 static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, 139 bool* resolvedp) { 140 void* p = JS::GetReservedSlot(obj, TestSlot).toPrivate(); 141 return static_cast<cls_testResolveRecursion*>(p)->doResolve(obj, id, 142 resolvedp); 143 } 144 END_TEST(testResolveRecursion) 145 146 /* 147 * Test that JS_InitStandardClasses does not cause resolve hooks to be called. 148 * 149 * (XPConnect apparently does have global classes, such as the one created by 150 * nsMessageManagerScriptExecutor::InitChildGlobalInternal(), that have resolve 151 * hooks which can call back into JS, and on which JS_InitStandardClasses is 152 * called. Calling back into JS in the middle of resolving `undefined` is bad.) 153 */ 154 BEGIN_TEST(testResolveRecursion_InitStandardClasses) { 155 CHECK(JS::InitRealmStandardClasses(cx)); 156 return true; 157 } 158 159 const JSClass* getGlobalClass() override { 160 static const JSClassOps myGlobalClassOps = { 161 nullptr, // addProperty 162 nullptr, // delProperty 163 nullptr, // enumerate 164 nullptr, // newEnumerate 165 my_resolve, // resolve 166 nullptr, // mayResolve 167 nullptr, // finalize 168 nullptr, // call 169 nullptr, // construct 170 JS_GlobalObjectTraceHook, // trace 171 }; 172 173 static const JSClass myGlobalClass = { 174 "testResolveRecursion_InitStandardClasses_myGlobalClass", 175 JSCLASS_GLOBAL_FLAGS, 176 &myGlobalClassOps, 177 }; 178 179 return &myGlobalClass; 180 } 181 182 static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, 183 bool* resolvedp) { 184 MOZ_ASSERT_UNREACHABLE( 185 "resolve hook should not be called from InitStandardClasses"); 186 JS_ReportErrorASCII(cx, "FAIL"); 187 return false; 188 } 189 END_TEST(testResolveRecursion_InitStandardClasses)