tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DebuggerMemory.cpp (13145B)


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