tor-browser

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

DebugScript.cpp (13074B)


      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/DebugScript.h"
      8 
      9 #include "mozilla/Assertions.h"  // for AssertionConditionType
     10 #include "mozilla/HashTable.h"   // for HashMapEntry, HashTable<>::Ptr, HashMap
     11 #include "mozilla/UniquePtr.h"   // for UniquePtr
     12 
     13 #include <utility>  // for std::move
     14 
     15 #include "debugger/DebugAPI.h"    // for DebugAPI
     16 #include "debugger/Debugger.h"    // for JSBreakpointSite, Breakpoint
     17 #include "gc/Cell.h"              // for TenuredCell
     18 #include "gc/GCContext.h"         // for JS::GCContext
     19 #include "gc/GCEnum.h"            // for MemoryUse, MemoryUse::BreakpointSite
     20 #include "gc/Marking.h"           // for IsAboutToBeFinalized
     21 #include "gc/Zone.h"              // for Zone
     22 #include "gc/ZoneAllocator.h"     // for AddCellMemory
     23 #include "jit/BaselineJIT.h"      // for BaselineScript
     24 #include "vm/BytecodeIterator.h"  // for AllBytecodesIterable
     25 #include "vm/JSContext.h"         // for JSContext
     26 #include "vm/JSScript.h"          // for JSScript, DebugScriptMap
     27 #include "vm/NativeObject.h"      // for NativeObject
     28 #include "vm/Realm.h"             // for Realm, AutoRealm
     29 #include "vm/Runtime.h"           // for ReportOutOfMemory
     30 #include "vm/Stack.h"             // for ActivationIterator, Activation
     31 
     32 #include "gc/GC-inl.h"                // for ZoneCellIter
     33 #include "gc/GCContext-inl.h"         // for JS::GCContext::free_
     34 #include "gc/Marking-inl.h"           // for CheckGCThingAfterMovingGC
     35 #include "gc/WeakMap-inl.h"           // for WeakMap::remove
     36 #include "vm/BytecodeIterator-inl.h"  // for AllBytecodesIterable
     37 #include "vm/JSContext-inl.h"         // for JSContext::check
     38 #include "vm/JSObject-inl.h"          // for NewObjectWithGivenProto
     39 #include "vm/JSScript-inl.h"          // for JSScript::hasBaselineScript
     40 #include "vm/Realm-inl.h"             // for AutoRealm::AutoRealm
     41 
     42 namespace js {
     43 
     44 const JSClass DebugScriptObject::class_ = {
     45    "DebugScriptObject",
     46    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE,
     47    &classOps_,
     48    JS_NULL_CLASS_SPEC,
     49 };
     50 
     51 const JSClassOps DebugScriptObject::classOps_ = {
     52    nullptr,                      // addProperty
     53    nullptr,                      // delProperty
     54    nullptr,                      // enumerate
     55    nullptr,                      // newEnumerate
     56    nullptr,                      // resolve
     57    nullptr,                      // mayResolve
     58    DebugScriptObject::finalize,  // finalize
     59    nullptr,                      // call
     60    nullptr,                      // construct
     61    DebugScriptObject::trace,     // trace
     62 };
     63 
     64 /* static */
     65 DebugScriptObject* DebugScriptObject::create(JSContext* cx,
     66                                             UniqueDebugScript debugScript,
     67                                             size_t nbytes) {
     68  auto* object = NewObjectWithGivenProto<DebugScriptObject>(cx, nullptr);
     69  if (!object) {
     70    return nullptr;
     71  }
     72 
     73  object->initReservedSlot(ScriptSlot, PrivateValue(debugScript.release()));
     74  AddCellMemory(object, nbytes, MemoryUse::ScriptDebugScript);
     75 
     76  return object;
     77 }
     78 
     79 DebugScript* DebugScriptObject::debugScript() const {
     80  return maybePtrFromReservedSlot<DebugScript>(ScriptSlot);
     81 }
     82 
     83 /* static */
     84 void DebugScriptObject::trace(JSTracer* trc, JSObject* obj) {
     85  DebugScript* debugScript = obj->as<DebugScriptObject>().debugScript();
     86  if (debugScript) {
     87    debugScript->trace(trc);
     88  }
     89 }
     90 
     91 /* static */
     92 void DebugScriptObject::finalize(JS::GCContext* gcx, JSObject* obj) {
     93  DebugScriptObject* object = &obj->as<DebugScriptObject>();
     94  DebugScript* debugScript = object->debugScript();
     95  if (debugScript) {
     96    debugScript->delete_(gcx, object);
     97  }
     98 }
     99 
    100 /* static */
    101 DebugScript* DebugScript::get(JSScript* script) {
    102  MOZ_ASSERT(script->hasDebugScript());
    103  DebugScriptMap* map = script->zone()->debugScriptMap;
    104  MOZ_ASSERT(map);
    105  DebugScriptObject* object = map->get(script);
    106  MOZ_ASSERT(object);
    107  return object->as<DebugScriptObject>().debugScript();
    108 }
    109 
    110 /* static */
    111 DebugScript* DebugScript::getUnbarriered(JSScript* script) {
    112  MOZ_ASSERT(script->hasDebugScript());
    113  DebugScriptMap* map = script->zone()->debugScriptMap;
    114  MOZ_ASSERT(map);
    115  DebugScriptMap::Ptr p = map->lookupUnbarriered(script);
    116  MOZ_ASSERT(p);
    117  return p->value().get()->as<DebugScriptObject>().debugScript();
    118 }
    119 
    120 /* static */
    121 DebugScript* DebugScript::getOrCreate(JSContext* cx, HandleScript script) {
    122  cx->check(script);
    123 
    124  if (script->hasDebugScript()) {
    125    return get(script);
    126  }
    127 
    128  size_t nbytes = allocSize(script->length());
    129  UniqueDebugScript debug(
    130      reinterpret_cast<DebugScript*>(cx->pod_calloc<uint8_t>(nbytes)));
    131  if (!debug) {
    132    return nullptr;
    133  }
    134 
    135  debug->codeLength = script->length();
    136 
    137  Rooted<DebugScriptObject*> object(
    138      cx, DebugScriptObject::create(cx, std::move(debug), nbytes));
    139  if (!object) {
    140    return nullptr;
    141  }
    142 
    143  /* Create zone's debugScriptMap if necessary. */
    144  Zone* zone = script->zone();
    145  MOZ_ASSERT(cx->zone() == zone);
    146  if (!zone->debugScriptMap) {
    147    DebugScriptMap* map = cx->new_<DebugScriptMap>(cx);
    148    if (!map) {
    149      return nullptr;
    150    }
    151 
    152    zone->debugScriptMap = map;
    153  }
    154 
    155  MOZ_ASSERT(script->hasBytecode());
    156 
    157  if (!zone->debugScriptMap->putNew(script.get(), object.get())) {
    158    ReportOutOfMemory(cx);
    159    return nullptr;
    160  }
    161 
    162  // It is safe to set this: we can't fail after this point.
    163  script->setHasDebugScript(true);
    164 
    165  /*
    166   * Ensure that any Interpret() instances running on this script have
    167   * interrupts enabled. The interrupts must stay enabled until the
    168   * debug state is destroyed.
    169   */
    170  for (ActivationIterator iter(cx); !iter.done(); ++iter) {
    171    if (iter->isInterpreter()) {
    172      iter->asInterpreter()->enableInterruptsIfRunning(script);
    173    }
    174  }
    175 
    176  return object->debugScript();
    177 }
    178 
    179 /* static */
    180 bool DebugScript::hasBreakpointSite(JSScript* script, jsbytecode* pc) {
    181  if (!script->hasDebugScript()) {
    182    return false;
    183  }
    184 
    185  uint32_t offset = script->pcToOffset(pc);
    186  return getUnbarriered(script)->breakpoints[offset];
    187 }
    188 
    189 /* static */
    190 JSBreakpointSite* DebugScript::getBreakpointSite(JSScript* script,
    191                                                 jsbytecode* pc) {
    192  uint32_t offset = script->pcToOffset(pc);
    193  return script->hasDebugScript() ? get(script)->breakpoints[offset] : nullptr;
    194 }
    195 
    196 /* static */
    197 JSBreakpointSite* DebugScript::getOrCreateBreakpointSite(JSContext* cx,
    198                                                         HandleScript script,
    199                                                         jsbytecode* pc) {
    200  AutoRealm ar(cx, script);
    201 
    202  DebugScript* debug = getOrCreate(cx, script);
    203  if (!debug) {
    204    return nullptr;
    205  }
    206 
    207  JSBreakpointSite*& site = debug->breakpoints[script->pcToOffset(pc)];
    208 
    209  if (!site) {
    210    site = cx->new_<JSBreakpointSite>(script, pc);
    211    if (!site) {
    212      return nullptr;
    213    }
    214    debug->numSites++;
    215    AddCellMemory(script, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite);
    216 
    217    if (script->hasBaselineScript()) {
    218      script->baselineScript()->toggleDebugTraps(script, pc);
    219    }
    220  }
    221 
    222  return site;
    223 }
    224 
    225 /* static */
    226 void DebugScript::destroyBreakpointSite(JS::GCContext* gcx, JSScript* script,
    227                                        jsbytecode* pc) {
    228  // Avoid barriers during sweeping. |debug| does not escape.
    229  DebugScript* debug = getUnbarriered(script);
    230 
    231  JSBreakpointSite*& site = debug->breakpoints[script->pcToOffset(pc)];
    232  MOZ_ASSERT(site);
    233  MOZ_ASSERT(site->isEmpty());
    234 
    235  site->delete_(gcx);
    236  site = nullptr;
    237 
    238  debug->numSites--;
    239  if (!debug->needed()) {
    240    DebugAPI::removeDebugScript(gcx, script);
    241  }
    242 
    243  if (script->hasBaselineScript()) {
    244    script->baselineScript()->toggleDebugTraps(script, pc);
    245  }
    246 }
    247 
    248 /* static */
    249 void DebugScript::clearBreakpointsIn(JS::GCContext* gcx, JSScript* script,
    250                                     Debugger* dbg, JSObject* handler) {
    251  MOZ_ASSERT(script);
    252  // Breakpoints hold wrappers in the script's compartment for the handler. Make
    253  // sure we don't try to search for the unwrapped handler.
    254  MOZ_ASSERT_IF(handler, script->compartment() == handler->compartment());
    255 
    256  if (!script->hasDebugScript()) {
    257    return;
    258  }
    259 
    260  AllBytecodesIterable iter(script);
    261  for (BytecodeLocation loc : iter) {
    262    JSBreakpointSite* site = getBreakpointSite(script, loc.toRawBytecode());
    263    if (site) {
    264      Breakpoint* nextbp;
    265      for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
    266        nextbp = bp->nextInSite();
    267        if ((!dbg || bp->debugger == dbg) &&
    268            (!handler || bp->getHandler() == handler)) {
    269          bp->remove(gcx);
    270        }
    271      }
    272    }
    273  }
    274 }
    275 
    276 #ifdef DEBUG
    277 /* static */
    278 uint32_t DebugScript::getStepperCount(JSScript* script) {
    279  return script->hasDebugScript() ? get(script)->stepperCount : 0;
    280 }
    281 #endif  // DEBUG
    282 
    283 /* static */
    284 bool DebugScript::incrementStepperCount(JSContext* cx, HandleScript script) {
    285  cx->check(script);
    286  MOZ_ASSERT(cx->realm()->isDebuggee());
    287 
    288  AutoRealm ar(cx, script);
    289 
    290  DebugScript* debug = getOrCreate(cx, script);
    291  if (!debug) {
    292    return false;
    293  }
    294 
    295  debug->stepperCount++;
    296 
    297  if (debug->stepperCount == 1) {
    298    if (script->hasBaselineScript()) {
    299      script->baselineScript()->toggleDebugTraps(script, nullptr);
    300    }
    301  }
    302 
    303  return true;
    304 }
    305 
    306 /* static */
    307 void DebugScript::decrementStepperCount(JS::GCContext* gcx, JSScript* script) {
    308  // Avoid barriers during sweeping. |debug| does not escape.
    309  DebugScript* debug = getUnbarriered(script);
    310  MOZ_ASSERT(debug);
    311  MOZ_ASSERT(debug->stepperCount > 0);
    312 
    313  debug->stepperCount--;
    314 
    315  if (debug->stepperCount == 0) {
    316    if (script->hasBaselineScript()) {
    317      script->baselineScript()->toggleDebugTraps(script, nullptr);
    318    }
    319 
    320    if (!debug->needed()) {
    321      DebugAPI::removeDebugScript(gcx, script);
    322    }
    323  }
    324 }
    325 
    326 /* static */
    327 bool DebugScript::incrementGeneratorObserverCount(JSContext* cx,
    328                                                  HandleScript script) {
    329  cx->check(script);
    330  MOZ_ASSERT(cx->realm()->isDebuggee());
    331 
    332  AutoRealm ar(cx, script);
    333 
    334  DebugScript* debug = getOrCreate(cx, script);
    335  if (!debug) {
    336    return false;
    337  }
    338 
    339  debug->generatorObserverCount++;
    340 
    341  // It is our caller's responsibility, before bumping the generator observer
    342  // count, to make sure that the baseline code includes the necessary
    343  // JSOp::AfterYield instrumentation by calling
    344  // {ensure,update}ExecutionObservabilityOfScript.
    345  MOZ_ASSERT_IF(script->hasBaselineScript(),
    346                script->baselineScript()->hasDebugInstrumentation());
    347 
    348  return true;
    349 }
    350 
    351 /* static */
    352 void DebugScript::decrementGeneratorObserverCount(JS::GCContext* gcx,
    353                                                  JSScript* script) {
    354  // Avoid barriers during sweeping. |debug| does not escape.
    355  DebugScript* debug = getUnbarriered(script);
    356  MOZ_ASSERT(debug);
    357  MOZ_ASSERT(debug->generatorObserverCount > 0);
    358 
    359  debug->generatorObserverCount--;
    360 
    361  if (!debug->needed()) {
    362    DebugAPI::removeDebugScript(gcx, script);
    363  }
    364 }
    365 
    366 void DebugScript::trace(JSTracer* trc) {
    367  for (size_t i = 0; i < codeLength; i++) {
    368    JSBreakpointSite* site = breakpoints[i];
    369    if (site) {
    370      site->trace(trc);
    371    }
    372  }
    373 }
    374 
    375 /* static */
    376 void DebugAPI::removeDebugScript(JS::GCContext* gcx, JSScript* script) {
    377  if (script->hasDebugScript()) {
    378    if (IsAboutToBeFinalizedUnbarriered(script)) {
    379      // The script is dying and all breakpoint data will be cleaned up.
    380      return;
    381    }
    382 
    383    DebugScriptMap* map = script->zone()->debugScriptMap;
    384    MOZ_ASSERT(map);
    385    MOZ_ASSERT(map->has(script));
    386    map->remove(script);
    387    script->setHasDebugScript(false);
    388 
    389    // The DebugScript will be destroyed at the next GC when its owning
    390    // DebugScriptObject dies.
    391  }
    392 }
    393 
    394 void DebugScript::delete_(JS::GCContext* gcx, DebugScriptObject* owner) {
    395  for (size_t i = 0; i < codeLength; i++) {
    396    JSBreakpointSite* site = breakpoints[i];
    397    if (site) {
    398      site->delete_(gcx);
    399    }
    400  }
    401 
    402  gcx->free_(owner, this, allocSize(codeLength), MemoryUse::ScriptDebugScript);
    403 }
    404 
    405 #ifdef JSGC_HASH_TABLE_CHECKS
    406 /* static */
    407 void DebugAPI::checkDebugScriptAfterMovingGC(DebugScript* ds) {
    408  for (uint32_t i = 0; i < ds->numSites; i++) {
    409    JSBreakpointSite* site = ds->breakpoints[i];
    410    if (site) {
    411      CheckGCThingAfterMovingGC(site->script.get());
    412    }
    413  }
    414 }
    415 #endif  // JSGC_HASH_TABLE_CHECKS
    416 
    417 /* static */
    418 bool DebugAPI::stepModeEnabledSlow(JSScript* script) {
    419  return DebugScript::getUnbarriered(script)->stepperCount > 0;
    420 }
    421 
    422 /* static */
    423 bool DebugAPI::hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc) {
    424  return DebugScript::hasBreakpointSite(script, pc);
    425 }
    426 
    427 /* static */
    428 void DebugAPI::traceDebugScriptMap(JSTracer* trc, DebugScriptMap* map) {
    429  map->trace(trc);
    430 }
    431 
    432 /* static */
    433 void DebugAPI::deleteDebugScriptMap(DebugScriptMap* map) { js_delete(map); }
    434 
    435 }  // namespace js