Source code

Revision control

Other Tools

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