Coverage Report

Created: 2018-09-25 14:53

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