Environment.cpp (18995B)
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 "debugger/Environment-inl.h" 8 9 #include "mozilla/Assertions.h" // for AssertionConditionType 10 #include "mozilla/Maybe.h" // for Maybe, Some, Nothing 11 #include "mozilla/Vector.h" // for Vector 12 13 #include <string.h> // for strlen, size_t 14 15 #include "debugger/Debugger.h" // for Env, Debugger, ValueToIdentifier 16 #include "debugger/Object.h" // for DebuggerObject 17 #include "debugger/Script.h" // for DebuggerScript 18 #include "gc/Tracer.h" // for TraceManuallyBarrieredCrossCompartmentEdge 19 #include "js/CallArgs.h" // for CallArgs 20 #include "js/friend/ErrorMessages.h" // for GetErrorMessage, JSMSG_* 21 #include "js/HeapAPI.h" // for IsInsideNursery 22 #include "js/RootingAPI.h" // for Rooted, MutableHandle 23 #include "util/Identifier.h" // for IsIdentifier 24 #include "vm/Compartment.h" // for Compartment 25 #include "vm/JSAtomUtils.h" // for Atomize 26 #include "vm/JSContext.h" // for JSContext 27 #include "vm/JSFunction.h" // for JSFunction 28 #include "vm/JSObject.h" // for JSObject, RequireObject, 29 #include "vm/NativeObject.h" // for NativeObject, JSObject::is 30 #include "vm/Realm.h" // for AutoRealm, ErrorCopier 31 #include "vm/Scope.h" // for ScopeKind, ScopeKindString 32 #include "vm/StringType.h" // for JSAtom 33 34 #include "gc/StableCellHasher-inl.h" 35 #include "vm/Compartment-inl.h" // for Compartment::wrap 36 #include "vm/EnvironmentObject-inl.h" // for JSObject::enclosingEnvironment 37 #include "vm/JSObject-inl.h" // for IsInternalFunctionObject, NewObjectWithGivenProtoAndKind 38 #include "vm/ObjectOperations-inl.h" // for HasProperty, GetProperty 39 #include "vm/Realm-inl.h" // for AutoRealm::AutoRealm 40 41 namespace js { 42 class GlobalObject; 43 } 44 45 using namespace js; 46 47 using mozilla::Maybe; 48 using mozilla::Nothing; 49 using mozilla::Some; 50 51 const JSClassOps DebuggerEnvironment::classOps_ = { 52 nullptr, // addProperty 53 nullptr, // delProperty 54 nullptr, // enumerate 55 nullptr, // newEnumerate 56 nullptr, // resolve 57 nullptr, // mayResolve 58 nullptr, // finalize 59 nullptr, // call 60 nullptr, // construct 61 CallTraceMethod<DebuggerEnvironment>, // trace 62 }; 63 64 const JSClass DebuggerEnvironment::class_ = { 65 "Environment", 66 JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS), 67 &classOps_, 68 }; 69 70 void DebuggerEnvironment::trace(JSTracer* trc) { 71 // There is a barrier on private pointers, so the Unbarriered marking 72 // is okay. 73 if (Env* referent = maybeReferent()) { 74 TraceManuallyBarrieredCrossCompartmentEdge(trc, this, &referent, 75 "Debugger.Environment referent"); 76 if (referent != maybeReferent()) { 77 setReservedSlotGCThingAsPrivateUnbarriered(ENV_SLOT, referent); 78 } 79 } 80 } 81 82 static DebuggerEnvironment* DebuggerEnvironment_checkThis( 83 JSContext* cx, const CallArgs& args) { 84 JSObject* thisobj = RequireObject(cx, args.thisv()); 85 if (!thisobj) { 86 return nullptr; 87 } 88 if (!thisobj->is<DebuggerEnvironment>()) { 89 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 90 JSMSG_INCOMPATIBLE_PROTO, "Debugger.Environment", 91 "method", thisobj->getClass()->name); 92 return nullptr; 93 } 94 95 return &thisobj->as<DebuggerEnvironment>(); 96 } 97 98 struct MOZ_STACK_CLASS DebuggerEnvironment::CallData { 99 JSContext* cx; 100 const CallArgs& args; 101 102 Handle<DebuggerEnvironment*> environment; 103 104 CallData(JSContext* cx, const CallArgs& args, 105 Handle<DebuggerEnvironment*> env) 106 : cx(cx), args(args), environment(env) {} 107 108 bool typeGetter(); 109 bool scopeKindGetter(); 110 bool parentGetter(); 111 bool objectGetter(); 112 bool calleeScriptGetter(); 113 bool inspectableGetter(); 114 bool optimizedOutGetter(); 115 116 bool namesMethod(); 117 bool findMethod(); 118 bool getVariableMethod(); 119 bool setVariableMethod(); 120 121 using Method = bool (CallData::*)(); 122 123 template <Method MyMethod> 124 static bool ToNative(JSContext* cx, unsigned argc, Value* vp); 125 }; 126 127 template <DebuggerEnvironment::CallData::Method MyMethod> 128 /* static */ 129 bool DebuggerEnvironment::CallData::ToNative(JSContext* cx, unsigned argc, 130 Value* vp) { 131 CallArgs args = CallArgsFromVp(argc, vp); 132 133 Rooted<DebuggerEnvironment*> environment( 134 cx, DebuggerEnvironment_checkThis(cx, args)); 135 if (!environment) { 136 return false; 137 } 138 139 CallData data(cx, args, environment); 140 return (data.*MyMethod)(); 141 } 142 143 /* static */ 144 bool DebuggerEnvironment::construct(JSContext* cx, unsigned argc, Value* vp) { 145 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, 146 "Debugger.Environment"); 147 return false; 148 } 149 150 static bool IsDeclarative(Env* env) { 151 return env->is<DebugEnvironmentProxy>() && 152 env->as<DebugEnvironmentProxy>().isForDeclarative(); 153 } 154 155 template <typename T> 156 static bool IsDebugEnvironmentWrapper(Env* env) { 157 return env->is<DebugEnvironmentProxy>() && 158 env->as<DebugEnvironmentProxy>().environment().is<T>(); 159 } 160 161 bool DebuggerEnvironment::CallData::typeGetter() { 162 if (!environment->requireDebuggee(cx)) { 163 return false; 164 } 165 166 DebuggerEnvironmentType type = environment->type(); 167 168 const char* s; 169 switch (type) { 170 case DebuggerEnvironmentType::Declarative: 171 s = "declarative"; 172 break; 173 case DebuggerEnvironmentType::With: 174 s = "with"; 175 break; 176 case DebuggerEnvironmentType::Object: 177 s = "object"; 178 break; 179 } 180 181 JSAtom* str = Atomize(cx, s, strlen(s)); 182 if (!str) { 183 return false; 184 } 185 186 args.rval().setString(str); 187 return true; 188 } 189 190 bool DebuggerEnvironment::CallData::scopeKindGetter() { 191 if (!environment->requireDebuggee(cx)) { 192 return false; 193 } 194 195 Maybe<ScopeKind> kind = environment->scopeKind(); 196 if (kind.isSome()) { 197 const char* s = ScopeKindString(*kind); 198 JSAtom* str = Atomize(cx, s, strlen(s)); 199 if (!str) { 200 return false; 201 } 202 args.rval().setString(str); 203 } else { 204 args.rval().setNull(); 205 } 206 207 return true; 208 } 209 210 bool DebuggerEnvironment::CallData::parentGetter() { 211 if (!environment->requireDebuggee(cx)) { 212 return false; 213 } 214 215 Rooted<DebuggerEnvironment*> result(cx); 216 if (!environment->getParent(cx, &result)) { 217 return false; 218 } 219 220 args.rval().setObjectOrNull(result); 221 return true; 222 } 223 224 bool DebuggerEnvironment::CallData::objectGetter() { 225 if (!environment->requireDebuggee(cx)) { 226 return false; 227 } 228 229 if (environment->type() == DebuggerEnvironmentType::Declarative) { 230 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 231 JSMSG_DEBUG_NO_ENV_OBJECT); 232 return false; 233 } 234 235 Rooted<DebuggerObject*> result(cx); 236 if (!environment->getObject(cx, &result)) { 237 return false; 238 } 239 240 args.rval().setObject(*result); 241 return true; 242 } 243 244 bool DebuggerEnvironment::CallData::calleeScriptGetter() { 245 if (!environment->requireDebuggee(cx)) { 246 return false; 247 } 248 249 Rooted<DebuggerScript*> result(cx); 250 if (!environment->getCalleeScript(cx, &result)) { 251 return false; 252 } 253 254 args.rval().setObjectOrNull(result); 255 return true; 256 } 257 258 bool DebuggerEnvironment::CallData::inspectableGetter() { 259 args.rval().setBoolean(environment->isDebuggee()); 260 return true; 261 } 262 263 bool DebuggerEnvironment::CallData::optimizedOutGetter() { 264 args.rval().setBoolean(environment->isOptimized()); 265 return true; 266 } 267 268 bool DebuggerEnvironment::CallData::namesMethod() { 269 if (!environment->requireDebuggee(cx)) { 270 return false; 271 } 272 273 RootedIdVector ids(cx); 274 if (!DebuggerEnvironment::getNames(cx, environment, &ids)) { 275 return false; 276 } 277 278 JSObject* obj = IdVectorToArray(cx, ids); 279 if (!obj) { 280 return false; 281 } 282 283 args.rval().setObject(*obj); 284 return true; 285 } 286 287 bool DebuggerEnvironment::CallData::findMethod() { 288 if (!args.requireAtLeast(cx, "Debugger.Environment.find", 1)) { 289 return false; 290 } 291 292 RootedId id(cx); 293 if (!ValueToIdentifier(cx, args[0], &id)) { 294 return false; 295 } 296 297 if (!environment->requireDebuggee(cx)) { 298 return false; 299 } 300 301 Rooted<DebuggerEnvironment*> result(cx); 302 if (!DebuggerEnvironment::find(cx, environment, id, &result)) { 303 return false; 304 } 305 306 args.rval().setObjectOrNull(result); 307 return true; 308 } 309 310 bool DebuggerEnvironment::CallData::getVariableMethod() { 311 if (!args.requireAtLeast(cx, "Debugger.Environment.getVariable", 1)) { 312 return false; 313 } 314 315 RootedId id(cx); 316 if (!ValueToIdentifier(cx, args[0], &id)) { 317 return false; 318 } 319 320 if (!environment->requireDebuggee(cx)) { 321 return false; 322 } 323 324 return DebuggerEnvironment::getVariable(cx, environment, id, args.rval()); 325 } 326 327 bool DebuggerEnvironment::CallData::setVariableMethod() { 328 if (!args.requireAtLeast(cx, "Debugger.Environment.setVariable", 2)) { 329 return false; 330 } 331 332 RootedId id(cx); 333 if (!ValueToIdentifier(cx, args[0], &id)) { 334 return false; 335 } 336 337 if (!environment->requireDebuggee(cx)) { 338 return false; 339 } 340 341 if (!DebuggerEnvironment::setVariable(cx, environment, id, args[1])) { 342 return false; 343 } 344 345 args.rval().setUndefined(); 346 return true; 347 } 348 349 bool DebuggerEnvironment::requireDebuggee(JSContext* cx) const { 350 if (!isDebuggee()) { 351 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 352 JSMSG_DEBUG_NOT_DEBUGGEE, "Debugger.Environment", 353 "environment"); 354 355 return false; 356 } 357 358 return true; 359 } 360 361 const JSPropertySpec DebuggerEnvironment::properties_[] = { 362 JS_DEBUG_PSG("type", typeGetter), 363 JS_DEBUG_PSG("scopeKind", scopeKindGetter), 364 JS_DEBUG_PSG("parent", parentGetter), 365 JS_DEBUG_PSG("object", objectGetter), 366 JS_DEBUG_PSG("calleeScript", calleeScriptGetter), 367 JS_DEBUG_PSG("inspectable", inspectableGetter), 368 JS_DEBUG_PSG("optimizedOut", optimizedOutGetter), 369 JS_PS_END, 370 }; 371 372 const JSFunctionSpec DebuggerEnvironment::methods_[] = { 373 JS_DEBUG_FN("names", namesMethod, 0), 374 JS_DEBUG_FN("find", findMethod, 1), 375 JS_DEBUG_FN("getVariable", getVariableMethod, 1), 376 JS_DEBUG_FN("setVariable", setVariableMethod, 2), 377 JS_FS_END, 378 }; 379 380 /* static */ 381 NativeObject* DebuggerEnvironment::initClass(JSContext* cx, 382 Handle<GlobalObject*> global, 383 HandleObject dbgCtor) { 384 return InitClass(cx, dbgCtor, nullptr, nullptr, "Environment", construct, 0, 385 properties_, methods_, nullptr, nullptr); 386 } 387 388 /* static */ 389 DebuggerEnvironment* DebuggerEnvironment::create( 390 JSContext* cx, HandleObject proto, HandleObject referent, 391 Handle<NativeObject*> debugger) { 392 DebuggerEnvironment* obj = 393 IsInsideNursery(referent) 394 ? NewObjectWithGivenProto<DebuggerEnvironment>(cx, proto) 395 : NewTenuredObjectWithGivenProto<DebuggerEnvironment>(cx, proto); 396 if (!obj) { 397 return nullptr; 398 } 399 400 obj->setReservedSlotGCThingAsPrivate(ENV_SLOT, referent); 401 obj->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger)); 402 403 return obj; 404 } 405 406 /* static */ 407 DebuggerEnvironmentType DebuggerEnvironment::type() const { 408 // Don't bother switching compartments just to check env's type. 409 if (IsDeclarative(referent())) { 410 return DebuggerEnvironmentType::Declarative; 411 } 412 if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) { 413 return DebuggerEnvironmentType::With; 414 } 415 return DebuggerEnvironmentType::Object; 416 } 417 418 mozilla::Maybe<ScopeKind> DebuggerEnvironment::scopeKind() const { 419 if (!referent()->is<DebugEnvironmentProxy>()) { 420 return Nothing(); 421 } 422 EnvironmentObject& env = 423 referent()->as<DebugEnvironmentProxy>().environment(); 424 Scope* scope = GetEnvironmentScope(env); 425 return scope ? Some(scope->kind()) : Nothing(); 426 } 427 428 bool DebuggerEnvironment::getParent( 429 JSContext* cx, MutableHandle<DebuggerEnvironment*> result) const { 430 // Don't bother switching compartments just to get env's parent. 431 Rooted<Env*> parent(cx, referent()->enclosingEnvironment()); 432 if (!parent) { 433 result.set(nullptr); 434 return true; 435 } 436 437 return owner()->wrapEnvironment(cx, parent, result); 438 } 439 440 bool DebuggerEnvironment::getObject( 441 JSContext* cx, MutableHandle<DebuggerObject*> result) const { 442 MOZ_ASSERT(type() != DebuggerEnvironmentType::Declarative); 443 444 // Don't bother switching compartments just to get env's object. 445 RootedObject object(cx); 446 if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) { 447 object.set(&referent() 448 ->as<DebugEnvironmentProxy>() 449 .environment() 450 .as<WithEnvironmentObject>() 451 .object()); 452 } else if (IsDebugEnvironmentWrapper<NonSyntacticVariablesObject>( 453 referent())) { 454 object.set(&referent() 455 ->as<DebugEnvironmentProxy>() 456 .environment() 457 .as<NonSyntacticVariablesObject>()); 458 } else { 459 object.set(referent()); 460 MOZ_ASSERT(!object->is<DebugEnvironmentProxy>()); 461 } 462 463 return owner()->wrapDebuggeeObject(cx, object, result); 464 } 465 466 bool DebuggerEnvironment::getCalleeScript( 467 JSContext* cx, MutableHandle<DebuggerScript*> result) const { 468 if (!referent()->is<DebugEnvironmentProxy>()) { 469 result.set(nullptr); 470 return true; 471 } 472 473 JSObject& scope = referent()->as<DebugEnvironmentProxy>().environment(); 474 if (!scope.is<CallObject>()) { 475 result.set(nullptr); 476 return true; 477 } 478 479 Rooted<BaseScript*> script(cx, scope.as<CallObject>().callee().baseScript()); 480 481 DebuggerScript* scriptObject = owner()->wrapScript(cx, script); 482 if (!scriptObject) { 483 return false; 484 } 485 486 result.set(scriptObject); 487 return true; 488 } 489 490 bool DebuggerEnvironment::isDebuggee() const { 491 MOZ_ASSERT(referent()); 492 MOZ_ASSERT(!referent()->is<EnvironmentObject>()); 493 494 return owner()->observesGlobal(&referent()->nonCCWGlobal()); 495 } 496 497 bool DebuggerEnvironment::isOptimized() const { 498 return referent()->is<DebugEnvironmentProxy>() && 499 referent()->as<DebugEnvironmentProxy>().isOptimizedOut(); 500 } 501 502 /* static */ 503 bool DebuggerEnvironment::getNames(JSContext* cx, 504 Handle<DebuggerEnvironment*> environment, 505 MutableHandleIdVector result) { 506 MOZ_ASSERT(environment->isDebuggee()); 507 MOZ_ASSERT(result.empty()); 508 509 Rooted<Env*> referent(cx, environment->referent()); 510 { 511 Maybe<AutoRealm> ar; 512 ar.emplace(cx, referent); 513 514 ErrorCopier ec(ar); 515 if (!GetPropertyKeys(cx, referent, JSITER_HIDDEN, result)) { 516 return false; 517 } 518 } 519 520 result.eraseIf([](PropertyKey key) { 521 return !key.isAtom() || !IsIdentifier(key.toAtom()); 522 }); 523 524 for (size_t i = 0; i < result.length(); ++i) { 525 cx->markAtom(result[i].toAtom()); 526 } 527 528 return true; 529 } 530 531 /* static */ 532 bool DebuggerEnvironment::find(JSContext* cx, 533 Handle<DebuggerEnvironment*> environment, 534 HandleId id, 535 MutableHandle<DebuggerEnvironment*> result) { 536 MOZ_ASSERT(environment->isDebuggee()); 537 538 Rooted<Env*> env(cx, environment->referent()); 539 Debugger* dbg = environment->owner(); 540 541 { 542 Maybe<AutoRealm> ar; 543 ar.emplace(cx, env); 544 545 cx->markId(id); 546 547 // This can trigger resolve hooks. 548 ErrorCopier ec(ar); 549 for (; env; env = env->enclosingEnvironment()) { 550 bool found; 551 if (!HasProperty(cx, env, id, &found)) { 552 return false; 553 } 554 if (found) { 555 break; 556 } 557 } 558 } 559 560 if (!env) { 561 result.set(nullptr); 562 return true; 563 } 564 565 return dbg->wrapEnvironment(cx, env, result); 566 } 567 568 /* static */ 569 bool DebuggerEnvironment::getVariable(JSContext* cx, 570 Handle<DebuggerEnvironment*> environment, 571 HandleId id, MutableHandleValue result) { 572 MOZ_ASSERT(environment->isDebuggee()); 573 574 Rooted<Env*> referent(cx, environment->referent()); 575 Debugger* dbg = environment->owner(); 576 577 { 578 Maybe<AutoRealm> ar; 579 ar.emplace(cx, referent); 580 581 cx->markId(id); 582 583 // This can trigger getters. 584 ErrorCopier ec(ar); 585 586 bool found; 587 if (!HasProperty(cx, referent, id, &found)) { 588 return false; 589 } 590 if (!found) { 591 result.setUndefined(); 592 return true; 593 } 594 595 // For DebugEnvironmentProxys, we get sentinel values for optimized out 596 // slots and arguments instead of throwing (the default behavior). 597 // 598 // See wrapDebuggeeValue for how the sentinel values are wrapped. 599 if (referent->is<DebugEnvironmentProxy>()) { 600 Rooted<DebugEnvironmentProxy*> env( 601 cx, &referent->as<DebugEnvironmentProxy>()); 602 if (!DebugEnvironmentProxy::getMaybeSentinelValue(cx, env, id, result)) { 603 return false; 604 } 605 } else { 606 if (!GetProperty(cx, referent, referent, id, result)) { 607 return false; 608 } 609 } 610 } 611 612 // When we've faked up scope chain objects for optimized-out scopes, 613 // declarative environments may contain internal JSFunction objects, which 614 // we shouldn't expose to the user. 615 if (result.isObject()) { 616 RootedObject obj(cx, &result.toObject()); 617 if (obj->is<JSFunction>() && 618 IsInternalFunctionObject(obj->as<JSFunction>())) 619 result.setMagic(JS_OPTIMIZED_OUT); 620 } 621 622 return dbg->wrapDebuggeeValue(cx, result); 623 } 624 625 /* static */ 626 bool DebuggerEnvironment::setVariable(JSContext* cx, 627 Handle<DebuggerEnvironment*> environment, 628 HandleId id, HandleValue value_) { 629 MOZ_ASSERT(environment->isDebuggee()); 630 631 Rooted<Env*> referent(cx, environment->referent()); 632 Debugger* dbg = environment->owner(); 633 634 RootedValue value(cx, value_); 635 if (!dbg->unwrapDebuggeeValue(cx, &value)) { 636 return false; 637 } 638 639 { 640 Maybe<AutoRealm> ar; 641 ar.emplace(cx, referent); 642 if (!cx->compartment()->wrap(cx, &value)) { 643 return false; 644 } 645 cx->markId(id); 646 647 // This can trigger setters. 648 ErrorCopier ec(ar); 649 650 // Make sure the environment actually has the specified binding. 651 bool found; 652 if (!HasProperty(cx, referent, id, &found)) { 653 return false; 654 } 655 if (!found) { 656 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 657 JSMSG_DEBUG_VARIABLE_NOT_FOUND); 658 return false; 659 } 660 661 // Just set the property. 662 if (!SetProperty(cx, referent, id, value)) { 663 return false; 664 } 665 } 666 667 return true; 668 }