LCOV - code coverage report
Current view: top level - js/src/vm - DebuggerMemory.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 3 193 1.6 %
Date: 2017-07-14 16:53:18 Functions: 1 18 5.6 %
Legend: Lines: hit not hit

          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             : };

Generated by: LCOV version 1.13