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/. */
7 #include "debugger/Environment-inl.h"
9 #include "mozilla/Assertions.h" // for AssertionConditionType
10 #include "mozilla/Maybe.h" // for Maybe, Some, Nothing
11 #include "mozilla/Vector.h" // for Vector
13 #include <string.h> // for strlen, size_t
14 #include <utility> // for move
16 #include "debugger/Debugger.h" // for Env, Debugger, ValueToIdentifier
17 #include "debugger/Object.h" // for DebuggerObject
18 #include "debugger/Script.h" // for DebuggerScript
19 #include "gc/Tracer.h" // for TraceManuallyBarrieredCrossCompartmentEdge
20 #include "js/CallArgs.h" // for CallArgs
21 #include "js/friend/ErrorMessages.h" // for GetErrorMessage, JSMSG_*
22 #include "js/HeapAPI.h" // for IsInsideNursery
23 #include "js/RootingAPI.h" // for Rooted, MutableHandle
24 #include "util/Identifier.h" // for IsIdentifier
25 #include "vm/Compartment.h" // for Compartment
26 #include "vm/JSAtomUtils.h" // for Atomize
27 #include "vm/JSContext.h" // for JSContext
28 #include "vm/JSFunction.h" // for JSFunction
29 #include "vm/JSObject.h" // for JSObject, RequireObject,
30 #include "vm/NativeObject.h" // for NativeObject, JSObject::is
31 #include "vm/Realm.h" // for AutoRealm, ErrorCopier
32 #include "vm/Scope.h" // for ScopeKind, ScopeKindString
33 #include "vm/StringType.h" // for JSAtom
35 #include "gc/StableCellHasher-inl.h"
36 #include "vm/Compartment-inl.h" // for Compartment::wrap
37 #include "vm/EnvironmentObject-inl.h" // for JSObject::enclosingEnvironment
38 #include "vm/JSObject-inl.h" // for IsInternalFunctionObject, NewObjectWithGivenProtoAndKind
39 #include "vm/ObjectOperations-inl.h" // for HasProperty, GetProperty
40 #include "vm/Realm-inl.h" // for AutoRealm::AutoRealm
49 using mozilla::Nothing
;
52 const JSClassOps
DebuggerEnvironment::classOps_
= {
53 nullptr, // addProperty
54 nullptr, // delProperty
56 nullptr, // newEnumerate
58 nullptr, // mayResolve
62 CallTraceMethod
<DebuggerEnvironment
>, // trace
65 const JSClass
DebuggerEnvironment::class_
= {
67 JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS
),
70 void DebuggerEnvironment::trace(JSTracer
* trc
) {
71 // There is a barrier on private pointers, so the Unbarriered marking
73 if (Env
* referent
= maybeReferent()) {
74 TraceManuallyBarrieredCrossCompartmentEdge(trc
, this, &referent
,
75 "Debugger.Environment referent");
76 if (referent
!= maybeReferent()) {
77 setReservedSlotGCThingAsPrivateUnbarriered(ENV_SLOT
, referent
);
82 static DebuggerEnvironment
* DebuggerEnvironment_checkThis(
83 JSContext
* cx
, const CallArgs
& args
) {
84 JSObject
* thisobj
= RequireObject(cx
, args
.thisv());
88 if (!thisobj
->is
<DebuggerEnvironment
>()) {
89 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
90 JSMSG_INCOMPATIBLE_PROTO
, "Debugger.Environment",
91 "method", thisobj
->getClass()->name
);
95 return &thisobj
->as
<DebuggerEnvironment
>();
98 struct MOZ_STACK_CLASS
DebuggerEnvironment::CallData
{
100 const CallArgs
& args
;
102 Handle
<DebuggerEnvironment
*> environment
;
104 CallData(JSContext
* cx
, const CallArgs
& args
,
105 Handle
<DebuggerEnvironment
*> env
)
106 : cx(cx
), args(args
), environment(env
) {}
109 bool scopeKindGetter();
112 bool calleeScriptGetter();
113 bool inspectableGetter();
114 bool optimizedOutGetter();
118 bool getVariableMethod();
119 bool setVariableMethod();
121 using Method
= bool (CallData::*)();
123 template <Method MyMethod
>
124 static bool ToNative(JSContext
* cx
, unsigned argc
, Value
* vp
);
127 template <DebuggerEnvironment::CallData::Method MyMethod
>
129 bool DebuggerEnvironment::CallData::ToNative(JSContext
* cx
, unsigned argc
,
131 CallArgs args
= CallArgsFromVp(argc
, vp
);
133 Rooted
<DebuggerEnvironment
*> environment(
134 cx
, DebuggerEnvironment_checkThis(cx
, args
));
139 CallData
data(cx
, args
, environment
);
140 return (data
.*MyMethod
)();
144 bool DebuggerEnvironment::construct(JSContext
* cx
, unsigned argc
, Value
* vp
) {
145 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_NO_CONSTRUCTOR
,
146 "Debugger.Environment");
150 static bool IsDeclarative(Env
* env
) {
151 return env
->is
<DebugEnvironmentProxy
>() &&
152 env
->as
<DebugEnvironmentProxy
>().isForDeclarative();
155 template <typename T
>
156 static bool IsDebugEnvironmentWrapper(Env
* env
) {
157 return env
->is
<DebugEnvironmentProxy
>() &&
158 env
->as
<DebugEnvironmentProxy
>().environment().is
<T
>();
161 bool DebuggerEnvironment::CallData::typeGetter() {
162 if (!environment
->requireDebuggee(cx
)) {
166 DebuggerEnvironmentType type
= environment
->type();
170 case DebuggerEnvironmentType::Declarative
:
173 case DebuggerEnvironmentType::With
:
176 case DebuggerEnvironmentType::Object
:
181 JSAtom
* str
= Atomize(cx
, s
, strlen(s
));
186 args
.rval().setString(str
);
190 bool DebuggerEnvironment::CallData::scopeKindGetter() {
191 if (!environment
->requireDebuggee(cx
)) {
195 Maybe
<ScopeKind
> kind
= environment
->scopeKind();
197 const char* s
= ScopeKindString(*kind
);
198 JSAtom
* str
= Atomize(cx
, s
, strlen(s
));
202 args
.rval().setString(str
);
204 args
.rval().setNull();
210 bool DebuggerEnvironment::CallData::parentGetter() {
211 if (!environment
->requireDebuggee(cx
)) {
215 Rooted
<DebuggerEnvironment
*> result(cx
);
216 if (!environment
->getParent(cx
, &result
)) {
220 args
.rval().setObjectOrNull(result
);
224 bool DebuggerEnvironment::CallData::objectGetter() {
225 if (!environment
->requireDebuggee(cx
)) {
229 if (environment
->type() == DebuggerEnvironmentType::Declarative
) {
230 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
231 JSMSG_DEBUG_NO_ENV_OBJECT
);
235 Rooted
<DebuggerObject
*> result(cx
);
236 if (!environment
->getObject(cx
, &result
)) {
240 args
.rval().setObject(*result
);
244 bool DebuggerEnvironment::CallData::calleeScriptGetter() {
245 if (!environment
->requireDebuggee(cx
)) {
249 Rooted
<DebuggerScript
*> result(cx
);
250 if (!environment
->getCalleeScript(cx
, &result
)) {
254 args
.rval().setObjectOrNull(result
);
258 bool DebuggerEnvironment::CallData::inspectableGetter() {
259 args
.rval().setBoolean(environment
->isDebuggee());
263 bool DebuggerEnvironment::CallData::optimizedOutGetter() {
264 args
.rval().setBoolean(environment
->isOptimized());
268 bool DebuggerEnvironment::CallData::namesMethod() {
269 if (!environment
->requireDebuggee(cx
)) {
273 RootedIdVector
ids(cx
);
274 if (!DebuggerEnvironment::getNames(cx
, environment
, &ids
)) {
278 JSObject
* obj
= IdVectorToArray(cx
, ids
);
283 args
.rval().setObject(*obj
);
287 bool DebuggerEnvironment::CallData::findMethod() {
288 if (!args
.requireAtLeast(cx
, "Debugger.Environment.find", 1)) {
293 if (!ValueToIdentifier(cx
, args
[0], &id
)) {
297 if (!environment
->requireDebuggee(cx
)) {
301 Rooted
<DebuggerEnvironment
*> result(cx
);
302 if (!DebuggerEnvironment::find(cx
, environment
, id
, &result
)) {
306 args
.rval().setObjectOrNull(result
);
310 bool DebuggerEnvironment::CallData::getVariableMethod() {
311 if (!args
.requireAtLeast(cx
, "Debugger.Environment.getVariable", 1)) {
316 if (!ValueToIdentifier(cx
, args
[0], &id
)) {
320 if (!environment
->requireDebuggee(cx
)) {
324 return DebuggerEnvironment::getVariable(cx
, environment
, id
, args
.rval());
327 bool DebuggerEnvironment::CallData::setVariableMethod() {
328 if (!args
.requireAtLeast(cx
, "Debugger.Environment.setVariable", 2)) {
333 if (!ValueToIdentifier(cx
, args
[0], &id
)) {
337 if (!environment
->requireDebuggee(cx
)) {
341 if (!DebuggerEnvironment::setVariable(cx
, environment
, id
, args
[1])) {
345 args
.rval().setUndefined();
349 bool DebuggerEnvironment::requireDebuggee(JSContext
* cx
) const {
351 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
352 JSMSG_DEBUG_NOT_DEBUGGEE
, "Debugger.Environment",
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
),
371 const JSFunctionSpec
DebuggerEnvironment::methods_
[] = {
372 JS_DEBUG_FN("names", namesMethod
, 0), JS_DEBUG_FN("find", findMethod
, 1),
373 JS_DEBUG_FN("getVariable", getVariableMethod
, 1),
374 JS_DEBUG_FN("setVariable", setVariableMethod
, 2), JS_FS_END
};
377 NativeObject
* DebuggerEnvironment::initClass(JSContext
* cx
,
378 Handle
<GlobalObject
*> global
,
379 HandleObject dbgCtor
) {
380 return InitClass(cx
, dbgCtor
, nullptr, nullptr, "Environment", construct
, 0,
381 properties_
, methods_
, nullptr, nullptr);
385 DebuggerEnvironment
* DebuggerEnvironment::create(
386 JSContext
* cx
, HandleObject proto
, HandleObject referent
,
387 Handle
<NativeObject
*> debugger
) {
388 DebuggerEnvironment
* obj
=
389 IsInsideNursery(referent
)
390 ? NewObjectWithGivenProto
<DebuggerEnvironment
>(cx
, proto
)
391 : NewTenuredObjectWithGivenProto
<DebuggerEnvironment
>(cx
, proto
);
396 obj
->setReservedSlotGCThingAsPrivate(ENV_SLOT
, referent
);
397 obj
->setReservedSlot(OWNER_SLOT
, ObjectValue(*debugger
));
403 DebuggerEnvironmentType
DebuggerEnvironment::type() const {
404 // Don't bother switching compartments just to check env's type.
405 if (IsDeclarative(referent())) {
406 return DebuggerEnvironmentType::Declarative
;
408 if (IsDebugEnvironmentWrapper
<WithEnvironmentObject
>(referent())) {
409 return DebuggerEnvironmentType::With
;
411 return DebuggerEnvironmentType::Object
;
414 mozilla::Maybe
<ScopeKind
> DebuggerEnvironment::scopeKind() const {
415 if (!referent()->is
<DebugEnvironmentProxy
>()) {
418 EnvironmentObject
& env
=
419 referent()->as
<DebugEnvironmentProxy
>().environment();
420 Scope
* scope
= GetEnvironmentScope(env
);
421 return scope
? Some(scope
->kind()) : Nothing();
424 bool DebuggerEnvironment::getParent(
425 JSContext
* cx
, MutableHandle
<DebuggerEnvironment
*> result
) const {
426 // Don't bother switching compartments just to get env's parent.
427 Rooted
<Env
*> parent(cx
, referent()->enclosingEnvironment());
433 return owner()->wrapEnvironment(cx
, parent
, result
);
436 bool DebuggerEnvironment::getObject(
437 JSContext
* cx
, MutableHandle
<DebuggerObject
*> result
) const {
438 MOZ_ASSERT(type() != DebuggerEnvironmentType::Declarative
);
440 // Don't bother switching compartments just to get env's object.
441 RootedObject
object(cx
);
442 if (IsDebugEnvironmentWrapper
<WithEnvironmentObject
>(referent())) {
443 object
.set(&referent()
444 ->as
<DebugEnvironmentProxy
>()
446 .as
<WithEnvironmentObject
>()
448 } else if (IsDebugEnvironmentWrapper
<NonSyntacticVariablesObject
>(
450 object
.set(&referent()
451 ->as
<DebugEnvironmentProxy
>()
453 .as
<NonSyntacticVariablesObject
>());
455 object
.set(referent());
456 MOZ_ASSERT(!object
->is
<DebugEnvironmentProxy
>());
459 return owner()->wrapDebuggeeObject(cx
, object
, result
);
462 bool DebuggerEnvironment::getCalleeScript(
463 JSContext
* cx
, MutableHandle
<DebuggerScript
*> result
) const {
464 if (!referent()->is
<DebugEnvironmentProxy
>()) {
469 JSObject
& scope
= referent()->as
<DebugEnvironmentProxy
>().environment();
470 if (!scope
.is
<CallObject
>()) {
475 Rooted
<BaseScript
*> script(cx
, scope
.as
<CallObject
>().callee().baseScript());
477 DebuggerScript
* scriptObject
= owner()->wrapScript(cx
, script
);
482 result
.set(scriptObject
);
486 bool DebuggerEnvironment::isDebuggee() const {
487 MOZ_ASSERT(referent());
488 MOZ_ASSERT(!referent()->is
<EnvironmentObject
>());
490 return owner()->observesGlobal(&referent()->nonCCWGlobal());
493 bool DebuggerEnvironment::isOptimized() const {
494 return referent()->is
<DebugEnvironmentProxy
>() &&
495 referent()->as
<DebugEnvironmentProxy
>().isOptimizedOut();
499 bool DebuggerEnvironment::getNames(JSContext
* cx
,
500 Handle
<DebuggerEnvironment
*> environment
,
501 MutableHandleIdVector result
) {
502 MOZ_ASSERT(environment
->isDebuggee());
503 MOZ_ASSERT(result
.empty());
505 Rooted
<Env
*> referent(cx
, environment
->referent());
508 ar
.emplace(cx
, referent
);
511 if (!GetPropertyKeys(cx
, referent
, JSITER_HIDDEN
, result
)) {
516 result
.eraseIf([](PropertyKey key
) {
517 return !key
.isAtom() || !IsIdentifier(key
.toAtom());
520 for (size_t i
= 0; i
< result
.length(); ++i
) {
521 cx
->markAtom(result
[i
].toAtom());
528 bool DebuggerEnvironment::find(JSContext
* cx
,
529 Handle
<DebuggerEnvironment
*> environment
,
531 MutableHandle
<DebuggerEnvironment
*> result
) {
532 MOZ_ASSERT(environment
->isDebuggee());
534 Rooted
<Env
*> env(cx
, environment
->referent());
535 Debugger
* dbg
= environment
->owner();
543 // This can trigger resolve hooks.
545 for (; env
; env
= env
->enclosingEnvironment()) {
547 if (!HasProperty(cx
, env
, id
, &found
)) {
561 return dbg
->wrapEnvironment(cx
, env
, result
);
565 bool DebuggerEnvironment::getVariable(JSContext
* cx
,
566 Handle
<DebuggerEnvironment
*> environment
,
567 HandleId id
, MutableHandleValue result
) {
568 MOZ_ASSERT(environment
->isDebuggee());
570 Rooted
<Env
*> referent(cx
, environment
->referent());
571 Debugger
* dbg
= environment
->owner();
575 ar
.emplace(cx
, referent
);
579 // This can trigger getters.
583 if (!HasProperty(cx
, referent
, id
, &found
)) {
587 result
.setUndefined();
591 // For DebugEnvironmentProxys, we get sentinel values for optimized out
592 // slots and arguments instead of throwing (the default behavior).
594 // See wrapDebuggeeValue for how the sentinel values are wrapped.
595 if (referent
->is
<DebugEnvironmentProxy
>()) {
596 Rooted
<DebugEnvironmentProxy
*> env(
597 cx
, &referent
->as
<DebugEnvironmentProxy
>());
598 if (!DebugEnvironmentProxy::getMaybeSentinelValue(cx
, env
, id
, result
)) {
602 if (!GetProperty(cx
, referent
, referent
, id
, result
)) {
608 // When we've faked up scope chain objects for optimized-out scopes,
609 // declarative environments may contain internal JSFunction objects, which
610 // we shouldn't expose to the user.
611 if (result
.isObject()) {
612 RootedObject
obj(cx
, &result
.toObject());
613 if (obj
->is
<JSFunction
>() &&
614 IsInternalFunctionObject(obj
->as
<JSFunction
>()))
615 result
.setMagic(JS_OPTIMIZED_OUT
);
618 return dbg
->wrapDebuggeeValue(cx
, result
);
622 bool DebuggerEnvironment::setVariable(JSContext
* cx
,
623 Handle
<DebuggerEnvironment
*> environment
,
624 HandleId id
, HandleValue value_
) {
625 MOZ_ASSERT(environment
->isDebuggee());
627 Rooted
<Env
*> referent(cx
, environment
->referent());
628 Debugger
* dbg
= environment
->owner();
630 RootedValue
value(cx
, value_
);
631 if (!dbg
->unwrapDebuggeeValue(cx
, &value
)) {
637 ar
.emplace(cx
, referent
);
638 if (!cx
->compartment()->wrap(cx
, &value
)) {
643 // This can trigger setters.
646 // Make sure the environment actually has the specified binding.
648 if (!HasProperty(cx
, referent
, id
, &found
)) {
652 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
653 JSMSG_DEBUG_VARIABLE_NOT_FOUND
);
657 // Just set the property.
658 if (!SetProperty(cx
, referent
, id
, value
)) {