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