BaseProxyHandler.cpp (13177B)
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 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "jsapi.h" 8 #include "NamespaceImports.h" 9 10 #include "gc/GC.h" 11 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 12 #include "js/Proxy.h" 13 #include "proxy/DeadObjectProxy.h" 14 #include "vm/Interpreter.h" 15 #include "vm/ProxyObject.h" 16 #include "vm/WrapperObject.h" 17 18 #include "vm/JSContext-inl.h" 19 #include "vm/JSObject-inl.h" 20 21 using namespace js; 22 23 using JS::IsArrayAnswer; 24 25 bool BaseProxyHandler::enter(JSContext* cx, HandleObject wrapper, HandleId id, 26 Action act, bool mayThrow, bool* bp) const { 27 *bp = true; 28 return true; 29 } 30 31 bool BaseProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, 32 bool* bp) const { 33 assertEnteredPolicy(cx, proxy, id, GET); 34 35 // This method is not covered by any spec, but we follow ES 2016 36 // (February 11, 2016) 9.1.7.1 fairly closely. 37 38 // Step 2. (Step 1 is a superfluous assertion.) 39 // Non-standard: Use our faster hasOwn trap. 40 if (!hasOwn(cx, proxy, id, bp)) { 41 return false; 42 } 43 44 // Step 3. 45 if (*bp) { 46 return true; 47 } 48 49 // The spec calls this variable "parent", but that word has weird 50 // connotations in SpiderMonkey, so let's go with "proto". 51 // Step 4. 52 RootedObject proto(cx); 53 if (!GetPrototype(cx, proxy, &proto)) { 54 return false; 55 } 56 57 // Step 5.,5.a. 58 if (proto) { 59 return HasProperty(cx, proto, id, bp); 60 } 61 62 // Step 6. 63 *bp = false; 64 return true; 65 } 66 67 bool BaseProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, 68 bool* bp) const { 69 assertEnteredPolicy(cx, proxy, id, GET); 70 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 71 if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) { 72 return false; 73 } 74 *bp = desc.isSome(); 75 return true; 76 } 77 78 bool BaseProxyHandler::get(JSContext* cx, HandleObject proxy, 79 HandleValue receiver, HandleId id, 80 MutableHandleValue vp) const { 81 assertEnteredPolicy(cx, proxy, id, GET); 82 83 // This method is not covered by any spec, but we follow ES 2016 84 // (January 21, 2016) 9.1.8 fairly closely. 85 86 // Step 2. (Step 1 is a superfluous assertion.) 87 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 88 if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) { 89 return false; 90 } 91 if (desc.isSome()) { 92 desc->assertComplete(); 93 } 94 95 // Step 3. 96 if (desc.isNothing()) { 97 // The spec calls this variable "parent", but that word has weird 98 // connotations in SpiderMonkey, so let's go with "proto". 99 // Step 3.a. 100 RootedObject proto(cx); 101 if (!GetPrototype(cx, proxy, &proto)) { 102 return false; 103 } 104 105 // Step 3.b. 106 if (!proto) { 107 vp.setUndefined(); 108 return true; 109 } 110 111 // Step 3.c. 112 return GetProperty(cx, proto, receiver, id, vp); 113 } 114 115 // Step 4. 116 if (desc->isDataDescriptor()) { 117 vp.set(desc->value()); 118 return true; 119 } 120 121 // Step 5. 122 MOZ_ASSERT(desc->isAccessorDescriptor()); 123 RootedObject getter(cx, desc->getter()); 124 125 // Step 6. 126 if (!getter) { 127 vp.setUndefined(); 128 return true; 129 } 130 131 // Step 7. 132 RootedValue getterFunc(cx, ObjectValue(*getter)); 133 return CallGetter(cx, receiver, getterFunc, vp); 134 } 135 136 bool BaseProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, 137 HandleValue v, HandleValue receiver, 138 ObjectOpResult& result) const { 139 assertEnteredPolicy(cx, proxy, id, SET); 140 141 // This method is not covered by any spec, but we follow ES6 draft rev 28 142 // (2014 Oct 14) 9.1.9 fairly closely, adapting it slightly for 143 // SpiderMonkey's particular foibles. 144 145 // Steps 2-3. (Step 1 is a superfluous assertion.) 146 Rooted<mozilla::Maybe<PropertyDescriptor>> ownDesc(cx); 147 if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc)) { 148 return false; 149 } 150 if (ownDesc.isSome()) { 151 ownDesc->assertComplete(); 152 } 153 154 // The rest is factored out into a separate function with a weird name. 155 // This algorithm continues just below. 156 return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, 157 result); 158 } 159 160 bool js::SetPropertyIgnoringNamedGetter( 161 JSContext* cx, HandleObject obj, HandleId id, HandleValue v, 162 HandleValue receiver, Handle<mozilla::Maybe<PropertyDescriptor>> ownDesc_, 163 ObjectOpResult& result) { 164 Rooted<PropertyDescriptor> ownDesc(cx); 165 166 // Step 4. 167 if (ownDesc_.isNothing()) { 168 // The spec calls this variable "parent", but that word has weird 169 // connotations in SpiderMonkey, so let's go with "proto". 170 RootedObject proto(cx); 171 if (!GetPrototype(cx, obj, &proto)) { 172 return false; 173 } 174 if (proto) { 175 return SetProperty(cx, proto, id, v, receiver, result); 176 } 177 178 // Step 4.d. 179 ownDesc.set(PropertyDescriptor::Data( 180 UndefinedValue(), 181 {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable, 182 JS::PropertyAttribute::Writable})); 183 } else { 184 ownDesc.set(*ownDesc_); 185 } 186 187 // Step 5. 188 if (ownDesc.isDataDescriptor()) { 189 // Steps 5.a-b. 190 if (!ownDesc.writable()) { 191 return result.fail(JSMSG_READ_ONLY); 192 } 193 if (!receiver.isObject()) { 194 return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER); 195 } 196 RootedObject receiverObj(cx, &receiver.toObject()); 197 198 // Steps 5.c-d. 199 Rooted<mozilla::Maybe<PropertyDescriptor>> existingDescriptor(cx); 200 if (!GetOwnPropertyDescriptor(cx, receiverObj, id, &existingDescriptor)) { 201 return false; 202 } 203 204 // Step 5.e. 205 if (existingDescriptor.isSome()) { 206 // Step 5.e.i. 207 if (existingDescriptor->isAccessorDescriptor()) { 208 return result.fail(JSMSG_OVERWRITING_ACCESSOR); 209 } 210 211 // Step 5.e.ii. 212 if (!existingDescriptor->writable()) { 213 return result.fail(JSMSG_READ_ONLY); 214 } 215 } 216 217 // Steps 5.e.iii-iv. and 5.f.i. 218 Rooted<PropertyDescriptor> desc(cx); 219 if (existingDescriptor.isSome()) { 220 desc = PropertyDescriptor::Empty(); 221 desc.setValue(v); 222 } else { 223 desc = PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable, 224 JS::PropertyAttribute::Enumerable, 225 JS::PropertyAttribute::Writable}); 226 } 227 return DefineProperty(cx, receiverObj, id, desc, result); 228 } 229 230 // Step 6. 231 MOZ_ASSERT(ownDesc.isAccessorDescriptor()); 232 RootedObject setter(cx); 233 if (ownDesc.hasSetter()) { 234 setter = ownDesc.setter(); 235 } 236 if (!setter) { 237 return result.fail(JSMSG_GETTER_ONLY); 238 } 239 RootedValue setterValue(cx, ObjectValue(*setter)); 240 if (!CallSetter(cx, receiver, setterValue, v)) { 241 return false; 242 } 243 return result.succeed(); 244 } 245 246 bool BaseProxyHandler::getOwnEnumerablePropertyKeys( 247 JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const { 248 assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), ENUMERATE); 249 MOZ_ASSERT(props.length() == 0); 250 251 if (!ownPropertyKeys(cx, proxy, props)) { 252 return false; 253 } 254 255 /* Select only the enumerable properties through in-place iteration. */ 256 RootedId id(cx); 257 size_t i = 0; 258 for (size_t j = 0, len = props.length(); j < len; j++) { 259 MOZ_ASSERT(i <= j); 260 id = props[j]; 261 if (id.isSymbol()) { 262 continue; 263 } 264 265 AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET); 266 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 267 if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) { 268 return false; 269 } 270 if (desc.isSome()) { 271 desc->assertComplete(); 272 } 273 274 if (desc.isSome() && desc->enumerable()) { 275 props[i++].set(id); 276 } 277 } 278 279 MOZ_ASSERT(i <= props.length()); 280 if (!props.resize(i)) { 281 return false; 282 } 283 284 return true; 285 } 286 287 bool BaseProxyHandler::enumerate(JSContext* cx, HandleObject proxy, 288 MutableHandleIdVector props) const { 289 assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), ENUMERATE); 290 291 // GetPropertyKeys will invoke getOwnEnumerablePropertyKeys along the proto 292 // chain for us. 293 MOZ_ASSERT(props.empty()); 294 return GetPropertyKeys(cx, proxy, 0, props); 295 } 296 297 bool BaseProxyHandler::call(JSContext* cx, HandleObject proxy, 298 const CallArgs& args) const { 299 MOZ_CRASH("callable proxies should implement call trap"); 300 } 301 302 bool BaseProxyHandler::construct(JSContext* cx, HandleObject proxy, 303 const CallArgs& args) const { 304 MOZ_CRASH("callable proxies should implement construct trap"); 305 } 306 307 const char* BaseProxyHandler::className(JSContext* cx, 308 HandleObject proxy) const { 309 return proxy->isCallable() ? "Function" : "Object"; 310 } 311 312 JSString* BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, 313 bool isToSource) const { 314 if (proxy->isCallable()) { 315 return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}"); 316 } 317 318 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 319 JSMSG_INCOMPATIBLE_PROTO, "Function", "toString", 320 "object"); 321 return nullptr; 322 } 323 324 RegExpShared* BaseProxyHandler::regexp_toShared(JSContext* cx, 325 HandleObject proxy) const { 326 MOZ_CRASH("This should have been a wrapped regexp"); 327 } 328 329 bool BaseProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy, 330 MutableHandleValue vp) const { 331 vp.setUndefined(); 332 return true; 333 } 334 335 bool BaseProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test, 336 NativeImpl impl, const CallArgs& args) const { 337 ReportIncompatible(cx, args); 338 return false; 339 } 340 341 bool BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, 342 ESClass* cls) const { 343 *cls = ESClass::Other; 344 return true; 345 } 346 347 bool BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy, 348 IsArrayAnswer* answer) const { 349 *answer = IsArrayAnswer::NotArray; 350 return true; 351 } 352 353 void BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {} 354 355 void BaseProxyHandler::finalize(JS::GCContext* gcx, JSObject* proxy) const {} 356 357 size_t BaseProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const { 358 return 0; 359 } 360 361 bool BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, 362 MutableHandleObject protop) const { 363 MOZ_CRASH("must override getPrototype with dynamic prototype"); 364 } 365 366 bool BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, 367 HandleObject proto, 368 ObjectOpResult& result) const { 369 // Disallow sets of protos on proxies with dynamic prototypes but no hook. 370 // This keeps us away from the footgun of having the first proto set opt 371 // you out of having dynamic protos altogether. 372 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 373 JSMSG_CANT_SET_PROTO_OF, "incompatible Proxy"); 374 return false; 375 } 376 377 bool BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, 378 bool* succeeded) const { 379 *succeeded = false; 380 return true; 381 } 382 383 bool BaseProxyHandler::getElements(JSContext* cx, HandleObject proxy, 384 uint32_t begin, uint32_t end, 385 ElementAdder* adder) const { 386 assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), GET); 387 388 return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder); 389 } 390 391 bool BaseProxyHandler::isCallable(JSObject* obj) const { return false; } 392 393 bool BaseProxyHandler::isConstructor(JSObject* obj) const { return false; } 394 395 JS_PUBLIC_API void js::NukeNonCCWProxy(JSContext* cx, HandleObject proxy) { 396 MOZ_ASSERT(proxy->is<ProxyObject>()); 397 MOZ_ASSERT(!proxy->is<CrossCompartmentWrapperObject>()); 398 399 // (NotifyGCNukeWrapper() only needs to be called on CCWs.) 400 401 // The proxy is about to be replaced, so we need to do any necessary 402 // cleanup first. 403 proxy->as<ProxyObject>().handler()->finalize(cx->gcContext(), proxy); 404 405 proxy->as<ProxyObject>().nuke(); 406 407 MOZ_ASSERT(IsDeadProxyObject(proxy)); 408 } 409 410 JS_PUBLIC_API void js::NukeRemovedCrossCompartmentWrapper(JSContext* cx, 411 JSObject* wrapper) { 412 MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>()); 413 414 NotifyGCNukeWrapper(cx, wrapper); 415 416 // We don't need to call finalize here because the CCW finalizer doesn't do 417 // anything. Skipping finalize means that |wrapper| doesn't need to be rooted 418 // to pass the hazard analysis, which is needed because this method is called 419 // from some tricky places inside transplanting where rooting can be 420 // difficult. 421 422 wrapper->as<ProxyObject>().nuke(); 423 424 MOZ_ASSERT(IsDeadProxyObject(wrapper)); 425 }