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