Bug 1865597 - Add error checking when initializing parallel marking and disable on...
[gecko.git] / js / src / debugger / Environment.cpp
blob9c90573c25707780adab413fb4445afa4dae8912
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
42 namespace js {
43 class GlobalObject;
46 using namespace js;
48 using mozilla::Maybe;
49 using mozilla::Nothing;
50 using mozilla::Some;
52 const JSClassOps DebuggerEnvironment::classOps_ = {
53 nullptr, // addProperty
54 nullptr, // delProperty
55 nullptr, // enumerate
56 nullptr, // newEnumerate
57 nullptr, // resolve
58 nullptr, // mayResolve
59 nullptr, // finalize
60 nullptr, // call
61 nullptr, // construct
62 CallTraceMethod<DebuggerEnvironment>, // trace
65 const JSClass DebuggerEnvironment::class_ = {
66 "Environment",
67 JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS),
68 &classOps_};
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);
82 static DebuggerEnvironment* DebuggerEnvironment_checkThis(
83 JSContext* cx, const CallArgs& args) {
84 JSObject* thisobj = RequireObject(cx, args.thisv());
85 if (!thisobj) {
86 return nullptr;
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;
95 return &thisobj->as<DebuggerEnvironment>();
98 struct MOZ_STACK_CLASS DebuggerEnvironment::CallData {
99 JSContext* cx;
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) {}
108 bool typeGetter();
109 bool scopeKindGetter();
110 bool parentGetter();
111 bool objectGetter();
112 bool calleeScriptGetter();
113 bool inspectableGetter();
114 bool optimizedOutGetter();
116 bool namesMethod();
117 bool findMethod();
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>
128 /* static */
129 bool DebuggerEnvironment::CallData::ToNative(JSContext* cx, unsigned argc,
130 Value* vp) {
131 CallArgs args = CallArgsFromVp(argc, vp);
133 Rooted<DebuggerEnvironment*> environment(
134 cx, DebuggerEnvironment_checkThis(cx, args));
135 if (!environment) {
136 return false;
139 CallData data(cx, args, environment);
140 return (data.*MyMethod)();
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;
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)) {
163 return false;
166 DebuggerEnvironmentType type = environment->type();
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;
181 JSAtom* str = Atomize(cx, s, strlen(s));
182 if (!str) {
183 return false;
186 args.rval().setString(str);
187 return true;
190 bool DebuggerEnvironment::CallData::scopeKindGetter() {
191 if (!environment->requireDebuggee(cx)) {
192 return false;
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;
202 args.rval().setString(str);
203 } else {
204 args.rval().setNull();
207 return true;
210 bool DebuggerEnvironment::CallData::parentGetter() {
211 if (!environment->requireDebuggee(cx)) {
212 return false;
215 Rooted<DebuggerEnvironment*> result(cx);
216 if (!environment->getParent(cx, &result)) {
217 return false;
220 args.rval().setObjectOrNull(result);
221 return true;
224 bool DebuggerEnvironment::CallData::objectGetter() {
225 if (!environment->requireDebuggee(cx)) {
226 return false;
229 if (environment->type() == DebuggerEnvironmentType::Declarative) {
230 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
231 JSMSG_DEBUG_NO_ENV_OBJECT);
232 return false;
235 Rooted<DebuggerObject*> result(cx);
236 if (!environment->getObject(cx, &result)) {
237 return false;
240 args.rval().setObject(*result);
241 return true;
244 bool DebuggerEnvironment::CallData::calleeScriptGetter() {
245 if (!environment->requireDebuggee(cx)) {
246 return false;
249 Rooted<DebuggerScript*> result(cx);
250 if (!environment->getCalleeScript(cx, &result)) {
251 return false;
254 args.rval().setObjectOrNull(result);
255 return true;
258 bool DebuggerEnvironment::CallData::inspectableGetter() {
259 args.rval().setBoolean(environment->isDebuggee());
260 return true;
263 bool DebuggerEnvironment::CallData::optimizedOutGetter() {
264 args.rval().setBoolean(environment->isOptimized());
265 return true;
268 bool DebuggerEnvironment::CallData::namesMethod() {
269 if (!environment->requireDebuggee(cx)) {
270 return false;
273 RootedIdVector ids(cx);
274 if (!DebuggerEnvironment::getNames(cx, environment, &ids)) {
275 return false;
278 JSObject* obj = IdVectorToArray(cx, ids);
279 if (!obj) {
280 return false;
283 args.rval().setObject(*obj);
284 return true;
287 bool DebuggerEnvironment::CallData::findMethod() {
288 if (!args.requireAtLeast(cx, "Debugger.Environment.find", 1)) {
289 return false;
292 if (!environment->requireDebuggee(cx)) {
293 return false;
296 RootedId id(cx);
297 if (!ValueToIdentifier(cx, args[0], &id)) {
298 return false;
301 Rooted<DebuggerEnvironment*> result(cx);
302 if (!DebuggerEnvironment::find(cx, environment, id, &result)) {
303 return false;
306 args.rval().setObjectOrNull(result);
307 return true;
310 bool DebuggerEnvironment::CallData::getVariableMethod() {
311 if (!args.requireAtLeast(cx, "Debugger.Environment.getVariable", 1)) {
312 return false;
315 if (!environment->requireDebuggee(cx)) {
316 return false;
319 RootedId id(cx);
320 if (!ValueToIdentifier(cx, args[0], &id)) {
321 return false;
324 return DebuggerEnvironment::getVariable(cx, environment, id, args.rval());
327 bool DebuggerEnvironment::CallData::setVariableMethod() {
328 if (!args.requireAtLeast(cx, "Debugger.Environment.setVariable", 2)) {
329 return false;
332 if (!environment->requireDebuggee(cx)) {
333 return false;
336 RootedId id(cx);
337 if (!ValueToIdentifier(cx, args[0], &id)) {
338 return false;
341 if (!DebuggerEnvironment::setVariable(cx, environment, id, args[1])) {
342 return false;
345 args.rval().setUndefined();
346 return true;
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");
355 return false;
358 return true;
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};
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};
376 /* static */
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);
384 /* static */
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);
392 if (!obj) {
393 return nullptr;
396 obj->setReservedSlotGCThingAsPrivate(ENV_SLOT, referent);
397 obj->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));
399 return obj;
402 /* static */
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>()) {
416 return Nothing();
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());
428 if (!parent) {
429 result.set(nullptr);
430 return true;
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>()
445 .environment()
446 .as<WithEnvironmentObject>()
447 .object());
448 } else if (IsDebugEnvironmentWrapper<NonSyntacticVariablesObject>(
449 referent())) {
450 object.set(&referent()
451 ->as<DebugEnvironmentProxy>()
452 .environment()
453 .as<NonSyntacticVariablesObject>());
454 } else {
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>()) {
465 result.set(nullptr);
466 return true;
469 JSObject& scope = referent()->as<DebugEnvironmentProxy>().environment();
470 if (!scope.is<CallObject>()) {
471 result.set(nullptr);
472 return true;
475 Rooted<BaseScript*> script(cx, scope.as<CallObject>().callee().baseScript());
477 DebuggerScript* scriptObject = owner()->wrapScript(cx, script);
478 if (!scriptObject) {
479 return false;
482 result.set(scriptObject);
483 return true;
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();
498 /* static */
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());
507 Maybe<AutoRealm> ar;
508 ar.emplace(cx, referent);
510 ErrorCopier ec(ar);
511 if (!GetPropertyKeys(cx, referent, JSITER_HIDDEN, result)) {
512 return false;
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());
524 return true;
527 /* static */
528 bool DebuggerEnvironment::find(JSContext* cx,
529 Handle<DebuggerEnvironment*> environment,
530 HandleId id,
531 MutableHandle<DebuggerEnvironment*> result) {
532 MOZ_ASSERT(environment->isDebuggee());
534 Rooted<Env*> env(cx, environment->referent());
535 Debugger* dbg = environment->owner();
538 Maybe<AutoRealm> ar;
539 ar.emplace(cx, env);
541 cx->markId(id);
543 // This can trigger resolve hooks.
544 ErrorCopier ec(ar);
545 for (; env; env = env->enclosingEnvironment()) {
546 bool found;
547 if (!HasProperty(cx, env, id, &found)) {
548 return false;
550 if (found) {
551 break;
556 if (!env) {
557 result.set(nullptr);
558 return true;
561 return dbg->wrapEnvironment(cx, env, result);
564 /* static */
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();
574 Maybe<AutoRealm> ar;
575 ar.emplace(cx, referent);
577 cx->markId(id);
579 // This can trigger getters.
580 ErrorCopier ec(ar);
582 bool found;
583 if (!HasProperty(cx, referent, id, &found)) {
584 return false;
586 if (!found) {
587 result.setUndefined();
588 return true;
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)) {
599 return false;
601 } else {
602 if (!GetProperty(cx, referent, referent, id, result)) {
603 return false;
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);
621 /* static */
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)) {
632 return false;
636 Maybe<AutoRealm> ar;
637 ar.emplace(cx, referent);
638 if (!cx->compartment()->wrap(cx, &value)) {
639 return false;
641 cx->markId(id);
643 // This can trigger setters.
644 ErrorCopier ec(ar);
646 // Make sure the environment actually has the specified binding.
647 bool found;
648 if (!HasProperty(cx, referent, id, &found)) {
649 return false;
651 if (!found) {
652 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
653 JSMSG_DEBUG_VARIABLE_NOT_FOUND);
654 return false;
657 // Just set the property.
658 if (!SetProperty(cx, referent, id, value)) {
659 return false;
663 return true;