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/DebuggerMemory.h"
8
9
#include "mozilla/Maybe.h"
10
#include "mozilla/Move.h"
11
#include "mozilla/Vector.h"
12
13
#include <stdlib.h>
14
15
#include "builtin/MapObject.h"
16
#include "debugger/Debugger.h"
17
#include "gc/Marking.h"
18
#include "js/AllocPolicy.h"
19
#include "js/Debug.h"
20
#include "js/PropertySpec.h"
21
#include "js/TracingAPI.h"
22
#include "js/UbiNode.h"
23
#include "js/UbiNodeCensus.h"
24
#include "js/Utility.h"
25
#include "vm/GlobalObject.h"
26
#include "vm/JSContext.h"
27
#include "vm/Realm.h"
28
#include "vm/SavedStacks.h"
29
30
#include "debugger/Debugger-inl.h"
31
#include "vm/NativeObject-inl.h"
32
33
using namespace js;
34
35
using mozilla::Maybe;
36
using mozilla::Nothing;
37
38
/* static */
39
DebuggerMemory* DebuggerMemory::create(JSContext* cx, Debugger* dbg) {
40
Value memoryProtoValue =
41
dbg->object->getReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO);
42
RootedObject memoryProto(cx, &memoryProtoValue.toObject());
43
Rooted<DebuggerMemory*> memory(
44
cx, NewObjectWithGivenProto<DebuggerMemory>(cx, memoryProto));
45
if (!memory) {
46
return nullptr;
47
}
48
49
dbg->object->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_INSTANCE,
50
ObjectValue(*memory));
51
memory->setReservedSlot(JSSLOT_DEBUGGER, ObjectValue(*dbg->object));
52
53
return memory;
54
}
55
56
Debugger* DebuggerMemory::getDebugger() {
57
const Value& dbgVal = getReservedSlot(JSSLOT_DEBUGGER);
58
return Debugger::fromJSObject(&dbgVal.toObject());
59
}
60
61
/* static */
62
bool DebuggerMemory::construct(JSContext* cx, unsigned argc, Value* vp) {
63
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
64
"Debugger.Source");
65
return false;
66
}
67
68
/* static */ const JSClass DebuggerMemory::class_ = {
69
"Memory", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_COUNT)};
70
71
/* static */
72
DebuggerMemory* DebuggerMemory::checkThis(JSContext* cx, CallArgs& args) {
73
const Value& thisValue = args.thisv();
74
75
if (!thisValue.isObject()) {
76
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
77
JSMSG_OBJECT_REQUIRED,
78
InformalValueTypeName(thisValue));
79
return nullptr;
80
}
81
82
JSObject& thisObject = thisValue.toObject();
83
if (!thisObject.is<DebuggerMemory>()) {
84
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
85
JSMSG_INCOMPATIBLE_PROTO, class_.name, "method",
86
thisObject.getClass()->name);
87
return nullptr;
88
}
89
90
// Check for Debugger.Memory.prototype, which has the same class as
91
// Debugger.Memory instances, however doesn't actually represent an instance
92
// of Debugger.Memory. It is the only object that is<DebuggerMemory>() but
93
// doesn't have a Debugger instance.
94
if (thisObject.as<DebuggerMemory>()
95
.getReservedSlot(JSSLOT_DEBUGGER)
96
.isUndefined()) {
97
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
98
JSMSG_INCOMPATIBLE_PROTO, class_.name, "method",
99
"prototype object");
100
return nullptr;
101
}
102
103
return &thisObject.as<DebuggerMemory>();
104
}
105
106
struct MOZ_STACK_CLASS DebuggerMemory::CallData {
107
JSContext* cx;
108
const CallArgs& args;
109
110
Handle<DebuggerMemory*> memory;
111
112
CallData(JSContext* cx, const CallArgs& args, Handle<DebuggerMemory*> memory)
113
: cx(cx), args(args), memory(memory) {}
114
115
// Accessor properties of Debugger.Memory.prototype.
116
117
bool setTrackingAllocationSites();
118
bool getTrackingAllocationSites();
119
bool setMaxAllocationsLogLength();
120
bool getMaxAllocationsLogLength();
121
bool setAllocationSamplingProbability();
122
bool getAllocationSamplingProbability();
123
bool getAllocationsLogOverflowed();
124
bool getOnGarbageCollection();
125
bool setOnGarbageCollection();
126
127
// Function properties of Debugger.Memory.prototype.
128
129
bool takeCensus();
130
bool drainAllocationsLog();
131
132
using Method = bool (CallData::*)();
133
134
template <Method MyMethod>
135
static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
136
};
137
138
template <DebuggerMemory::CallData::Method MyMethod>
139
/* static */
140
bool DebuggerMemory::CallData::ToNative(JSContext* cx, unsigned argc,
141
Value* vp) {
142
CallArgs args = CallArgsFromVp(argc, vp);
143
144
Rooted<DebuggerMemory*> memory(cx, DebuggerMemory::checkThis(cx, args));
145
if (!memory) {
146
return false;
147
}
148
149
CallData data(cx, args, memory);
150
return (data.*MyMethod)();
151
}
152
153
static bool undefined(const CallArgs& args) {
154
args.rval().setUndefined();
155
return true;
156
}
157
158
bool DebuggerMemory::CallData::setTrackingAllocationSites() {
159
if (!args.requireAtLeast(cx, "(set trackingAllocationSites)", 1)) {
160
return false;
161
}
162
163
Debugger* dbg = memory->getDebugger();
164
bool enabling = ToBoolean(args[0]);
165
166
if (enabling == dbg->trackingAllocationSites) {
167
return undefined(args);
168
}
169
170
dbg->trackingAllocationSites = enabling;
171
172
if (enabling) {
173
if (!dbg->addAllocationsTrackingForAllDebuggees(cx)) {
174
dbg->trackingAllocationSites = false;
175
return false;
176
}
177
} else {
178
dbg->removeAllocationsTrackingForAllDebuggees();
179
}
180
181
return undefined(args);
182
}
183
184
bool DebuggerMemory::CallData::getTrackingAllocationSites() {
185
args.rval().setBoolean(memory->getDebugger()->trackingAllocationSites);
186
return true;
187
}
188
189
bool DebuggerMemory::CallData::drainAllocationsLog() {
190
Debugger* dbg = memory->getDebugger();
191
192
if (!dbg->trackingAllocationSites) {
193
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
194
JSMSG_NOT_TRACKING_ALLOCATIONS,
195
"drainAllocationsLog");
196
return false;
197
}
198
199
size_t length = dbg->allocationsLog.length();
200
201
RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
202
if (!result) {
203
return false;
204
}
205
result->ensureDenseInitializedLength(cx, 0, length);
206
207
for (size_t i = 0; i < length; i++) {
208
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
209
if (!obj) {
210
return false;
211
}
212
213
// Don't pop the AllocationsLogEntry yet. The queue's links are followed
214
// by the GC to find the AllocationsLogEntry, but are not barriered, so
215
// we must edit them with great care. Use the queue entry in place, and
216
// then pop and delete together.
217
Debugger::AllocationsLogEntry& entry = dbg->allocationsLog.front();
218
219
RootedValue frame(cx, ObjectOrNullValue(entry.frame));
220
if (!DefineDataProperty(cx, obj, cx->names().frame, frame)) {
221
return false;
222
}
223
224
double when =
225
(entry.when - mozilla::TimeStamp::ProcessCreation()).ToMilliseconds();
226
RootedValue timestampValue(cx, NumberValue(when));
227
if (!DefineDataProperty(cx, obj, cx->names().timestamp, timestampValue)) {
228
return false;
229
}
230
231
RootedString className(
232
cx, Atomize(cx, entry.className, strlen(entry.className)));
233
if (!className) {
234
return false;
235
}
236
RootedValue classNameValue(cx, StringValue(className));
237
if (!DefineDataProperty(cx, obj, cx->names().class_, classNameValue)) {
238
return false;
239
}
240
241
RootedValue ctorName(cx, NullValue());
242
if (entry.ctorName) {
243
ctorName.setString(entry.ctorName);
244
}
245
if (!DefineDataProperty(cx, obj, cx->names().constructor, ctorName)) {
246
return false;
247
}
248
249
RootedValue size(cx, NumberValue(entry.size));
250
if (!DefineDataProperty(cx, obj, cx->names().size, size)) {
251
return false;
252
}
253
254
RootedValue inNursery(cx, BooleanValue(entry.inNursery));
255
if (!DefineDataProperty(cx, obj, cx->names().inNursery, inNursery)) {
256
return false;
257
}
258
259
result->setDenseElement(i, ObjectValue(*obj));
260
261
// Pop the front queue entry, and delete it immediately, so that the GC
262
// sees the AllocationsLogEntry's HeapPtr barriers run atomically with
263
// the change to the graph (the queue link).
264
dbg->allocationsLog.popFront();
265
}
266
267
dbg->allocationsLogOverflowed = false;
268
args.rval().setObject(*result);
269
return true;
270
}
271
272
bool DebuggerMemory::CallData::getMaxAllocationsLogLength() {
273
args.rval().setInt32(memory->getDebugger()->maxAllocationsLogLength);
274
return true;
275
}
276
277
bool DebuggerMemory::CallData::setMaxAllocationsLogLength() {
278
if (!args.requireAtLeast(cx, "(set maxAllocationsLogLength)", 1)) {
279
return false;
280
}
281
282
int32_t max;
283
if (!ToInt32(cx, args[0], &max)) {
284
return false;
285
}
286
287
if (max < 1) {
288
JS_ReportErrorNumberASCII(
289
cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
290
"(set maxAllocationsLogLength)'s parameter", "not a positive integer");
291
return false;
292
}
293
294
Debugger* dbg = memory->getDebugger();
295
dbg->maxAllocationsLogLength = max;
296
297
while (dbg->allocationsLog.length() > dbg->maxAllocationsLogLength) {
298
dbg->allocationsLog.popFront();
299
}
300
301
args.rval().setUndefined();
302
return true;
303
}
304
305
bool DebuggerMemory::CallData::getAllocationSamplingProbability() {
306
args.rval().setDouble(memory->getDebugger()->allocationSamplingProbability);
307
return true;
308
}
309
310
bool DebuggerMemory::CallData::setAllocationSamplingProbability() {
311
if (!args.requireAtLeast(cx, "(set allocationSamplingProbability)", 1)) {
312
return false;
313
}
314
315
double probability;
316
if (!ToNumber(cx, args[0], &probability)) {
317
return false;
318
}
319
320
// Careful! This must also reject NaN.
321
if (!(0.0 <= probability && probability <= 1.0)) {
322
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
323
JSMSG_UNEXPECTED_TYPE,
324
"(set allocationSamplingProbability)'s parameter",
325
"not a number between 0 and 1");
326
return false;
327
}
328
329
Debugger* dbg = memory->getDebugger();
330
if (dbg->allocationSamplingProbability != probability) {
331
dbg->allocationSamplingProbability = probability;
332
333
// If this is a change any debuggees would observe, have all debuggee
334
// realms recompute their sampling probabilities.
335
if (dbg->trackingAllocationSites) {
336
for (auto r = dbg->debuggees.all(); !r.empty(); r.popFront()) {
337
r.front()->realm()->chooseAllocationSamplingProbability();
338
}
339
}
340
}
341
342
args.rval().setUndefined();
343
return true;
344
}
345
346
bool DebuggerMemory::CallData::getAllocationsLogOverflowed() {
347
args.rval().setBoolean(memory->getDebugger()->allocationsLogOverflowed);
348
return true;
349
}
350
351
bool DebuggerMemory::CallData::getOnGarbageCollection() {
352
return Debugger::getHookImpl(cx, args, *memory->getDebugger(),
353
Debugger::OnGarbageCollection);
354
}
355
356
bool DebuggerMemory::CallData::setOnGarbageCollection() {
357
return Debugger::setHookImpl(cx, args, *memory->getDebugger(),
358
Debugger::OnGarbageCollection);
359
}
360
361
/* Debugger.Memory.prototype.takeCensus */
362
363
JS_PUBLIC_API void JS::dbg::SetDebuggerMallocSizeOf(
364
JSContext* cx, mozilla::MallocSizeOf mallocSizeOf) {
365
cx->runtime()->debuggerMallocSizeOf = mallocSizeOf;
366
}
367
368
JS_PUBLIC_API mozilla::MallocSizeOf JS::dbg::GetDebuggerMallocSizeOf(
369
JSContext* cx) {
370
return cx->runtime()->debuggerMallocSizeOf;
371
}
372
373
using JS::ubi::Census;
374
using JS::ubi::CountBasePtr;
375
using JS::ubi::CountTypePtr;
376
377
// The takeCensus function works in three phases:
378
//
379
// 1) We examine the 'breakdown' property of our 'options' argument, and
380
// use that to build a CountType tree.
381
//
382
// 2) We create a count node for the root of our CountType tree, and then walk
383
// the heap, counting each node we find, expanding our tree of counts as we
384
// go.
385
//
386
// 3) We walk the tree of counts and produce JavaScript objects reporting the
387
// accumulated results.
388
bool DebuggerMemory::CallData::takeCensus() {
389
Census census(cx);
390
CountTypePtr rootType;
391
392
RootedObject options(cx);
393
if (args.get(0).isObject()) {
394
options = &args[0].toObject();
395
}
396
397
if (!JS::ubi::ParseCensusOptions(cx, census, options, rootType)) {
398
return false;
399
}
400
401
JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
402
if (!rootCount) {
403
return false;
404
}
405
JS::ubi::CensusHandler handler(census, rootCount,
406
cx->runtime()->debuggerMallocSizeOf);
407
408
Debugger* dbg = memory->getDebugger();
409
RootedObject dbgObj(cx, dbg->object);
410
411
// Populate our target set of debuggee zones.
412
for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
413
r.popFront()) {
414
if (!census.targetZones.put(r.front()->zone())) {
415
return false;
416
}
417
}
418
419
{
420
Maybe<JS::AutoCheckCannotGC> maybeNoGC;
421
JS::ubi::RootList rootList(cx, maybeNoGC);
422
if (!rootList.init(dbgObj)) {
423
ReportOutOfMemory(cx);
424
return false;
425
}
426
427
JS::ubi::CensusTraversal traversal(cx, handler, maybeNoGC.ref());
428
traversal.wantNames = false;
429
430
if (!traversal.addStart(JS::ubi::Node(&rootList)) ||
431
!traversal.traverse()) {
432
ReportOutOfMemory(cx);
433
return false;
434
}
435
}
436
437
return handler.report(cx, args.rval());
438
}
439
440
/* Debugger.Memory property and method tables. */
441
442
/* static */ const JSPropertySpec DebuggerMemory::properties[] = {
443
JS_DEBUG_PSGS("trackingAllocationSites", getTrackingAllocationSites,
444
setTrackingAllocationSites),
445
JS_DEBUG_PSGS("maxAllocationsLogLength", getMaxAllocationsLogLength,
446
setMaxAllocationsLogLength),
447
JS_DEBUG_PSGS("allocationSamplingProbability",
448
getAllocationSamplingProbability,
449
setAllocationSamplingProbability),
450
JS_DEBUG_PSG("allocationsLogOverflowed", getAllocationsLogOverflowed),
451
JS_DEBUG_PSGS("onGarbageCollection", getOnGarbageCollection,
452
setOnGarbageCollection),
453
JS_PS_END};
454
455
/* static */ const JSFunctionSpec DebuggerMemory::methods[] = {
456
JS_DEBUG_FN("drainAllocationsLog", drainAllocationsLog, 0),
457
JS_DEBUG_FN("takeCensus", takeCensus, 0), JS_FS_END};