JSScript.cpp (124676B)
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 /* 8 * JS script operations. 9 */ 10 11 #include "vm/JSScript-inl.h" 12 13 #include "mozilla/ArrayUtils.h" 14 #include "mozilla/CheckedInt.h" 15 #include "mozilla/DebugOnly.h" 16 #include "mozilla/Maybe.h" 17 #include "mozilla/MemoryReporting.h" 18 #include "mozilla/ScopeExit.h" 19 #include "mozilla/Span.h" // mozilla::{Span,Span} 20 #include "mozilla/Sprintf.h" 21 #include "mozilla/Utf8.h" 22 #include "mozilla/Vector.h" 23 24 #include <algorithm> 25 #include <new> 26 #include <string.h> 27 #include <utility> 28 29 #include "jstypes.h" 30 31 #include "frontend/BytecodeSection.h" 32 #include "frontend/CompilationStencil.h" // frontend::CompilationStencil, frontend::InitialStencilAndDelazifications 33 #include "frontend/FrontendContext.h" // AutoReportFrontendContext 34 #include "frontend/ParseContext.h" 35 #include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator 36 #include "frontend/Stencil.h" // DumpFunctionFlagsItems, DumpImmutableScriptFlags 37 #include "frontend/StencilXdr.h" // XDRStencilEncoder 38 #include "gc/GCContext.h" 39 #include "jit/BaselineJIT.h" 40 #include "jit/CacheIRHealth.h" 41 #include "jit/Ion.h" 42 #include "jit/IonScript.h" 43 #include "jit/JitCode.h" 44 #include "jit/JitOptions.h" 45 #include "jit/JitRuntime.h" 46 #include "js/CharacterEncoding.h" // JS_EncodeStringToUTF8 47 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOneOrigin, JS::ColumnNumberOffset 48 #include "js/CompileOptions.h" 49 #include "js/experimental/SourceHook.h" 50 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 51 #include "js/HeapAPI.h" // JS::GCCellPtr 52 #include "js/MemoryMetrics.h" 53 #include "js/Printer.h" // js::GenericPrinter, js::Fprinter, js::Sprinter, js::QuoteString 54 #include "js/Transcoding.h" 55 #include "js/UniquePtr.h" 56 #include "js/Utility.h" // JS::UniqueChars 57 #include "js/Value.h" // JS::Value 58 #include "util/Poison.h" 59 #include "util/StringBuilder.h" 60 #include "util/Text.h" 61 #include "vm/BigIntType.h" // JS::BigInt 62 #include "vm/BytecodeIterator.h" 63 #include "vm/BytecodeLocation.h" 64 #include "vm/BytecodeUtil.h" // Disassemble 65 #include "vm/Compression.h" 66 #include "vm/HelperThreadState.h" // js::RunPendingSourceCompressions 67 #include "vm/JSFunction.h" 68 #include "vm/JSObject.h" 69 #include "vm/JSONPrinter.h" // JSONPrinter 70 #include "vm/Opcodes.h" 71 #include "vm/PortableBaselineInterpret.h" 72 #include "vm/Scope.h" // Scope 73 #include "vm/SharedImmutableStringsCache.h" 74 #include "vm/StencilEnums.h" // TryNote, TryNoteKind, ScopeNote 75 #include "vm/StringType.h" // JSString, JSAtom 76 #include "vm/Time.h" // AutoIncrementalTimer 77 #include "vm/ToSource.h" // JS::ValueToSource 78 #ifdef MOZ_VTUNE 79 # include "vtune/VTuneWrapper.h" 80 #endif 81 82 #include "gc/Marking-inl.h" 83 #include "vm/BytecodeIterator-inl.h" 84 #include "vm/BytecodeLocation-inl.h" 85 #include "vm/Compartment-inl.h" 86 #include "vm/JSContext-inl.h" 87 #include "vm/JSObject-inl.h" 88 #include "vm/SharedImmutableStringsCache-inl.h" 89 #include "vm/Stack-inl.h" 90 91 using namespace js; 92 93 using mozilla::CheckedInt; 94 using mozilla::Maybe; 95 using mozilla::PointerRangeSize; 96 using mozilla::Utf8Unit; 97 98 using JS::ReadOnlyCompileOptions; 99 using JS::SourceText; 100 101 bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime* rt) const { 102 return jitCodeRaw() == rt->jitRuntime()->interpreterStub().value; 103 } 104 105 js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const { 106 return MaybeForwarded(sourceObject())->source(); 107 } 108 109 void js::BaseScript::setEnclosingScript(BaseScript* enclosingScript) { 110 MOZ_ASSERT(enclosingScript); 111 warmUpData_.initEnclosingScript(enclosingScript); 112 } 113 114 void js::BaseScript::setEnclosingScope(Scope* enclosingScope) { 115 if (warmUpData_.isEnclosingScript()) { 116 warmUpData_.clearEnclosingScript(); 117 } 118 119 MOZ_ASSERT(enclosingScope); 120 warmUpData_.initEnclosingScope(enclosingScope); 121 } 122 123 void js::BaseScript::finalize(JS::GCContext* gcx) { 124 // Scripts with bytecode may have optional data stored in per-runtime or 125 // per-zone maps. Note that a failed compilation must not have entries since 126 // the script itself will not be marked as having bytecode. 127 if (hasBytecode()) { 128 JSScript* script = this->asJSScript(); 129 130 if (coverage::IsLCovEnabled()) { 131 coverage::CollectScriptCoverage(script, true); 132 } 133 134 script->destroyScriptCounts(); 135 } 136 137 { 138 JSRuntime* rt = gcx->runtime(); 139 if (rt->hasJitRuntime() && rt->jitRuntime()->hasInterpreterEntryMap()) { 140 rt->jitRuntime()->getInterpreterEntryMap()->remove(this); 141 } 142 143 rt->geckoProfiler().onScriptFinalized(this); 144 } 145 146 #ifdef MOZ_VTUNE 147 if (zone()->scriptVTuneIdMap) { 148 // Note: we should only get here if the VTune JIT profiler is running. 149 zone()->scriptVTuneIdMap->remove(this); 150 } 151 #endif 152 153 if (warmUpData_.isJitScript()) { 154 JSScript* script = this->asJSScript(); 155 #ifdef JS_CACHEIR_SPEW 156 maybeUpdateWarmUpCount(script); 157 #endif 158 script->releaseJitScriptOnFinalize(gcx); 159 } 160 161 #ifdef JS_CACHEIR_SPEW 162 if (hasBytecode()) { 163 maybeSpewScriptFinalWarmUpCount(this->asJSScript()); 164 } 165 #endif 166 167 if (data_) { 168 // We don't need to triger any barriers here, just free the memory. 169 size_t size = data_->allocationSize(); 170 AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size, 171 MemCheckKind::MakeNoAccess); 172 gcx->free_(this, data_, size, MemoryUse::ScriptPrivateData); 173 } 174 175 freeSharedData(); 176 } 177 178 js::Scope* js::BaseScript::releaseEnclosingScope() { 179 Scope* enclosing = warmUpData_.toEnclosingScope(); 180 warmUpData_.clearEnclosingScope(); 181 return enclosing; 182 } 183 184 void js::BaseScript::swapData(UniquePtr<PrivateScriptData>& other) { 185 if (data_) { 186 RemoveCellMemory(this, data_->allocationSize(), 187 MemoryUse::ScriptPrivateData); 188 } 189 190 PrivateScriptData* old = data_; 191 data_.set(zone(), other.release()); 192 other.reset(old); 193 194 if (data_) { 195 AddCellMemory(this, data_->allocationSize(), MemoryUse::ScriptPrivateData); 196 } 197 } 198 199 js::Scope* js::BaseScript::enclosingScope() const { 200 MOZ_ASSERT(!warmUpData_.isEnclosingScript(), 201 "Enclosing scope is not computed yet"); 202 203 if (warmUpData_.isEnclosingScope()) { 204 return warmUpData_.toEnclosingScope(); 205 } 206 207 MOZ_ASSERT(data_, "Script doesn't seem to be compiled"); 208 209 return gcthings()[js::GCThingIndex::outermostScopeIndex()] 210 .as<Scope>() 211 .enclosing(); 212 } 213 214 size_t JSScript::numAlwaysLiveFixedSlots() const { 215 if (bodyScope()->is<js::FunctionScope>()) { 216 return bodyScope()->as<js::FunctionScope>().nextFrameSlot(); 217 } 218 if (bodyScope()->is<js::ModuleScope>()) { 219 return bodyScope()->as<js::ModuleScope>().nextFrameSlot(); 220 } 221 if (bodyScope()->is<js::EvalScope>() && 222 bodyScope()->kind() == ScopeKind::StrictEval) { 223 return bodyScope()->as<js::EvalScope>().nextFrameSlot(); 224 } 225 return 0; 226 } 227 228 unsigned JSScript::numArgs() const { 229 if (bodyScope()->is<js::FunctionScope>()) { 230 return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters(); 231 } 232 return 0; 233 } 234 235 bool JSScript::functionHasParameterExprs() const { 236 // Only functions have parameters. 237 js::Scope* scope = bodyScope(); 238 if (!scope->is<js::FunctionScope>()) { 239 return false; 240 } 241 return scope->as<js::FunctionScope>().hasParameterExprs(); 242 } 243 244 bool JSScript::isModule() const { return bodyScope()->is<js::ModuleScope>(); } 245 246 js::ModuleObject* JSScript::module() const { 247 MOZ_ASSERT(isModule()); 248 return bodyScope()->as<js::ModuleScope>().module(); 249 } 250 251 bool JSScript::isGlobalCode() const { 252 return bodyScope()->is<js::GlobalScope>(); 253 } 254 255 js::VarScope* JSScript::functionExtraBodyVarScope() const { 256 MOZ_ASSERT(functionHasExtraBodyVarScope()); 257 for (JS::GCCellPtr gcThing : gcthings()) { 258 if (!gcThing.is<js::Scope>()) { 259 continue; 260 } 261 js::Scope* scope = &gcThing.as<js::Scope>(); 262 if (scope->kind() == js::ScopeKind::FunctionBodyVar) { 263 return &scope->as<js::VarScope>(); 264 } 265 } 266 MOZ_CRASH("Function extra body var scope not found"); 267 } 268 269 bool JSScript::needsBodyEnvironment() const { 270 for (JS::GCCellPtr gcThing : gcthings()) { 271 if (!gcThing.is<js::Scope>()) { 272 continue; 273 } 274 js::Scope* scope = &gcThing.as<js::Scope>(); 275 if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) { 276 return true; 277 } 278 } 279 return false; 280 } 281 282 bool JSScript::isDirectEvalInFunction() const { 283 if (!isForEval()) { 284 return false; 285 } 286 return bodyScope()->hasOnChain(js::ScopeKind::Function); 287 } 288 289 // Initialize the optional arrays in the trailing allocation. This is a set of 290 // offsets that delimit each optional array followed by the arrays themselves. 291 // See comment before 'ImmutableScriptData' for more details. 292 void ImmutableScriptData::initOptionalArrays(Offset* pcursor, 293 uint32_t numResumeOffsets, 294 uint32_t numScopeNotes, 295 uint32_t numTryNotes) { 296 Offset cursor = (*pcursor); 297 298 // The byte arrays must have already been padded. 299 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor), 300 "Bytecode and source notes should be padded to keep alignment"); 301 302 // Each non-empty optional array needs will need an offset to its end. 303 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) + 304 unsigned(numScopeNotes > 0) + 305 unsigned(numTryNotes > 0); 306 307 // Default-initialize the optional-offsets. 308 initElements<Offset>(cursor, numOptionalArrays); 309 cursor += numOptionalArrays * sizeof(Offset); 310 311 // Offset between optional-offsets table and the optional arrays. This is 312 // later used to access the optional-offsets table as well as first optional 313 // array. 314 optArrayOffset_ = cursor; 315 316 // Each optional array that follows must store an end-offset in the offset 317 // table. Assign table entries by using this 'offsetIndex'. The index 0 is 318 // reserved for implicit value 'optArrayOffset'. 319 int offsetIndex = 0; 320 321 // Default-initialize optional 'resumeOffsets'. 322 MOZ_ASSERT(resumeOffsetsOffset() == cursor); 323 if (numResumeOffsets > 0) { 324 initElements<uint32_t>(cursor, numResumeOffsets); 325 cursor += numResumeOffsets * sizeof(uint32_t); 326 setOptionalOffset(++offsetIndex, cursor); 327 } 328 flagsRef().resumeOffsetsEndIndex = offsetIndex; 329 330 // Default-initialize optional 'scopeNotes'. 331 MOZ_ASSERT(scopeNotesOffset() == cursor); 332 if (numScopeNotes > 0) { 333 initElements<ScopeNote>(cursor, numScopeNotes); 334 cursor += numScopeNotes * sizeof(ScopeNote); 335 setOptionalOffset(++offsetIndex, cursor); 336 } 337 flagsRef().scopeNotesEndIndex = offsetIndex; 338 339 // Default-initialize optional 'tryNotes' 340 MOZ_ASSERT(tryNotesOffset() == cursor); 341 if (numTryNotes > 0) { 342 initElements<TryNote>(cursor, numTryNotes); 343 cursor += numTryNotes * sizeof(TryNote); 344 setOptionalOffset(++offsetIndex, cursor); 345 } 346 flagsRef().tryNotesEndIndex = offsetIndex; 347 348 MOZ_ASSERT(endOffset() == cursor); 349 (*pcursor) = cursor; 350 } 351 352 ImmutableScriptData::ImmutableScriptData(uint32_t codeLength, 353 uint32_t noteLength, 354 uint32_t numResumeOffsets, 355 uint32_t numScopeNotes, 356 uint32_t numTryNotes) 357 : codeLength_(codeLength) { 358 // Variable-length data begins immediately after ImmutableScriptData itself. 359 Offset cursor = sizeof(ImmutableScriptData); 360 361 // The following arrays are byte-aligned with additional padding to ensure 362 // that together they maintain uint32_t-alignment. 363 { 364 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor)); 365 366 // Zero-initialize 'flags' 367 MOZ_ASSERT(isAlignedOffset<Flags>(cursor)); 368 new (offsetToPointer<void>(cursor)) Flags{}; 369 cursor += sizeof(Flags); 370 371 initElements<jsbytecode>(cursor, codeLength); 372 cursor += codeLength * sizeof(jsbytecode); 373 374 initElements<SrcNote>(cursor, noteLength); 375 cursor += noteLength * sizeof(SrcNote); 376 377 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor)); 378 } 379 380 // Initialization for remaining arrays. 381 initOptionalArrays(&cursor, numResumeOffsets, numScopeNotes, numTryNotes); 382 383 // Check that we correctly recompute the expected values. 384 MOZ_ASSERT(this->codeLength() == codeLength); 385 MOZ_ASSERT(this->noteLength() == noteLength); 386 387 // Sanity check 388 MOZ_ASSERT(endOffset() == cursor); 389 } 390 391 void js::FillImmutableFlagsFromCompileOptionsForTopLevel( 392 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) { 393 using ImmutableFlags = ImmutableScriptFlagsEnum; 394 395 js::FillImmutableFlagsFromCompileOptionsForFunction(options, flags); 396 397 flags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce); 398 flags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval); 399 } 400 401 void js::FillImmutableFlagsFromCompileOptionsForFunction( 402 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) { 403 using ImmutableFlags = ImmutableScriptFlagsEnum; 404 405 flags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode); 406 flags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode()); 407 flags.setFlag(ImmutableFlags::HasNonSyntacticScope, 408 options.nonSyntacticScope); 409 } 410 411 // Check if flags matches to compile options for flags set by 412 // FillImmutableFlagsFromCompileOptionsForTopLevel above. 413 bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions& options, 414 ImmutableScriptFlags flags) { 415 using ImmutableFlags = ImmutableScriptFlagsEnum; 416 417 bool selfHosted = !!(flags & uint32_t(ImmutableFlags::SelfHosted)); 418 bool forceStrict = !!(flags & uint32_t(ImmutableFlags::ForceStrict)); 419 bool hasNonSyntacticScope = 420 !!(flags & uint32_t(ImmutableFlags::HasNonSyntacticScope)); 421 bool noScriptRval = !!(flags & uint32_t(ImmutableFlags::NoScriptRval)); 422 bool treatAsRunOnce = !!(flags & uint32_t(ImmutableFlags::TreatAsRunOnce)); 423 424 return options.selfHostingMode == selfHosted && 425 options.noScriptRval == noScriptRval && 426 options.isRunOnce == treatAsRunOnce && 427 options.forceStrictMode() == forceStrict && 428 options.nonSyntacticScope == hasNonSyntacticScope; 429 } 430 431 JS_PUBLIC_API bool JS::CheckCompileOptionsMatch( 432 const ReadOnlyCompileOptions& options, JSScript* script) { 433 return js::CheckCompileOptionsMatch(options, script->immutableFlags()); 434 } 435 436 bool JSScript::initScriptCounts(JSContext* cx) { 437 MOZ_ASSERT(!hasScriptCounts()); 438 439 // Record all pc which are the first instruction of a basic block. 440 mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets; 441 442 js::BytecodeLocation main = mainLocation(); 443 AllBytecodesIterable iterable(this); 444 for (auto& loc : iterable) { 445 if (loc.isJumpTarget() || loc == main) { 446 if (!jumpTargets.append(loc.toRawBytecode())) { 447 ReportOutOfMemory(cx); 448 return false; 449 } 450 } 451 } 452 453 // Initialize all PCCounts counters to 0. 454 ScriptCounts::PCCountsVector base; 455 if (!base.reserve(jumpTargets.length())) { 456 ReportOutOfMemory(cx); 457 return false; 458 } 459 460 for (size_t i = 0; i < jumpTargets.length(); i++) { 461 base.infallibleEmplaceBack(pcToOffset(jumpTargets[i])); 462 } 463 464 // Create zone's scriptCountsMap if necessary. 465 if (!zone()->scriptCountsMap) { 466 auto map = cx->make_unique<ScriptCountsMap>(); 467 if (!map) { 468 return false; 469 } 470 471 zone()->scriptCountsMap = std::move(map); 472 } 473 474 // Allocate the ScriptCounts. 475 UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base)); 476 if (!sc) { 477 return false; 478 } 479 480 MOZ_ASSERT(this->hasBytecode()); 481 482 // Register the current ScriptCounts in the zone's map. 483 if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) { 484 ReportOutOfMemory(cx); 485 return false; 486 } 487 488 // safe to set this; we can't fail after this point. 489 setHasScriptCounts(); 490 491 // Enable interrupts in any interpreter frames running on this script. This 492 // is used to let the interpreter increment the PCCounts, if present. 493 for (ActivationIterator iter(cx); !iter.done(); ++iter) { 494 if (iter->isInterpreter()) { 495 iter->asInterpreter()->enableInterruptsIfRunning(this); 496 } 497 } 498 499 return true; 500 } 501 502 static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) { 503 MOZ_ASSERT(script->hasScriptCounts()); 504 ScriptCountsMap::Ptr p = script->zone()->scriptCountsMap->lookup(script); 505 MOZ_ASSERT(p); 506 return p; 507 } 508 509 ScriptCounts& JSScript::getScriptCounts() { 510 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); 511 return *p->value(); 512 } 513 514 js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) { 515 PCCounts searched = PCCounts(offset); 516 PCCounts* elem = 517 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched); 518 if (elem == pcCounts_.end() || elem->pcOffset() != offset) { 519 return nullptr; 520 } 521 return elem; 522 } 523 524 const js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) const { 525 PCCounts searched = PCCounts(offset); 526 const PCCounts* elem = 527 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched); 528 if (elem == pcCounts_.end() || elem->pcOffset() != offset) { 529 return nullptr; 530 } 531 return elem; 532 } 533 534 js::PCCounts* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset) { 535 PCCounts searched = PCCounts(offset); 536 PCCounts* elem = 537 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched); 538 if (elem == pcCounts_.end()) { 539 return &pcCounts_.back(); 540 } 541 if (elem->pcOffset() == offset) { 542 return elem; 543 } 544 if (elem != pcCounts_.begin()) { 545 return elem - 1; 546 } 547 return nullptr; 548 } 549 550 const js::PCCounts* ScriptCounts::maybeGetThrowCounts(size_t offset) const { 551 PCCounts searched = PCCounts(offset); 552 const PCCounts* elem = 553 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched); 554 if (elem == throwCounts_.end() || elem->pcOffset() != offset) { 555 return nullptr; 556 } 557 return elem; 558 } 559 560 const js::PCCounts* ScriptCounts::getImmediatePrecedingThrowCounts( 561 size_t offset) const { 562 PCCounts searched = PCCounts(offset); 563 const PCCounts* elem = 564 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched); 565 if (elem == throwCounts_.end()) { 566 if (throwCounts_.begin() == throwCounts_.end()) { 567 return nullptr; 568 } 569 return &throwCounts_.back(); 570 } 571 if (elem->pcOffset() == offset) { 572 return elem; 573 } 574 if (elem != throwCounts_.begin()) { 575 return elem - 1; 576 } 577 return nullptr; 578 } 579 580 js::PCCounts* ScriptCounts::getThrowCounts(size_t offset) { 581 PCCounts searched = PCCounts(offset); 582 PCCounts* elem = 583 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched); 584 if (elem == throwCounts_.end() || elem->pcOffset() != offset) { 585 elem = throwCounts_.insert(elem, searched); 586 } 587 return elem; 588 } 589 590 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { 591 size_t size = mallocSizeOf(this); 592 size += pcCounts_.sizeOfExcludingThis(mallocSizeOf); 593 size += throwCounts_.sizeOfExcludingThis(mallocSizeOf); 594 if (ionCounts_) { 595 size += ionCounts_->sizeOfIncludingThis(mallocSizeOf); 596 } 597 return size; 598 } 599 600 js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) { 601 MOZ_ASSERT(containsPC(pc)); 602 return getScriptCounts().maybeGetPCCounts(pcToOffset(pc)); 603 } 604 605 const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) { 606 MOZ_ASSERT(containsPC(pc)); 607 return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc)); 608 } 609 610 js::PCCounts* JSScript::getThrowCounts(jsbytecode* pc) { 611 MOZ_ASSERT(containsPC(pc)); 612 return getScriptCounts().getThrowCounts(pcToOffset(pc)); 613 } 614 615 uint64_t JSScript::getHitCount(jsbytecode* pc) { 616 MOZ_ASSERT(containsPC(pc)); 617 if (pc < main()) { 618 pc = main(); 619 } 620 621 ScriptCounts& sc = getScriptCounts(); 622 size_t targetOffset = pcToOffset(pc); 623 const js::PCCounts* baseCount = 624 sc.getImmediatePrecedingPCCounts(targetOffset); 625 if (!baseCount) { 626 return 0; 627 } 628 if (baseCount->pcOffset() == targetOffset) { 629 return baseCount->numExec(); 630 } 631 MOZ_ASSERT(baseCount->pcOffset() < targetOffset); 632 uint64_t count = baseCount->numExec(); 633 do { 634 const js::PCCounts* throwCount = 635 sc.getImmediatePrecedingThrowCounts(targetOffset); 636 if (!throwCount) { 637 return count; 638 } 639 if (throwCount->pcOffset() <= baseCount->pcOffset()) { 640 return count; 641 } 642 count -= throwCount->numExec(); 643 targetOffset = throwCount->pcOffset() - 1; 644 } while (true); 645 } 646 647 void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) { 648 ScriptCounts& sc = getScriptCounts(); 649 if (sc.ionCounts_) { 650 ionCounts->setPrevious(sc.ionCounts_); 651 } 652 sc.ionCounts_ = ionCounts; 653 } 654 655 jit::IonScriptCounts* JSScript::getIonCounts() { 656 return getScriptCounts().ionCounts_; 657 } 658 659 void JSScript::releaseScriptCounts(ScriptCounts* counts) { 660 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); 661 *counts = std::move(*p->value().get()); 662 zone()->scriptCountsMap->remove(p); 663 clearHasScriptCounts(); 664 } 665 666 void JSScript::destroyScriptCounts() { 667 if (hasScriptCounts()) { 668 ScriptCounts scriptCounts; 669 releaseScriptCounts(&scriptCounts); 670 } 671 } 672 673 void JSScript::resetScriptCounts() { 674 if (!hasScriptCounts()) { 675 return; 676 } 677 678 ScriptCounts& sc = getScriptCounts(); 679 680 for (PCCounts& elem : sc.pcCounts_) { 681 elem.numExec() = 0; 682 } 683 684 for (PCCounts& elem : sc.throwCounts_) { 685 elem.numExec() = 0; 686 } 687 } 688 689 void ScriptSourceObject::finalize(JS::GCContext* gcx, JSObject* obj) { 690 MOZ_ASSERT(gcx->onMainThread()); 691 ScriptSourceObject* sso = &obj->as<ScriptSourceObject>(); 692 sso->source()->Release(); 693 694 // Clear the private value, calling the release hook if necessary. 695 sso->setPrivate(gcx->runtime(), UndefinedValue()); 696 697 sso->clearStencils(); 698 } 699 700 static const JSClassOps ScriptSourceObjectClassOps = { 701 nullptr, // addProperty 702 nullptr, // delProperty 703 nullptr, // enumerate 704 nullptr, // newEnumerate 705 nullptr, // resolve 706 nullptr, // mayResolve 707 ScriptSourceObject::finalize, // finalize 708 nullptr, // call 709 nullptr, // construct 710 nullptr, // trace 711 }; 712 713 const JSClass ScriptSourceObject::class_ = { 714 "ScriptSource", 715 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_FOREGROUND_FINALIZE | 716 JSCLASS_SLOT0_IS_NSISUPPORTS, 717 &ScriptSourceObjectClassOps, 718 }; 719 720 ScriptSourceObject* ScriptSourceObject::create(JSContext* cx, 721 ScriptSource* source) { 722 ScriptSourceObject* obj = 723 NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr); 724 if (!obj) { 725 return nullptr; 726 } 727 728 // The matching decref is in ScriptSourceObject::finalize. 729 obj->initReservedSlot(SOURCE_SLOT, PrivateValue(do_AddRef(source).take())); 730 731 // The slots below should be populated by a call to initFromOptions. Poison 732 // them. 733 obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC)); 734 obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC)); 735 736 obj->initReservedSlot(STENCILS_SLOT, UndefinedValue()); 737 738 return obj; 739 } 740 741 [[nodiscard]] static bool MaybeValidateFilename( 742 JSContext* cx, Handle<ScriptSourceObject*> sso, 743 const JS::InstantiateOptions& options) { 744 if (!gFilenameValidationCallback) { 745 return true; 746 } 747 748 const char* filename = sso->source()->filename(); 749 if (!filename || options.skipFilenameValidation) { 750 return true; 751 } 752 753 if (gFilenameValidationCallback(cx, filename)) { 754 return true; 755 } 756 757 const char* utf8Filename; 758 if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename))) { 759 utf8Filename = filename; 760 } else { 761 utf8Filename = "(invalid UTF-8 filename)"; 762 } 763 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME, 764 utf8Filename); 765 return false; 766 } 767 768 /* static */ 769 bool ScriptSourceObject::initFromOptions( 770 JSContext* cx, Handle<ScriptSourceObject*> source, 771 const JS::InstantiateOptions& options) { 772 cx->releaseCheck(source); 773 MOZ_ASSERT( 774 source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC)); 775 MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT) 776 .isMagic(JS_GENERIC_MAGIC)); 777 778 if (!MaybeValidateFilename(cx, source, options)) { 779 return false; 780 } 781 782 if (options.deferDebugMetadata) { 783 return true; 784 } 785 786 // Initialize the element attribute slot and introduction script slot 787 // this marks the SSO as initialized for asserts. 788 789 RootedString elementAttributeName(cx); 790 if (!initElementProperties(cx, source, elementAttributeName)) { 791 return false; 792 } 793 794 source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, UndefinedValue()); 795 796 return true; 797 } 798 799 /* static */ 800 bool ScriptSourceObject::initElementProperties( 801 JSContext* cx, Handle<ScriptSourceObject*> source, 802 HandleString elementAttrName) { 803 RootedValue nameValue(cx); 804 if (elementAttrName) { 805 nameValue = StringValue(elementAttrName); 806 } 807 if (!cx->compartment()->wrap(cx, &nameValue)) { 808 return false; 809 } 810 811 source->setReservedSlot(ELEMENT_PROPERTY_SLOT, nameValue); 812 813 return true; 814 } 815 816 void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) { 817 // Update the private value, calling addRef/release hooks if necessary 818 // to allow the embedding to maintain a reference count for the 819 // private data. 820 JS::AutoSuppressGCAnalysis nogc; 821 Value prevValue = getReservedSlot(PRIVATE_SLOT); 822 rt->releaseScriptPrivate(prevValue); 823 setReservedSlot(PRIVATE_SLOT, value); 824 rt->addRefScriptPrivate(value); 825 } 826 827 void ScriptSourceObject::clearPrivate(JSRuntime* rt) { 828 // Clear the private value, calling release hook if necessary. 829 // |this| may be gray, be careful not to create edges to it. 830 JS::AutoSuppressGCAnalysis nogc; 831 Value prevValue = getReservedSlot(PRIVATE_SLOT); 832 rt->releaseScriptPrivate(prevValue); 833 getSlotRef(PRIVATE_SLOT).setUndefinedUnchecked(); 834 } 835 836 // Main-thread source loader that can retrieve sources via the source hook. 837 class ScriptSource::LoadSourceMatcher { 838 JSContext* const cx_; 839 ScriptSource* const ss_; 840 bool* const loaded_; 841 842 public: 843 explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded) 844 : cx_(cx), ss_(ss), loaded_(loaded) {} 845 846 template <typename Unit, SourceRetrievable CanRetrieve> 847 bool operator()(const Compressed<Unit, CanRetrieve>&) const { 848 *loaded_ = true; 849 return true; 850 } 851 852 template <typename Unit, SourceRetrievable CanRetrieve> 853 bool operator()(const Uncompressed<Unit, CanRetrieve>&) const { 854 *loaded_ = true; 855 return true; 856 } 857 858 bool operator()(const Missing&) const { 859 *loaded_ = false; 860 return true; 861 } 862 863 template <typename Unit> 864 bool operator()(const Retrievable<Unit>&) { 865 if (!cx_->runtime()->sourceHook.ref()) { 866 *loaded_ = false; 867 return true; 868 } 869 870 size_t length; 871 872 // The first argument is just for overloading -- its value doesn't matter. 873 if (!tryLoadAndSetSource(Unit('0'), &length)) { 874 return false; 875 } 876 877 return true; 878 } 879 880 private: 881 bool tryLoadAndSetSource(const Utf8Unit&, size_t* length) const { 882 char* utf8Source; 883 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr, 884 &utf8Source, length)) { 885 return false; 886 } 887 888 if (!utf8Source) { 889 *loaded_ = false; 890 return true; 891 } 892 893 if (!ss_->setRetrievedSource( 894 cx_, EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)), 895 *length)) { 896 return false; 897 } 898 899 *loaded_ = true; 900 return true; 901 } 902 903 bool tryLoadAndSetSource(const char16_t&, size_t* length) const { 904 char16_t* utf16Source; 905 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source, 906 nullptr, length)) { 907 return false; 908 } 909 910 if (!utf16Source) { 911 *loaded_ = false; 912 return true; 913 } 914 915 if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source), 916 *length)) { 917 return false; 918 } 919 920 *loaded_ = true; 921 return true; 922 } 923 }; 924 925 /* static */ 926 bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) { 927 return ss->data.match(LoadSourceMatcher(cx, ss, loaded)); 928 } 929 930 // Matcher to get source properties: whether source is present and whether 931 // it is retrievable. 932 class ScriptSource::SourcePropertiesGetter { 933 bool* const hasSourceText_; 934 bool* const retrievable_; 935 936 public: 937 explicit SourcePropertiesGetter(bool* hasSourceText, bool* retrievable) 938 : hasSourceText_(hasSourceText), retrievable_(retrievable) {} 939 940 template <typename Unit, SourceRetrievable CanRetrieve> 941 void operator()(const Compressed<Unit, CanRetrieve>&) const { 942 *hasSourceText_ = true; 943 *retrievable_ = false; 944 } 945 946 template <typename Unit, SourceRetrievable CanRetrieve> 947 void operator()(const Uncompressed<Unit, CanRetrieve>&) const { 948 *hasSourceText_ = true; 949 *retrievable_ = false; 950 } 951 952 template <typename Unit> 953 void operator()(const Retrievable<Unit>&) { 954 // Retrievable requires the main thread. Do not attempt to retrieve it. 955 *hasSourceText_ = false; 956 *retrievable_ = true; 957 } 958 959 void operator()(const Missing&) const { 960 *hasSourceText_ = false; 961 *retrievable_ = false; 962 } 963 }; 964 965 void ScriptSource::getSourceProperties(ScriptSource* ss, bool* hasSourceText, 966 bool* retrievable) { 967 ss->data.match(SourcePropertiesGetter(hasSourceText, retrievable)); 968 } 969 970 /* static */ 971 JSLinearString* JSScript::sourceData(JSContext* cx, HandleScript script) { 972 MOZ_ASSERT(script->scriptSource()->hasSourceText()); 973 return script->scriptSource()->substring(cx, script->sourceStart(), 974 script->sourceEnd()); 975 } 976 977 bool BaseScript::appendSourceDataForToString(JSContext* cx, 978 StringBuilder& buf) { 979 MOZ_ASSERT(scriptSource()->hasSourceText()); 980 return scriptSource()->appendSubstring(cx, buf, toStringStart(), 981 toStringEnd()); 982 } 983 984 void UncompressedSourceCache::holdEntry(AutoHoldEntry& holder, 985 const ScriptSourceChunk& ssc) { 986 MOZ_ASSERT(!holder_); 987 holder.holdEntry(this, ssc); 988 holder_ = &holder; 989 } 990 991 void UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder) { 992 MOZ_ASSERT(holder_ == &holder); 993 holder_ = nullptr; 994 } 995 996 template <typename Unit> 997 const Unit* UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc, 998 AutoHoldEntry& holder) { 999 MOZ_ASSERT(!holder_); 1000 MOZ_ASSERT(ssc.ss->isCompressed<Unit>()); 1001 1002 if (!map_) { 1003 return nullptr; 1004 } 1005 1006 if (Map::Ptr p = map_->lookup(ssc)) { 1007 holdEntry(holder, ssc); 1008 return static_cast<const Unit*>(p->value().get()); 1009 } 1010 1011 return nullptr; 1012 } 1013 1014 bool UncompressedSourceCache::put(const ScriptSourceChunk& ssc, SourceData data, 1015 AutoHoldEntry& holder) { 1016 MOZ_ASSERT(!holder_); 1017 1018 if (!map_) { 1019 map_ = MakeUnique<Map>(); 1020 if (!map_) { 1021 return false; 1022 } 1023 } 1024 1025 if (!map_->put(ssc, std::move(data))) { 1026 return false; 1027 } 1028 1029 holdEntry(holder, ssc); 1030 return true; 1031 } 1032 1033 void UncompressedSourceCache::purge() { 1034 if (!map_) { 1035 return; 1036 } 1037 1038 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { 1039 if (holder_ && r.front().key() == holder_->sourceChunk()) { 1040 holder_->deferDelete(std::move(r.front().value())); 1041 holder_ = nullptr; 1042 } 1043 } 1044 1045 map_ = nullptr; 1046 } 1047 1048 size_t UncompressedSourceCache::sizeOfExcludingThis( 1049 mozilla::MallocSizeOf mallocSizeOf) { 1050 size_t n = 0; 1051 if (map_ && !map_->empty()) { 1052 n += map_->shallowSizeOfIncludingThis(mallocSizeOf); 1053 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { 1054 n += mallocSizeOf(r.front().value().get()); 1055 } 1056 } 1057 return n; 1058 } 1059 1060 template <typename Unit> 1061 const Unit* ScriptSource::chunkUnits( 1062 JSContext* maybeCx, UncompressedSourceCache::AutoHoldEntry& holder, 1063 size_t chunk) { 1064 const CompressedData<Unit>& c = *compressedData<Unit>(); 1065 1066 // Try cache lookup only if we have a JSContext 1067 if (maybeCx) { 1068 ScriptSourceChunk ssc(this, chunk); 1069 if (const Unit* decompressed = 1070 maybeCx->caches().uncompressedSourceCache.lookup<Unit>(ssc, 1071 holder)) { 1072 return decompressed; 1073 } 1074 } 1075 1076 size_t totalLengthInBytes = length() * sizeof(Unit); 1077 size_t chunkBytes = Compressor::chunkSize(totalLengthInBytes, chunk); 1078 1079 MOZ_ASSERT((chunkBytes % sizeof(Unit)) == 0); 1080 const size_t chunkLength = chunkBytes / sizeof(Unit); 1081 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(chunkLength)); 1082 if (!decompressed) { 1083 if (maybeCx) { 1084 JS_ReportOutOfMemory(maybeCx); 1085 } 1086 return nullptr; 1087 } 1088 1089 // Compression treats input and output memory as plain ol' bytes. These 1090 // reinterpret_cast<>s accord exactly with that. 1091 if (!DecompressStringChunk( 1092 reinterpret_cast<const unsigned char*>(c.raw.chars()), chunk, 1093 reinterpret_cast<unsigned char*>(decompressed.get()), chunkBytes)) { 1094 if (maybeCx) { 1095 JS_ReportOutOfMemory(maybeCx); 1096 } 1097 return nullptr; 1098 } 1099 1100 const Unit* ret = decompressed.get(); 1101 1102 // Try to cache the result only if we have a JSContext 1103 if (maybeCx) { 1104 ScriptSourceChunk ssc(this, chunk); 1105 if (!maybeCx->caches().uncompressedSourceCache.put( 1106 ssc, ToSourceData(std::move(decompressed)), holder)) { 1107 JS_ReportOutOfMemory(maybeCx); 1108 return nullptr; 1109 } 1110 } else { 1111 // Without caching, transfer ownership to holder for memory management 1112 holder.holdUnits(std::move(decompressed)); 1113 } 1114 1115 return ret; 1116 } 1117 1118 template <typename Unit> 1119 void ScriptSource::convertToCompressedSource(SharedImmutableString compressed, 1120 size_t uncompressedLength) { 1121 MOZ_ASSERT(isUncompressed<Unit>()); 1122 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength); 1123 1124 if (data.is<Uncompressed<Unit, SourceRetrievable::Yes>>()) { 1125 data = SourceType(Compressed<Unit, SourceRetrievable::Yes>( 1126 std::move(compressed), uncompressedLength)); 1127 } else { 1128 data = SourceType(Compressed<Unit, SourceRetrievable::No>( 1129 std::move(compressed), uncompressedLength)); 1130 } 1131 } 1132 1133 template <typename Unit> 1134 void ScriptSource::performDelayedConvertToCompressedSource( 1135 ExclusiveData<ReaderInstances>::Guard& g) { 1136 // There might not be a conversion to compressed source happening at all. 1137 if (g->pendingCompressed.empty()) { 1138 return; 1139 } 1140 1141 CompressedData<Unit>& pending = 1142 g->pendingCompressed.ref<CompressedData<Unit>>(); 1143 1144 convertToCompressedSource<Unit>(std::move(pending.raw), 1145 pending.uncompressedLength); 1146 1147 g->pendingCompressed.destroy(); 1148 } 1149 1150 void ScriptSource::PinnedUnitsBase::addReader() { 1151 auto guard = source_->readers_.lock(); 1152 guard->count++; 1153 } 1154 1155 template <typename Unit> 1156 void ScriptSource::PinnedUnitsBase::removeReader() { 1157 // If the off-thread compression task couldn't perform 1158 // convertToCompressedSource, the conversion is pending on 1159 // the pendingCompressed field. 1160 // 1161 // If there's no other reader at this point, perform the pending conversion 1162 // here. 1163 // 1164 // See also ScriptSource::triggerConvertToCompressedSource. 1165 auto guard = source_->readers_.lock(); 1166 MOZ_ASSERT(guard->count > 0); 1167 if (--guard->count == 0) { 1168 source_->performDelayedConvertToCompressedSource<Unit>(guard); 1169 } 1170 } 1171 1172 template <typename Unit> 1173 ScriptSource::PinnedUnits<Unit>::~PinnedUnits() { 1174 if (units_) { 1175 removeReader<Unit>(); 1176 } 1177 } 1178 1179 template <typename Unit> 1180 ScriptSource::PinnedUnitsIfUncompressed<Unit>::~PinnedUnitsIfUncompressed() { 1181 if (units_) { 1182 removeReader<Unit>(); 1183 } 1184 } 1185 1186 template <typename Unit> 1187 const Unit* ScriptSource::units(JSContext* maybeCx, 1188 UncompressedSourceCache::AutoHoldEntry& holder, 1189 size_t begin, size_t len) { 1190 MOZ_ASSERT(begin <= length()); 1191 MOZ_ASSERT(begin + len <= length()); 1192 1193 if (isUncompressed<Unit>()) { 1194 const Unit* units = uncompressedData<Unit>()->units(); 1195 if (!units) { 1196 return nullptr; 1197 } 1198 return units + begin; 1199 } 1200 1201 if (data.is<Missing>()) { 1202 MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source"); 1203 } 1204 1205 if (data.is<Retrievable<Unit>>()) { 1206 MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source"); 1207 } 1208 1209 MOZ_ASSERT(isCompressed<Unit>()); 1210 1211 // Determine first/last chunks, the offset (in bytes) into the first chunk 1212 // of the requested units, and the number of bytes in the last chunk. 1213 // 1214 // Note that first and last chunk sizes are miscomputed and *must not be 1215 // used* when the first chunk is the last chunk. 1216 size_t firstChunk, firstChunkOffset, firstChunkSize; 1217 size_t lastChunk, lastChunkSize; 1218 Compressor::rangeToChunkAndOffset( 1219 begin * sizeof(Unit), (begin + len) * sizeof(Unit), &firstChunk, 1220 &firstChunkOffset, &firstChunkSize, &lastChunk, &lastChunkSize); 1221 MOZ_ASSERT(firstChunk <= lastChunk); 1222 MOZ_ASSERT(firstChunkOffset % sizeof(Unit) == 0); 1223 MOZ_ASSERT(firstChunkSize % sizeof(Unit) == 0); 1224 1225 size_t firstUnit = firstChunkOffset / sizeof(Unit); 1226 1227 // Directly return units within a single chunk. UncompressedSourceCache 1228 // and |holder| will hold the units alive past function return. 1229 if (firstChunk == lastChunk) { 1230 const Unit* units = chunkUnits<Unit>(maybeCx, holder, firstChunk); 1231 if (!units) { 1232 return nullptr; 1233 } 1234 1235 return units + firstUnit; 1236 } 1237 1238 // Otherwise the units span multiple chunks. Copy successive chunks' 1239 // decompressed units into freshly-allocated memory to return. 1240 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(len)); 1241 if (!decompressed) { 1242 if (maybeCx) { 1243 JS_ReportOutOfMemory(maybeCx); 1244 } 1245 return nullptr; 1246 } 1247 1248 Unit* cursor; 1249 1250 { 1251 // |AutoHoldEntry| is single-shot, and a holder successfully filled in 1252 // by |chunkUnits| must be destroyed before another can be used. Thus 1253 // we can't use |holder| with |chunkUnits| when |chunkUnits| is used 1254 // with multiple chunks, and we must use and destroy distinct, fresh 1255 // holders for each chunk. 1256 UncompressedSourceCache::AutoHoldEntry firstHolder; 1257 const Unit* units = chunkUnits<Unit>(maybeCx, firstHolder, firstChunk); 1258 if (!units) { 1259 return nullptr; 1260 } 1261 1262 cursor = std::copy_n(units + firstUnit, firstChunkSize / sizeof(Unit), 1263 decompressed.get()); 1264 } 1265 1266 for (size_t i = firstChunk + 1; i < lastChunk; i++) { 1267 UncompressedSourceCache::AutoHoldEntry chunkHolder; 1268 const Unit* units = chunkUnits<Unit>(maybeCx, chunkHolder, i); 1269 if (!units) { 1270 return nullptr; 1271 } 1272 1273 cursor = std::copy_n(units, Compressor::CHUNK_SIZE / sizeof(Unit), cursor); 1274 } 1275 1276 { 1277 UncompressedSourceCache::AutoHoldEntry lastHolder; 1278 const Unit* units = chunkUnits<Unit>(maybeCx, lastHolder, lastChunk); 1279 if (!units) { 1280 return nullptr; 1281 } 1282 1283 cursor = std::copy_n(units, lastChunkSize / sizeof(Unit), cursor); 1284 } 1285 1286 MOZ_ASSERT(PointerRangeSize(decompressed.get(), cursor) == len); 1287 1288 // Transfer ownership to |holder|. 1289 const Unit* ret = decompressed.get(); 1290 holder.holdUnits(std::move(decompressed)); 1291 return ret; 1292 } 1293 1294 template <typename Unit> 1295 const Unit* ScriptSource::uncompressedUnits(size_t begin, size_t len) { 1296 MOZ_ASSERT(begin <= length()); 1297 MOZ_ASSERT(begin + len <= length()); 1298 1299 if (!isUncompressed<Unit>()) { 1300 return nullptr; 1301 } 1302 1303 const Unit* units = uncompressedData<Unit>()->units(); 1304 if (!units) { 1305 return nullptr; 1306 } 1307 return units + begin; 1308 } 1309 1310 template <typename Unit> 1311 ScriptSource::PinnedUnits<Unit>::PinnedUnits( 1312 JSContext* maybeCx, ScriptSource* source, 1313 UncompressedSourceCache::AutoHoldEntry& holder, size_t begin, size_t len) 1314 : PinnedUnitsBase(source) { 1315 MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type"); 1316 1317 addReader(); 1318 1319 units_ = source->units<Unit>(maybeCx, holder, begin, len); 1320 if (!units_) { 1321 removeReader<Unit>(); 1322 } 1323 } 1324 1325 template class ScriptSource::PinnedUnits<Utf8Unit>; 1326 template class ScriptSource::PinnedUnits<char16_t>; 1327 1328 template <typename Unit> 1329 ScriptSource::PinnedUnitsIfUncompressed<Unit>::PinnedUnitsIfUncompressed( 1330 ScriptSource* source, size_t begin, size_t len) 1331 : PinnedUnitsBase(source) { 1332 MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type"); 1333 1334 addReader(); 1335 1336 units_ = source->uncompressedUnits<Unit>(begin, len); 1337 if (!units_) { 1338 removeReader<Unit>(); 1339 } 1340 } 1341 1342 template class ScriptSource::PinnedUnitsIfUncompressed<Utf8Unit>; 1343 template class ScriptSource::PinnedUnitsIfUncompressed<char16_t>; 1344 1345 JSLinearString* ScriptSource::substring(JSContext* cx, size_t start, 1346 size_t stop) { 1347 MOZ_ASSERT(start <= stop); 1348 1349 size_t len = stop - start; 1350 if (!len) { 1351 return cx->emptyString(); 1352 } 1353 UncompressedSourceCache::AutoHoldEntry holder; 1354 1355 // UTF-8 source text. 1356 if (hasSourceType<Utf8Unit>()) { 1357 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len); 1358 if (!units.asChars()) { 1359 return nullptr; 1360 } 1361 1362 const char* str = units.asChars(); 1363 return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len)); 1364 } 1365 1366 // UTF-16 source text. 1367 PinnedUnits<char16_t> units(cx, this, holder, start, len); 1368 if (!units.asChars()) { 1369 return nullptr; 1370 } 1371 1372 return NewStringCopyN<CanGC>(cx, units.asChars(), len); 1373 } 1374 1375 JSLinearString* ScriptSource::substringDontDeflate(JSContext* cx, size_t start, 1376 size_t stop) { 1377 MOZ_ASSERT(start <= stop); 1378 1379 size_t len = stop - start; 1380 if (!len) { 1381 return cx->emptyString(); 1382 } 1383 UncompressedSourceCache::AutoHoldEntry holder; 1384 1385 // UTF-8 source text. 1386 if (hasSourceType<Utf8Unit>()) { 1387 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len); 1388 if (!units.asChars()) { 1389 return nullptr; 1390 } 1391 1392 const char* str = units.asChars(); 1393 1394 // There doesn't appear to be a non-deflating UTF-8 string creation 1395 // function -- but then again, it's not entirely clear how current 1396 // callers benefit from non-deflation. 1397 return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len)); 1398 } 1399 1400 // UTF-16 source text. 1401 PinnedUnits<char16_t> units(cx, this, holder, start, len); 1402 if (!units.asChars()) { 1403 return nullptr; 1404 } 1405 1406 return NewStringCopyNDontDeflate<CanGC>(cx, units.asChars(), len); 1407 } 1408 1409 SubstringCharsResult ScriptSource::substringChars(size_t start, size_t stop) { 1410 MOZ_ASSERT(start <= stop); 1411 1412 size_t len = stop - start; 1413 MOZ_ASSERT(len > 0, "Callers must handle empty sources before calling this"); 1414 1415 UncompressedSourceCache::AutoHoldEntry holder; 1416 1417 // UTF-8 source text. 1418 if (hasSourceType<Utf8Unit>()) { 1419 // Pass nullptr JSContext - this method is designed to be called 1420 // off-main-thread where JSContext is not available. Decompression still 1421 // works but without caching. 1422 PinnedUnits<Utf8Unit> units(nullptr, this, holder, start, len); 1423 if (!units.asChars()) { 1424 // Allocation failure or decompression error. 1425 return SubstringCharsResult(JS::UniqueChars(nullptr)); 1426 } 1427 1428 const char* str = units.asChars(); 1429 // For UTF-8 source, create a copy of the char data. 1430 // Note: We allocate exactly `len` bytes without a null terminator. 1431 // Callers must track the length separately. 1432 char* copy = static_cast<char*>(js_malloc(len * sizeof(char))); 1433 if (!copy) { 1434 // Allocation failure. 1435 return SubstringCharsResult(JS::UniqueChars(nullptr)); 1436 } 1437 1438 mozilla::PodCopy(copy, str, len); 1439 return SubstringCharsResult(JS::UniqueChars(copy)); 1440 } 1441 1442 // UTF-16 source text. 1443 // Pass nullptr JSContext - this method is designed to be called 1444 // off-main-thread where JSContext is not available. Decompression still works 1445 // but without caching. 1446 PinnedUnits<char16_t> units(nullptr, this, holder, start, len); 1447 if (!units.asChars()) { 1448 // Allocation failure or decompression error. 1449 return SubstringCharsResult(JS::UniqueTwoByteChars(nullptr)); 1450 } 1451 1452 // For UTF-16 source, create a copy of the char16_t data. 1453 // Note: We allocate exactly `len` char16_t elements without a null 1454 // terminator. Callers must track the length separately. 1455 char16_t* copy = static_cast<char16_t*>(js_malloc(len * sizeof(char16_t))); 1456 if (!copy) { 1457 // Allocation failure. 1458 return SubstringCharsResult(JS::UniqueTwoByteChars(nullptr)); 1459 } 1460 1461 mozilla::PodCopy(copy, units.asChars(), len); 1462 return SubstringCharsResult(JS::UniqueTwoByteChars(copy)); 1463 } 1464 1465 bool ScriptSource::appendSubstring(JSContext* cx, StringBuilder& buf, 1466 size_t start, size_t stop) { 1467 MOZ_ASSERT(start <= stop); 1468 1469 size_t len = stop - start; 1470 UncompressedSourceCache::AutoHoldEntry holder; 1471 1472 if (hasSourceType<Utf8Unit>()) { 1473 PinnedUnits<Utf8Unit> pinned(cx, this, holder, start, len); 1474 if (!pinned.get()) { 1475 return false; 1476 } 1477 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) { 1478 return false; 1479 } 1480 1481 const Utf8Unit* units = pinned.get(); 1482 return buf.append(units, len); 1483 } else { 1484 PinnedUnits<char16_t> pinned(cx, this, holder, start, len); 1485 if (!pinned.get()) { 1486 return false; 1487 } 1488 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) { 1489 return false; 1490 } 1491 1492 const char16_t* units = pinned.get(); 1493 return buf.append(units, len); 1494 } 1495 } 1496 1497 JSLinearString* ScriptSource::functionBodyString(JSContext* cx) { 1498 MOZ_ASSERT(isFunctionBody()); 1499 1500 size_t start = parameterListEnd_ + FunctionConstructorMedialSigils.length(); 1501 size_t stop = length() - FunctionConstructorFinalBrace.length(); 1502 return substring(cx, start, stop); 1503 } 1504 1505 SubstringCharsResult ScriptSource::functionBodyStringChars(size_t* outLength) { 1506 MOZ_ASSERT(isFunctionBody()); 1507 MOZ_ASSERT(outLength); 1508 1509 size_t start = parameterListEnd_ + FunctionConstructorMedialSigils.length(); 1510 size_t stop = length() - FunctionConstructorFinalBrace.length(); 1511 *outLength = stop - start; 1512 1513 // Handle empty function body. Return nullptr to indicate empty result. 1514 // This is distinct from substringChars which asserts non-empty length. 1515 if (*outLength == 0) { 1516 return SubstringCharsResult(JS::UniqueChars(nullptr)); 1517 } 1518 1519 return substringChars(start, stop); 1520 } 1521 1522 template <typename ContextT, typename Unit> 1523 [[nodiscard]] bool ScriptSource::setUncompressedSourceHelper( 1524 ContextT* cx, EntryUnits<Unit>&& source, size_t length, 1525 SourceRetrievable retrievable) { 1526 auto& cache = SharedImmutableStringsCache::getSingleton(); 1527 1528 auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source)); 1529 auto deduped = cache.getOrCreate(std::move(uniqueChars), length); 1530 if (!deduped) { 1531 ReportOutOfMemory(cx); 1532 return false; 1533 } 1534 1535 if (retrievable == SourceRetrievable::Yes) { 1536 data = SourceType( 1537 Uncompressed<Unit, SourceRetrievable::Yes>(std::move(deduped))); 1538 } else { 1539 data = SourceType( 1540 Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped))); 1541 } 1542 return true; 1543 } 1544 1545 template <typename Unit> 1546 [[nodiscard]] bool ScriptSource::setRetrievedSource(JSContext* cx, 1547 EntryUnits<Unit>&& source, 1548 size_t length) { 1549 MOZ_ASSERT(data.is<Retrievable<Unit>>(), 1550 "retrieved source can only overwrite the corresponding " 1551 "retrievable source"); 1552 return setUncompressedSourceHelper(cx, std::move(source), length, 1553 SourceRetrievable::Yes); 1554 } 1555 1556 bool js::IsOffThreadSourceCompressionEnabled() { 1557 // If we don't have concurrent execution compression will contend with 1558 // main-thread execution, in which case we disable. Similarly we don't want to 1559 // block the thread pool if it is too small. 1560 return GetHelperThreadCPUCount() > 1 && GetHelperThreadCount() > 1 && 1561 CanUseExtraThreads(); 1562 } 1563 1564 bool ScriptSource::tryCompressOffThread(JSContext* cx) { 1565 // Beware: |js::SynchronouslyCompressSource| assumes that this function is 1566 // only called once, just after a script has been compiled, and it's never 1567 // called at some random time after that. If multiple calls of this can ever 1568 // occur, that function may require changes. 1569 1570 // The SourceCompressionTask needs to record the major GC number for 1571 // scheduling. 1572 MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); 1573 1574 // If source compression was already attempted, do not queue a new task. 1575 if (hadCompressionTask_) { 1576 return true; 1577 } 1578 1579 if (!hasUncompressedSource()) { 1580 // This excludes compressed, missing, and retrievable source. 1581 return true; 1582 } 1583 1584 // There are several cases where source compression is not a good idea: 1585 // - If the script is tiny, then compression will save little or no space. 1586 // - If there is only one core, then compression will contend with JS 1587 // execution (which hurts benchmarketing). 1588 // 1589 // Otherwise, enqueue a compression task to be processed when a major 1590 // GC is requested. 1591 1592 if (length() < ScriptSource::MinimumCompressibleLength || 1593 !IsOffThreadSourceCompressionEnabled()) { 1594 return true; 1595 } 1596 1597 if (!cx->runtime()->addPendingCompressionEntry(this)) { 1598 ReportOutOfMemory(cx); 1599 return false; 1600 } 1601 return true; 1602 } 1603 1604 template <typename Unit> 1605 void ScriptSource::triggerConvertToCompressedSource( 1606 SharedImmutableString compressed, size_t uncompressedLength) { 1607 MOZ_ASSERT(isUncompressed<Unit>(), 1608 "should only be triggering compressed source installation to " 1609 "overwrite identically-encoded uncompressed source"); 1610 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength); 1611 1612 // If units aren't pinned -- and they probably won't be, we'd have to have a 1613 // GC in the small window of time where a |PinnedUnits| was live -- then we 1614 // can immediately convert. 1615 { 1616 auto guard = readers_.lock(); 1617 if (MOZ_LIKELY(!guard->count)) { 1618 convertToCompressedSource<Unit>(std::move(compressed), 1619 uncompressedLength); 1620 return; 1621 } 1622 1623 // Otherwise, set aside the compressed-data info. The conversion is 1624 // performed when the last |PinnedUnits| dies. 1625 MOZ_ASSERT(guard->pendingCompressed.empty(), 1626 "shouldn't be multiple conversions happening"); 1627 guard->pendingCompressed.construct<CompressedData<Unit>>( 1628 std::move(compressed), uncompressedLength); 1629 } 1630 } 1631 1632 template <typename Unit> 1633 [[nodiscard]] bool ScriptSource::initializeWithUnretrievableCompressedSource( 1634 FrontendContext* fc, UniqueChars&& compressed, size_t rawLength, 1635 size_t sourceLength) { 1636 MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing"); 1637 MOZ_ASSERT(compressed != nullptr); 1638 1639 auto& cache = SharedImmutableStringsCache::getSingleton(); 1640 auto deduped = cache.getOrCreate(std::move(compressed), rawLength); 1641 if (!deduped) { 1642 ReportOutOfMemory(fc); 1643 return false; 1644 } 1645 1646 #ifdef DEBUG 1647 { 1648 auto guard = readers_.lock(); 1649 MOZ_ASSERT( 1650 guard->count == 0, 1651 "shouldn't be initializing a ScriptSource while its characters " 1652 "are pinned -- that only makes sense with a ScriptSource actively " 1653 "being inspected"); 1654 } 1655 #endif 1656 1657 data = SourceType(Compressed<Unit, SourceRetrievable::No>(std::move(deduped), 1658 sourceLength)); 1659 1660 return true; 1661 } 1662 1663 template bool ScriptSource::initializeWithUnretrievableCompressedSource< 1664 Utf8Unit>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength, 1665 size_t sourceLength); 1666 template bool ScriptSource::initializeWithUnretrievableCompressedSource< 1667 char16_t>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength, 1668 size_t sourceLength); 1669 1670 template <typename Unit> 1671 bool ScriptSource::assignSource(FrontendContext* fc, 1672 const ReadOnlyCompileOptions& options, 1673 SourceText<Unit>& srcBuf) { 1674 MOZ_ASSERT(data.is<Missing>(), 1675 "source assignment should only occur on fresh ScriptSources"); 1676 1677 mutedErrors_ = options.mutedErrors(); 1678 delazificationMode_ = options.eagerDelazificationStrategy(); 1679 1680 if (options.discardSource) { 1681 return true; 1682 } 1683 1684 if (options.sourceIsLazy) { 1685 data = SourceType(Retrievable<Unit>()); 1686 return true; 1687 } 1688 1689 auto& cache = SharedImmutableStringsCache::getSingleton(); 1690 auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() { 1691 using CharT = typename SourceTypeTraits<Unit>::CharT; 1692 return srcBuf.ownsUnits() 1693 ? UniquePtr<CharT[], JS::FreePolicy>(srcBuf.takeChars()) 1694 : DuplicateString(srcBuf.get(), srcBuf.length()); 1695 }); 1696 if (!deduped) { 1697 ReportOutOfMemory(fc); 1698 return false; 1699 } 1700 1701 data = 1702 SourceType(Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped))); 1703 return true; 1704 } 1705 1706 template bool ScriptSource::assignSource(FrontendContext* fc, 1707 const ReadOnlyCompileOptions& options, 1708 SourceText<char16_t>& srcBuf); 1709 template bool ScriptSource::assignSource(FrontendContext* fc, 1710 const ReadOnlyCompileOptions& options, 1711 SourceText<Utf8Unit>& srcBuf); 1712 1713 [[nodiscard]] static bool reallocUniquePtr(UniqueChars& unique, size_t size) { 1714 auto newPtr = static_cast<char*>(js_realloc(unique.get(), size)); 1715 if (!newPtr) { 1716 return false; 1717 } 1718 1719 // Since the realloc succeeded, unique is now holding a freed pointer. 1720 (void)unique.release(); 1721 unique.reset(newPtr); 1722 return true; 1723 } 1724 1725 template <typename Unit> 1726 void SourceCompressionTaskEntry::workEncodingSpecific(Compressor& comp) { 1727 MOZ_ASSERT(source_->isUncompressed<Unit>()); 1728 1729 // Try to keep the maximum memory usage down by only allocating half the 1730 // size of the string, first. 1731 size_t inputBytes = source_->length() * sizeof(Unit); 1732 size_t firstSize = inputBytes / 2; 1733 UniqueChars compressed(js_pod_malloc<char>(firstSize)); 1734 if (!compressed) { 1735 return; 1736 } 1737 1738 const Unit* chars = source_->uncompressedData<Unit>()->units(); 1739 if (!comp.setInput(reinterpret_cast<const unsigned char*>(chars), 1740 inputBytes)) { 1741 return; 1742 } 1743 1744 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize); 1745 bool cont = true; 1746 bool reallocated = false; 1747 while (cont) { 1748 if (shouldCancel()) { 1749 return; 1750 } 1751 1752 switch (comp.compressMore()) { 1753 case Compressor::CONTINUE: 1754 break; 1755 case Compressor::MOREOUTPUT: { 1756 if (reallocated) { 1757 // The compressed string is longer than the original string. 1758 return; 1759 } 1760 1761 // The compressed output is greater than half the size of the 1762 // original string. Reallocate to the full size. 1763 if (!reallocUniquePtr(compressed, inputBytes)) { 1764 return; 1765 } 1766 1767 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), 1768 inputBytes); 1769 reallocated = true; 1770 break; 1771 } 1772 case Compressor::DONE: 1773 cont = false; 1774 break; 1775 case Compressor::OOM: 1776 return; 1777 } 1778 } 1779 1780 size_t totalBytes = comp.totalBytesNeeded(); 1781 1782 // Shrink the buffer to the size of the compressed data. 1783 if (!reallocUniquePtr(compressed, totalBytes)) { 1784 return; 1785 } 1786 1787 comp.finish(compressed.get(), totalBytes); 1788 1789 if (shouldCancel()) { 1790 return; 1791 } 1792 1793 auto& strings = SharedImmutableStringsCache::getSingleton(); 1794 resultString_ = strings.getOrCreate(std::move(compressed), totalBytes); 1795 } 1796 1797 PendingSourceCompressionEntry::PendingSourceCompressionEntry( 1798 JSRuntime* rt, ScriptSource* source) 1799 : majorGCNumber_(rt->gc.majorGCCount()), source_(source) { 1800 source->noteSourceCompressionTask(); 1801 } 1802 1803 struct SourceCompressionTaskEntry::PerformTaskWork { 1804 SourceCompressionTaskEntry* const task_; 1805 Compressor& comp_; 1806 1807 PerformTaskWork(SourceCompressionTaskEntry* task, Compressor& comp) 1808 : task_(task), comp_(comp) {} 1809 1810 template <typename Unit, SourceRetrievable CanRetrieve> 1811 void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) { 1812 task_->workEncodingSpecific<Unit>(comp_); 1813 } 1814 1815 template <typename T> 1816 void operator()(const T&) { 1817 MOZ_CRASH( 1818 "why are we compressing missing, missing-but-retrievable, " 1819 "or already-compressed source?"); 1820 } 1821 }; 1822 1823 void ScriptSource::performTaskWork(SourceCompressionTaskEntry* task, 1824 Compressor& comp) { 1825 MOZ_ASSERT(hasUncompressedSource()); 1826 data.match(SourceCompressionTaskEntry::PerformTaskWork(task, comp)); 1827 } 1828 1829 void SourceCompressionTaskEntry::runTask(Compressor& comp) { 1830 if (shouldCancel()) { 1831 return; 1832 } 1833 1834 MOZ_ASSERT(source_->hasUncompressedSource()); 1835 1836 source_->performTaskWork(this, comp); 1837 } 1838 1839 void SourceCompressionTask::runTask() { 1840 MOZ_ASSERT(!entries_.empty()); 1841 // Note: here and in workEncodingSpecific we abort compression work on OOM 1842 // since source compression is optional. 1843 Compressor comp; 1844 if (!comp.init()) { 1845 return; 1846 } 1847 for (auto& entry : entries_) { 1848 entry.runTask(comp); 1849 } 1850 } 1851 1852 void SourceCompressionTask::runHelperThreadTask( 1853 AutoLockHelperThreadState& locked) { 1854 { 1855 AutoUnlockHelperThreadState unlock(locked); 1856 this->runTask(); 1857 } 1858 1859 { 1860 AutoEnterOOMUnsafeRegion oomUnsafe; 1861 if (!HelperThreadState().compressionFinishedList(locked).append(this)) { 1862 oomUnsafe.crash("SourceCompressionTask::runHelperThreadTask"); 1863 } 1864 } 1865 } 1866 1867 void ScriptSource::triggerConvertToCompressedSourceFromTask( 1868 SharedImmutableString compressed) { 1869 data.match(TriggerConvertToCompressedSourceFromTask(this, compressed)); 1870 } 1871 1872 void SourceCompressionTaskEntry::complete() { 1873 if (!shouldCancel() && resultString_) { 1874 source_->triggerConvertToCompressedSourceFromTask(std::move(resultString_)); 1875 } 1876 } 1877 1878 void SourceCompressionTask::complete() { 1879 MOZ_ASSERT(!entries_.empty()); 1880 for (auto& entry : entries_) { 1881 entry.complete(); 1882 } 1883 } 1884 1885 bool js::SynchronouslyCompressSource(JSContext* cx, 1886 JS::Handle<BaseScript*> script) { 1887 // Finish all pending source compressions, including the single compression 1888 // task that may have been created (by |ScriptSource::tryCompressOffThread|) 1889 // just after the script was compiled. Because we have flushed this queue, 1890 // no code below needs to synchronize with an off-thread parse task that 1891 // assumes the immutability of a |ScriptSource|'s data. 1892 // 1893 // This *may* end up compressing |script|'s source. If it does -- we test 1894 // this below -- that takes care of things. But if it doesn't, we will 1895 // synchronously compress ourselves (and as noted above, this won't race 1896 // anything). 1897 RunPendingSourceCompressions(cx->runtime()); 1898 1899 ScriptSource* ss = script->scriptSource(); 1900 #ifdef DEBUG 1901 { 1902 auto guard = ss->readers_.lock(); 1903 MOZ_ASSERT(guard->count == 0, 1904 "can't synchronously compress while source units are in use"); 1905 } 1906 #endif 1907 1908 // In principle a previously-triggered compression on a helper thread could 1909 // have already completed. If that happens, there's nothing more to do. 1910 if (ss->hasCompressedSource()) { 1911 return true; 1912 } 1913 1914 MOZ_ASSERT(ss->hasUncompressedSource(), 1915 "shouldn't be compressing uncompressible source"); 1916 1917 // Use an explicit scope to delineate the lifetime of |task|, for simplicity. 1918 { 1919 #ifdef DEBUG 1920 uint32_t sourceRefs = ss->refs; 1921 #endif 1922 MOZ_ASSERT(sourceRefs > 0, "at least |script| here should have a ref"); 1923 1924 // |SourceCompressionTaskEntry::shouldCancel| can periodically result in 1925 // source compression being canceled if we're not careful. Guarantee that 1926 // two refs to |ss| are always live in this function (at least one 1927 // preexisting and one held by the task) so that compression is never 1928 // canceled. 1929 auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), ss); 1930 if (!task) { 1931 ReportOutOfMemory(cx); 1932 return false; 1933 } 1934 1935 MOZ_ASSERT(ss->refs > sourceRefs, "must have at least two refs now"); 1936 1937 // Attempt to compress. This may not succeed if OOM happens, but (because 1938 // it ordinarily happens on a helper thread) no error will ever be set here. 1939 MOZ_ASSERT(!cx->isExceptionPending()); 1940 task->runTask(); 1941 MOZ_ASSERT(!cx->isExceptionPending()); 1942 1943 // Convert |ss| from uncompressed to compressed data. 1944 task->complete(); 1945 1946 MOZ_ASSERT(!cx->isExceptionPending()); 1947 } 1948 1949 // The only way source won't be compressed here is if OOM happened. 1950 return ss->hasCompressedSource(); 1951 } 1952 1953 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, 1954 JS::ScriptSourceInfo* info) const { 1955 info->misc += mallocSizeOf(this); 1956 info->numScripts++; 1957 } 1958 1959 frontend::InitialStencilAndDelazifications* 1960 ScriptSourceObject::maybeGetStencils() { 1961 Value stencilsVal = getReservedSlot(STENCILS_SLOT); 1962 if (stencilsVal.isUndefined()) { 1963 return nullptr; 1964 } 1965 1966 return reinterpret_cast<frontend::InitialStencilAndDelazifications*>( 1967 uintptr_t(stencilsVal.toPrivate()) & ~STENCILS_MASK); 1968 } 1969 1970 void ScriptSourceObject::clearStencils() { 1971 auto* stencils = maybeGetStencils(); 1972 if (!stencils) { 1973 return; 1974 } 1975 1976 stencils->Release(); 1977 setReservedSlot(STENCILS_SLOT, UndefinedValue()); 1978 } 1979 1980 template <uintptr_t flag> 1981 void ScriptSourceObject::setStencilsFlag() { 1982 JS::Value stencilsVal = getReservedSlot(STENCILS_SLOT); 1983 MOZ_ASSERT(!stencilsVal.isUndefined(), 1984 "This should be called after setStencils"); 1985 uintptr_t raw = uintptr_t(stencilsVal.toPrivate()); 1986 MOZ_ASSERT((raw & flag) == 0); 1987 raw |= flag; 1988 setReservedSlot(STENCILS_SLOT, PrivateValue(raw)); 1989 } 1990 1991 template <uintptr_t flag> 1992 void ScriptSourceObject::unsetStencilsFlag() { 1993 JS::Value stencilsVal = getReservedSlot(STENCILS_SLOT); 1994 MOZ_ASSERT(!stencilsVal.isUndefined(), 1995 "This should be called after setStencils"); 1996 uintptr_t raw = uintptr_t(stencilsVal.toPrivate()); 1997 raw &= ~flag; 1998 if (raw & STENCILS_MASK) { 1999 setReservedSlot(STENCILS_SLOT, PrivateValue(raw)); 2000 } else { 2001 clearStencils(); 2002 } 2003 } 2004 2005 template <uintptr_t flag> 2006 bool ScriptSourceObject::isStencilsFlagSet() const { 2007 JS::Value stencilsVal = getReservedSlot(STENCILS_SLOT); 2008 if (stencilsVal.isUndefined()) { 2009 return false; 2010 } 2011 uintptr_t raw = uintptr_t(stencilsVal.toPrivate()); 2012 return bool(raw & flag); 2013 } 2014 2015 void ScriptSourceObject::setStencils( 2016 already_AddRefed<frontend::InitialStencilAndDelazifications> stencils) { 2017 MOZ_ASSERT(!maybeGetStencils()); 2018 setReservedSlot(STENCILS_SLOT, PrivateValue(stencils.take())); 2019 } 2020 2021 void ScriptSourceObject::setCollectingDelazifications() { 2022 setStencilsFlag<STENCILS_COLLECTING_DELAZIFICATIONS_FLAG>(); 2023 } 2024 2025 void ScriptSourceObject::unsetCollectingDelazifications() { 2026 unsetStencilsFlag<STENCILS_COLLECTING_DELAZIFICATIONS_FLAG>(); 2027 } 2028 2029 bool ScriptSourceObject::isCollectingDelazifications() const { 2030 return isStencilsFlagSet<STENCILS_COLLECTING_DELAZIFICATIONS_FLAG>(); 2031 } 2032 2033 void ScriptSourceObject::setSharingDelazifications() { 2034 setStencilsFlag<STENCILS_SHARING_DELAZIFICATIONS_FLAG>(); 2035 } 2036 2037 bool ScriptSourceObject::isSharingDelazifications() const { 2038 return isStencilsFlagSet<STENCILS_SHARING_DELAZIFICATIONS_FLAG>(); 2039 } 2040 2041 template <typename Unit> 2042 [[nodiscard]] bool ScriptSource::initializeUnretrievableUncompressedSource( 2043 FrontendContext* fc, EntryUnits<Unit>&& source, size_t length) { 2044 MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource"); 2045 return setUncompressedSourceHelper(fc, std::move(source), length, 2046 SourceRetrievable::No); 2047 } 2048 2049 template bool ScriptSource::initializeUnretrievableUncompressedSource( 2050 FrontendContext* fc, EntryUnits<Utf8Unit>&& source, size_t length); 2051 template bool ScriptSource::initializeUnretrievableUncompressedSource( 2052 FrontendContext* fc, EntryUnits<char16_t>&& source, size_t length); 2053 2054 // Format and return a cx->pod_malloc'ed URL for a generated script like: 2055 // {filename} line {lineno} > {introducer} 2056 // For example: 2057 // foo.js line 7 > eval 2058 // indicating code compiled by the call to 'eval' on line 7 of foo.js. 2059 UniqueChars js::FormatIntroducedFilename(const char* filename, uint32_t lineno, 2060 const char* introducer) { 2061 // Compute the length of the string in advance, so we can allocate a 2062 // buffer of the right size on the first shot. 2063 // 2064 // (JS_smprintf would be perfect, as that allocates the result 2065 // dynamically as it formats the string, but it won't allocate from cx, 2066 // and wants us to use a special free function.) 2067 char linenoBuf[15]; 2068 size_t filenameLen = strlen(filename); 2069 size_t linenoLen = SprintfLiteral(linenoBuf, "%u", lineno); 2070 size_t introducerLen = strlen(introducer); 2071 size_t len = filenameLen + 6 /* == strlen(" line ") */ + linenoLen + 2072 3 /* == strlen(" > ") */ + introducerLen + 1 /* \0 */; 2073 UniqueChars formatted(js_pod_malloc<char>(len)); 2074 if (!formatted) { 2075 return nullptr; 2076 } 2077 2078 mozilla::DebugOnly<size_t> checkLen = snprintf( 2079 formatted.get(), len, "%s line %s > %s", filename, linenoBuf, introducer); 2080 MOZ_ASSERT(checkLen == len - 1); 2081 2082 return formatted; 2083 } 2084 2085 bool ScriptSource::initFromOptions(FrontendContext* fc, 2086 const ReadOnlyCompileOptions& options) { 2087 MOZ_ASSERT(!filename_); 2088 MOZ_ASSERT(!introducerFilename_); 2089 2090 mutedErrors_ = options.mutedErrors(); 2091 delazificationMode_ = options.eagerDelazificationStrategy(); 2092 2093 startLine_ = options.lineno; 2094 startColumn_ = JS::LimitedColumnNumberOneOrigin::fromUnlimited( 2095 JS::ColumnNumberOneOrigin(options.column)); 2096 introductionType_ = options.introductionType; 2097 setIntroductionOffset(options.introductionOffset); 2098 // The parameterListEnd_ is initialized later by setParameterListEnd, before 2099 // we expose any scripts that use this ScriptSource to the debugger. 2100 2101 if (options.hasIntroductionInfo) { 2102 MOZ_ASSERT(options.introductionType != nullptr); 2103 const char* filename = 2104 options.filename() ? options.filename().c_str() : "<unknown>"; 2105 UniqueChars formatted = FormatIntroducedFilename( 2106 filename, options.introductionLineno, options.introductionType); 2107 if (!formatted) { 2108 ReportOutOfMemory(fc); 2109 return false; 2110 } 2111 if (!setFilename(fc, std::move(formatted))) { 2112 return false; 2113 } 2114 } else if (options.filename()) { 2115 if (!setFilename(fc, options.filename().c_str())) { 2116 return false; 2117 } 2118 } 2119 2120 if (options.introducerFilename()) { 2121 if (!setIntroducerFilename(fc, options.introducerFilename().c_str())) { 2122 return false; 2123 } 2124 } 2125 2126 return true; 2127 } 2128 2129 // Use the SharedImmutableString map to deduplicate input string. The input 2130 // string must be null-terminated. 2131 template <typename SharedT, typename CharT> 2132 static SharedT GetOrCreateStringZ(FrontendContext* fc, 2133 UniquePtr<CharT[], JS::FreePolicy>&& str) { 2134 size_t lengthWithNull = std::char_traits<CharT>::length(str.get()) + 1; 2135 auto res = SharedImmutableStringsCache::getSingleton().getOrCreate( 2136 std::move(str), lengthWithNull); 2137 if (!res) { 2138 ReportOutOfMemory(fc); 2139 } 2140 return res; 2141 } 2142 2143 SharedImmutableString ScriptSource::getOrCreateStringZ(FrontendContext* fc, 2144 UniqueChars&& str) { 2145 return GetOrCreateStringZ<SharedImmutableString>(fc, std::move(str)); 2146 } 2147 2148 SharedImmutableTwoByteString ScriptSource::getOrCreateStringZ( 2149 FrontendContext* fc, UniqueTwoByteChars&& str) { 2150 return GetOrCreateStringZ<SharedImmutableTwoByteString>(fc, std::move(str)); 2151 } 2152 2153 bool ScriptSource::setFilename(FrontendContext* fc, const char* filename) { 2154 UniqueChars owned = DuplicateString(fc, filename); 2155 if (!owned) { 2156 return false; 2157 } 2158 return setFilename(fc, std::move(owned)); 2159 } 2160 2161 bool ScriptSource::setFilename(FrontendContext* fc, UniqueChars&& filename) { 2162 MOZ_ASSERT(!filename_); 2163 filename_ = getOrCreateStringZ(fc, std::move(filename)); 2164 if (filename_) { 2165 filenameHash_ = 2166 mozilla::HashStringKnownLength(filename_.chars(), filename_.length()); 2167 return true; 2168 } 2169 return false; 2170 } 2171 2172 bool ScriptSource::setIntroducerFilename(FrontendContext* fc, 2173 const char* filename) { 2174 UniqueChars owned = DuplicateString(fc, filename); 2175 if (!owned) { 2176 return false; 2177 } 2178 return setIntroducerFilename(fc, std::move(owned)); 2179 } 2180 2181 bool ScriptSource::setIntroducerFilename(FrontendContext* fc, 2182 UniqueChars&& filename) { 2183 MOZ_ASSERT(!introducerFilename_); 2184 introducerFilename_ = getOrCreateStringZ(fc, std::move(filename)); 2185 return bool(introducerFilename_); 2186 } 2187 2188 bool ScriptSource::setDisplayURL(FrontendContext* fc, const char16_t* url) { 2189 UniqueTwoByteChars owned = DuplicateString(fc, url); 2190 if (!owned) { 2191 return false; 2192 } 2193 return setDisplayURL(fc, std::move(owned)); 2194 } 2195 2196 bool ScriptSource::setDisplayURL(FrontendContext* fc, 2197 UniqueTwoByteChars&& url) { 2198 MOZ_ASSERT(!hasDisplayURL()); 2199 MOZ_ASSERT(url); 2200 if (url[0] == '\0') { 2201 return true; 2202 } 2203 2204 displayURL_ = getOrCreateStringZ(fc, std::move(url)); 2205 return bool(displayURL_); 2206 } 2207 2208 bool ScriptSource::setSourceMapURL(FrontendContext* fc, const char16_t* url) { 2209 UniqueTwoByteChars owned = DuplicateString(fc, url); 2210 if (!owned) { 2211 return false; 2212 } 2213 return setSourceMapURL(fc, std::move(owned)); 2214 } 2215 2216 bool ScriptSource::setSourceMapURL(FrontendContext* fc, 2217 UniqueTwoByteChars&& url) { 2218 MOZ_ASSERT(url); 2219 if (url[0] == '\0') { 2220 return true; 2221 } 2222 2223 sourceMapURL_ = getOrCreateStringZ(fc, std::move(url)); 2224 return bool(sourceMapURL_); 2225 } 2226 2227 /* static */ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> 2228 ScriptSource::idCount_; 2229 2230 /* 2231 * [SMDOC] JSScript data layout (immutable) 2232 * 2233 * Script data that shareable across processes. There are no pointers (GC or 2234 * otherwise) and the data is relocatable. 2235 * 2236 * Array elements Pointed to by Length 2237 * -------------- ------------- ------ 2238 * jsbytecode code() codeLength() 2239 * jsscrnote notes() noteLength() 2240 * uint32_t resumeOffsets() 2241 * ScopeNote scopeNotes() 2242 * TryNote tryNotes() 2243 */ 2244 2245 /* static */ CheckedInt<uint32_t> ImmutableScriptData::sizeFor( 2246 uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets, 2247 uint32_t numScopeNotes, uint32_t numTryNotes) { 2248 // Take a count of which optional arrays will be used and need offset info. 2249 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) + 2250 unsigned(numScopeNotes > 0) + 2251 unsigned(numTryNotes > 0); 2252 2253 // Compute size including trailing arrays. 2254 CheckedInt<uint32_t> size = sizeof(ImmutableScriptData); 2255 size += sizeof(Flags); 2256 size += CheckedInt<uint32_t>(codeLength) * sizeof(jsbytecode); 2257 size += CheckedInt<uint32_t>(noteLength) * sizeof(SrcNote); 2258 size += CheckedInt<uint32_t>(numOptionalArrays) * sizeof(Offset); 2259 size += CheckedInt<uint32_t>(numResumeOffsets) * sizeof(uint32_t); 2260 size += CheckedInt<uint32_t>(numScopeNotes) * sizeof(ScopeNote); 2261 size += CheckedInt<uint32_t>(numTryNotes) * sizeof(TryNote); 2262 2263 return size; 2264 } 2265 2266 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_( 2267 FrontendContext* fc, uint32_t codeLength, uint32_t noteLength, 2268 uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) { 2269 auto size = sizeFor(codeLength, noteLength, numResumeOffsets, numScopeNotes, 2270 numTryNotes); 2271 if (!size.isValid()) { 2272 ReportAllocationOverflow(fc); 2273 return nullptr; 2274 } 2275 2276 // Allocate contiguous raw buffer. 2277 void* raw = fc->getAllocator()->pod_malloc<uint8_t>(size.value()); 2278 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0); 2279 if (!raw) { 2280 return nullptr; 2281 } 2282 2283 // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but 2284 // GCPtrs are put into a safe state. 2285 UniquePtr<ImmutableScriptData> result(new (raw) ImmutableScriptData( 2286 codeLength, noteLength, numResumeOffsets, numScopeNotes, numTryNotes)); 2287 if (!result) { 2288 return nullptr; 2289 } 2290 2291 // Sanity check 2292 MOZ_ASSERT(result->endOffset() == size.value()); 2293 2294 return result; 2295 } 2296 2297 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_( 2298 FrontendContext* fc, uint32_t totalSize) { 2299 void* raw = fc->getAllocator()->pod_malloc<uint8_t>(totalSize); 2300 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0); 2301 UniquePtr<ImmutableScriptData> result( 2302 reinterpret_cast<ImmutableScriptData*>(raw)); 2303 return result; 2304 } 2305 2306 bool js::ImmutableScriptData::validateLayout(uint32_t expectedSize) { 2307 constexpr size_t HeaderSize = sizeof(js::ImmutableScriptData); 2308 constexpr size_t OptionalOffsetsMaxSize = 3 * sizeof(Offset); 2309 2310 // Check that the optional-offsets array lies within the allocation before we 2311 // try to read from it while computing sizes. Remember that the array *ends* 2312 // at the `optArrayOffset_`. 2313 static_assert(OptionalOffsetsMaxSize <= HeaderSize); 2314 if (HeaderSize > optArrayOffset_) { 2315 return false; 2316 } 2317 if (optArrayOffset_ > expectedSize) { 2318 return false; 2319 } 2320 2321 // Round-trip the size computation using `CheckedInt` to detect overflow. This 2322 // should indirectly validate most alignment, size, and ordering requirments. 2323 auto size = sizeFor(codeLength(), noteLength(), resumeOffsets().size(), 2324 scopeNotes().size(), tryNotes().size()); 2325 return size.isValid() && (size.value() == expectedSize); 2326 } 2327 2328 /* static */ 2329 SharedImmutableScriptData* SharedImmutableScriptData::create( 2330 FrontendContext* fc) { 2331 return fc->getAllocator()->new_<SharedImmutableScriptData>(); 2332 } 2333 2334 /* static */ 2335 SharedImmutableScriptData* SharedImmutableScriptData::createWith( 2336 FrontendContext* fc, js::UniquePtr<ImmutableScriptData>&& isd) { 2337 MOZ_ASSERT(isd.get()); 2338 SharedImmutableScriptData* sisd = create(fc); 2339 if (!sisd) { 2340 return nullptr; 2341 } 2342 2343 sisd->setOwn(std::move(isd)); 2344 return sisd; 2345 } 2346 2347 void JSScript::relazify(JSRuntime* rt) { 2348 js::Scope* scope = enclosingScope(); 2349 UniquePtr<PrivateScriptData> scriptData; 2350 2351 // Any JIT compiles should have been released, so we already point to the 2352 // interpreter trampoline which supports lazy scripts. 2353 MOZ_ASSERT_IF(jit::HasJitBackend(), isUsingInterpreterTrampoline(rt)); 2354 2355 realm()->removeFromCompileQueue(this); 2356 2357 // Without bytecode, the script counts are invalid so destroy them if they 2358 // still exist. 2359 destroyScriptCounts(); 2360 2361 // Release the bytecode and gcthings list. 2362 // NOTE: We clear the PrivateScriptData to nullptr. This is fine because we 2363 // only allowed relazification (via AllowRelazify) if the original lazy 2364 // script we compiled from had a nullptr PrivateScriptData. 2365 swapData(scriptData); 2366 freeSharedData(); 2367 2368 // We should not still be in any side-tables for the debugger or 2369 // code-coverage. The finalizer will not be able to clean them up once 2370 // bytecode is released. We check in JSFunction::maybeRelazify() for these 2371 // conditions before requesting relazification. 2372 MOZ_ASSERT(!coverage::IsLCovEnabled()); 2373 MOZ_ASSERT(!hasScriptCounts()); 2374 MOZ_ASSERT(!hasDebugScript()); 2375 2376 // Rollback warmUpData_ to have enclosingScope. 2377 MOZ_ASSERT(warmUpData_.isWarmUpCount(), 2378 "JitScript should already be released"); 2379 warmUpData_.resetWarmUpCount(0); 2380 warmUpData_.initEnclosingScope(scope); 2381 2382 MOZ_ASSERT(isReadyForDelazification()); 2383 } 2384 2385 // Takes ownership of the passed SharedImmutableScriptData and either adds it 2386 // into the runtime's SharedImmutableScriptDataTable, or frees it if a matching 2387 // entry already exists and replaces the passed RefPtr with the existing entry. 2388 /* static */ 2389 bool SharedImmutableScriptData::shareScriptData( 2390 FrontendContext* fc, RefPtr<SharedImmutableScriptData>& sisd) { 2391 MOZ_ASSERT(sisd); 2392 MOZ_ASSERT(sisd->refCount() == 1); 2393 2394 SharedImmutableScriptData* data = sisd.get(); 2395 2396 SharedImmutableScriptData::Hasher::Lookup lookup(data); 2397 2398 Maybe<AutoLockGlobalScriptData> lock; 2399 js::SharedImmutableScriptDataTable& table = 2400 fc->scriptDataTableHolder()->getMaybeLocked(lock); 2401 2402 SharedImmutableScriptDataTable::AddPtr p = table.lookupForAdd(lookup); 2403 if (p) { 2404 MOZ_ASSERT(data != *p); 2405 sisd = *p; 2406 } else { 2407 if (!table.add(p, data)) { 2408 ReportOutOfMemory(fc); 2409 return false; 2410 } 2411 2412 // Being in the table counts as a reference on the script data. 2413 data->AddRef(); 2414 } 2415 2416 // Refs: sisd argument, SharedImmutableScriptDataTable 2417 MOZ_ASSERT(sisd->refCount() >= 2); 2418 2419 return true; 2420 } 2421 2422 static void SweepScriptDataTable(SharedImmutableScriptDataTable& table) { 2423 // Entries are removed from the table when their reference count is one, 2424 // i.e. when the only reference to them is from the table entry. 2425 2426 for (SharedImmutableScriptDataTable::Enum e(table); !e.empty(); 2427 e.popFront()) { 2428 SharedImmutableScriptData* sharedData = e.front(); 2429 if (sharedData->refCount() == 1) { 2430 sharedData->Release(); 2431 e.removeFront(); 2432 } 2433 } 2434 } 2435 2436 void js::SweepScriptData(JSRuntime* rt) { 2437 SweepScriptDataTable(rt->scriptDataTableHolder().getWithoutLock()); 2438 2439 AutoLockGlobalScriptData lock; 2440 SweepScriptDataTable(js::globalSharedScriptDataTableHolder.get(lock)); 2441 } 2442 2443 inline size_t PrivateScriptData::allocationSize() const { return endOffset(); } 2444 2445 // Initialize and placement-new the trailing arrays. 2446 PrivateScriptData::PrivateScriptData(uint32_t ngcthings) 2447 : ngcthings(ngcthings) { 2448 // Variable-length data begins immediately after PrivateScriptData itself. 2449 // NOTE: Alignment is computed using cursor/offset so the alignment of 2450 // PrivateScriptData must be stricter than any trailing array type. 2451 Offset cursor = sizeof(PrivateScriptData); 2452 2453 // Layout and initialize the gcthings array. 2454 { 2455 initElements<JS::GCCellPtr>(cursor, ngcthings); 2456 cursor += ngcthings * sizeof(JS::GCCellPtr); 2457 } 2458 2459 // Sanity check. 2460 MOZ_ASSERT(endOffset() == cursor); 2461 } 2462 2463 /* static */ 2464 PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings) { 2465 // Compute size including trailing arrays. 2466 CheckedInt<Offset> size = sizeof(PrivateScriptData); 2467 size += CheckedInt<Offset>(ngcthings) * sizeof(JS::GCCellPtr); 2468 if (!size.isValid()) { 2469 ReportAllocationOverflow(cx); 2470 return nullptr; 2471 } 2472 2473 // Allocate contiguous raw buffer for the trailing arrays. 2474 void* raw = cx->pod_malloc<uint8_t>(size.value()); 2475 MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0); 2476 if (!raw) { 2477 return nullptr; 2478 } 2479 2480 // Constuct the PrivateScriptData. Trailing arrays are uninitialized but 2481 // GCPtrs are put into a safe state. 2482 PrivateScriptData* result = new (raw) PrivateScriptData(ngcthings); 2483 if (!result) { 2484 return nullptr; 2485 } 2486 2487 // Sanity check. 2488 MOZ_ASSERT(result->endOffset() == size.value()); 2489 2490 return result; 2491 } 2492 2493 /* static */ 2494 bool PrivateScriptData::InitFromStencil( 2495 JSContext* cx, js::HandleScript script, 2496 const js::frontend::CompilationAtomCache& atomCache, 2497 const js::frontend::CompilationStencil& stencil, 2498 js::frontend::CompilationGCOutput& gcOutput, 2499 const js::frontend::ScriptIndex scriptIndex) { 2500 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex]; 2501 uint32_t ngcthings = scriptStencil.gcThingsLength; 2502 2503 MOZ_ASSERT(ngcthings <= INDEX_LIMIT); 2504 2505 // Create and initialize PrivateScriptData 2506 if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) { 2507 return false; 2508 } 2509 2510 js::PrivateScriptData* data = script->data_; 2511 if (ngcthings) { 2512 if (!EmitScriptThingsVector(cx, atomCache, stencil, gcOutput, 2513 scriptStencil.gcthings(stencil), 2514 data->gcthings())) { 2515 return false; 2516 } 2517 } 2518 2519 return true; 2520 } 2521 2522 void PrivateScriptData::trace(JSTracer* trc) { 2523 for (JS::GCCellPtr& elem : gcthings()) { 2524 TraceManuallyBarrieredGCCellPtr(trc, &elem, "script-gcthing"); 2525 } 2526 } 2527 2528 /*static*/ 2529 JSScript* JSScript::Create(JSContext* cx, JS::Handle<JSFunction*> function, 2530 js::Handle<ScriptSourceObject*> sourceObject, 2531 const SourceExtent& extent, 2532 js::ImmutableScriptFlags flags) { 2533 return static_cast<JSScript*>( 2534 BaseScript::New(cx, function, sourceObject, extent, flags)); 2535 } 2536 2537 #ifdef MOZ_VTUNE 2538 uint32_t JSScript::vtuneMethodID() { 2539 if (!zone()->scriptVTuneIdMap) { 2540 auto map = MakeUnique<ScriptVTuneIdMap>(); 2541 if (!map) { 2542 MOZ_CRASH("Failed to allocate ScriptVTuneIdMap"); 2543 } 2544 2545 zone()->scriptVTuneIdMap = std::move(map); 2546 } 2547 2548 ScriptVTuneIdMap::AddPtr p = zone()->scriptVTuneIdMap->lookupForAdd(this); 2549 if (p) { 2550 return p->value(); 2551 } 2552 2553 MOZ_ASSERT(this->hasBytecode()); 2554 2555 uint32_t id = vtune::GenerateUniqueMethodID(); 2556 if (!zone()->scriptVTuneIdMap->add(p, this, id)) { 2557 MOZ_CRASH("Failed to add vtune method id"); 2558 } 2559 2560 return id; 2561 } 2562 #endif 2563 2564 /* static */ 2565 bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script, 2566 uint32_t ngcthings) { 2567 cx->check(script); 2568 2569 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings)); 2570 if (!data) { 2571 return false; 2572 } 2573 2574 script->swapData(data); 2575 MOZ_ASSERT(!data); 2576 2577 return true; 2578 } 2579 2580 /* static */ 2581 bool JSScript::fullyInitFromStencil( 2582 JSContext* cx, const js::frontend::CompilationAtomCache& atomCache, 2583 const js::frontend::CompilationStencil& stencil, 2584 frontend::CompilationGCOutput& gcOutput, HandleScript script, 2585 const js::frontend::ScriptIndex scriptIndex) { 2586 MutableScriptFlags lazyMutableFlags; 2587 Rooted<Scope*> lazyEnclosingScope(cx); 2588 2589 // A holder for the lazy PrivateScriptData that we must keep around in case 2590 // this process fails and we must return the script to its original state. 2591 // 2592 // This is initialized by BaseScript::swapData() which will run pre-barriers 2593 // for us. On successful conversion to non-lazy script, the old script data 2594 // here will be released by the UniquePtr. 2595 Rooted<UniquePtr<PrivateScriptData>> lazyData(cx); 2596 2597 // Whether we are a newborn script or an existing lazy script, we should 2598 // already be pointing to the interpreter trampoline. 2599 MOZ_ASSERT_IF(jit::HasJitBackend(), 2600 script->isUsingInterpreterTrampoline(cx->runtime())); 2601 2602 // If we are using an existing lazy script, record enough info to be able to 2603 // rollback on failure. 2604 if (script->isReadyForDelazification()) { 2605 lazyMutableFlags = script->mutableFlags_; 2606 lazyEnclosingScope = script->releaseEnclosingScope(); 2607 script->swapData(lazyData.get()); 2608 MOZ_ASSERT(script->sharedData_ == nullptr); 2609 } 2610 2611 // Restore the script to lazy state on failure. If this was a fresh script, we 2612 // just need to clear bytecode to mark script as incomplete. 2613 auto rollbackGuard = mozilla::MakeScopeExit([&] { 2614 if (lazyEnclosingScope) { 2615 script->mutableFlags_ = lazyMutableFlags; 2616 script->warmUpData_.initEnclosingScope(lazyEnclosingScope); 2617 script->swapData(lazyData.get()); 2618 script->sharedData_ = nullptr; 2619 2620 MOZ_ASSERT(script->isReadyForDelazification()); 2621 } else { 2622 script->sharedData_ = nullptr; 2623 } 2624 }); 2625 2626 // The counts of indexed things must be checked during code generation. 2627 MOZ_ASSERT(stencil.scriptData[scriptIndex].gcThingsLength <= INDEX_LIMIT); 2628 2629 // Note: These flags should already be correct when the BaseScript was 2630 // allocated. 2631 MOZ_ASSERT_IF(stencil.isInitialStencil(), 2632 script->immutableFlags() == 2633 stencil.scriptExtra[scriptIndex].immutableFlags); 2634 2635 // Create and initialize PrivateScriptData 2636 if (!PrivateScriptData::InitFromStencil(cx, script, atomCache, stencil, 2637 gcOutput, scriptIndex)) { 2638 return false; 2639 } 2640 2641 // Member-initializer data is computed in initial parse only. If we are 2642 // delazifying, make sure to copy it off the `lazyData` before we throw it 2643 // away. 2644 if (script->useMemberInitializers()) { 2645 if (stencil.isInitialStencil()) { 2646 MemberInitializers initializers( 2647 stencil.scriptExtra[scriptIndex].memberInitializers()); 2648 script->setMemberInitializers(initializers); 2649 } else { 2650 script->setMemberInitializers(lazyData.get()->getMemberInitializers()); 2651 } 2652 } 2653 auto* scriptData = stencil.sharedData.get(scriptIndex); 2654 script->initSharedData(scriptData); 2655 2656 // NOTE: JSScript is now constructed and should be linked in. 2657 rollbackGuard.release(); 2658 2659 // Link Scope -> JSFunction -> BaseScript. 2660 if (script->isFunction()) { 2661 JSFunction* fun = gcOutput.getFunction(scriptIndex); 2662 script->bodyScope()->as<FunctionScope>().initCanonicalFunction(fun); 2663 if (fun->isIncomplete()) { 2664 fun->initScript(script); 2665 } else if (fun->hasSelfHostedLazyScript()) { 2666 fun->clearSelfHostedLazyScript(); 2667 fun->initScript(script); 2668 } else { 2669 // We are delazifying in-place. 2670 MOZ_ASSERT(fun->baseScript() == script); 2671 } 2672 } 2673 2674 // NOTE: The caller is responsible for linking ModuleObjects if this is a 2675 // module script. 2676 2677 #ifdef JS_STRUCTURED_SPEW 2678 // We want this to happen after line number initialization to allow filtering 2679 // to work. 2680 script->setSpewEnabled(cx->spewer().enabled(script)); 2681 #endif 2682 2683 #ifdef DEBUG 2684 script->assertValidJumpTargets(); 2685 #endif 2686 2687 if (coverage::IsLCovEnabled()) { 2688 if (!coverage::InitScriptCoverage(cx, script)) { 2689 return false; 2690 } 2691 } 2692 2693 return true; 2694 } 2695 2696 JSScript* JSScript::fromStencil(JSContext* cx, 2697 frontend::CompilationAtomCache& atomCache, 2698 const frontend::CompilationStencil& stencil, 2699 frontend::CompilationGCOutput& gcOutput, 2700 frontend::ScriptIndex scriptIndex) { 2701 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex]; 2702 js::frontend::ScriptStencilExtra& scriptExtra = 2703 stencil.scriptExtra[scriptIndex]; 2704 MOZ_ASSERT(scriptStencil.hasSharedData(), 2705 "Need generated bytecode to use JSScript::fromStencil"); 2706 2707 Rooted<JSFunction*> function(cx); 2708 if (scriptStencil.isFunction()) { 2709 function = gcOutput.getFunction(scriptIndex); 2710 } 2711 2712 Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject); 2713 RootedScript script(cx, Create(cx, function, sourceObject, scriptExtra.extent, 2714 scriptExtra.immutableFlags)); 2715 if (!script) { 2716 return nullptr; 2717 } 2718 2719 if (!fullyInitFromStencil(cx, atomCache, stencil, gcOutput, script, 2720 scriptIndex)) { 2721 return nullptr; 2722 } 2723 2724 return script; 2725 } 2726 2727 #ifdef DEBUG 2728 void JSScript::assertValidJumpTargets() const { 2729 BytecodeLocation mainLoc = mainLocation(); 2730 BytecodeLocation endLoc = endLocation(); 2731 AllBytecodesIterable iter(this); 2732 for (BytecodeLocation loc : iter) { 2733 // Check jump instructions' target. 2734 if (loc.isJump()) { 2735 BytecodeLocation target = loc.getJumpTarget(); 2736 MOZ_ASSERT(mainLoc <= target && target < endLoc); 2737 MOZ_ASSERT(target.isJumpTarget()); 2738 2739 // All backward jumps must be to a JSOp::LoopHead op. This is an invariant 2740 // we want to maintain to simplify JIT compilation and bytecode analysis. 2741 MOZ_ASSERT_IF(target < loc, target.is(JSOp::LoopHead)); 2742 MOZ_ASSERT_IF(target < loc, IsBackedgePC(loc.toRawBytecode())); 2743 2744 // All forward jumps must be to a JSOp::JumpTarget op. 2745 MOZ_ASSERT_IF(target > loc, target.is(JSOp::JumpTarget)); 2746 2747 // Jumps must not cross scope boundaries. 2748 MOZ_ASSERT(loc.innermostScope(this) == target.innermostScope(this)); 2749 2750 // Check fallthrough of conditional jump instructions. 2751 if (loc.fallsThrough()) { 2752 BytecodeLocation fallthrough = loc.next(); 2753 MOZ_ASSERT(mainLoc <= fallthrough && fallthrough < endLoc); 2754 MOZ_ASSERT(fallthrough.isJumpTarget()); 2755 } 2756 } 2757 2758 // Check table switch case labels. 2759 if (loc.is(JSOp::TableSwitch)) { 2760 BytecodeLocation target = loc.getTableSwitchDefaultTarget(); 2761 2762 // Default target. 2763 MOZ_ASSERT(mainLoc <= target && target < endLoc); 2764 MOZ_ASSERT(target.is(JSOp::JumpTarget)); 2765 2766 int32_t low = loc.getTableSwitchLow(); 2767 int32_t high = loc.getTableSwitchHigh(); 2768 2769 for (int i = 0; i < high - low + 1; i++) { 2770 BytecodeLocation switchCase = loc.getTableSwitchCaseTarget(this, i); 2771 MOZ_ASSERT(mainLoc <= switchCase && switchCase < endLoc); 2772 MOZ_ASSERT(switchCase.is(JSOp::JumpTarget)); 2773 } 2774 } 2775 } 2776 2777 // Check catch/finally blocks as jump targets. 2778 for (const TryNote& tn : trynotes()) { 2779 if (tn.kind() != TryNoteKind::Catch && tn.kind() != TryNoteKind::Finally) { 2780 continue; 2781 } 2782 2783 jsbytecode* tryStart = offsetToPC(tn.start); 2784 jsbytecode* tryPc = tryStart - JSOpLength_Try; 2785 MOZ_ASSERT(JSOp(*tryPc) == JSOp::Try); 2786 2787 jsbytecode* tryTarget = tryStart + tn.length; 2788 MOZ_ASSERT(main() <= tryTarget && tryTarget < codeEnd()); 2789 MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget))); 2790 } 2791 } 2792 #endif 2793 2794 void JSScript::addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf, 2795 size_t* sizeOfJitScript, 2796 size_t* sizeOfAllocSites) const { 2797 if (!hasJitScript()) { 2798 return; 2799 } 2800 2801 jitScript()->addSizeOfIncludingThis(mallocSizeOf, sizeOfJitScript, 2802 sizeOfAllocSites); 2803 } 2804 2805 js::GlobalObject& JSScript::uninlinedGlobal() const { return global(); } 2806 2807 unsigned js::PCToLineNumber(unsigned startLine, 2808 JS::LimitedColumnNumberOneOrigin startCol, 2809 SrcNote* notes, SrcNote* notesEnd, jsbytecode* code, 2810 jsbytecode* pc, 2811 JS::LimitedColumnNumberOneOrigin* columnp) { 2812 unsigned lineno = startLine; 2813 JS::LimitedColumnNumberOneOrigin column = startCol; 2814 2815 /* 2816 * Walk through source notes accumulating their deltas, keeping track of 2817 * line-number notes, until we pass the note for pc's offset within 2818 * script->code. 2819 */ 2820 ptrdiff_t offset = 0; 2821 ptrdiff_t target = pc - code; 2822 for (SrcNoteIterator iter(notes, notesEnd); !iter.atEnd(); ++iter) { 2823 const auto* sn = *iter; 2824 offset += sn->delta(); 2825 if (offset > target) { 2826 break; 2827 } 2828 2829 SrcNoteType type = sn->type(); 2830 if (type == SrcNoteType::SetLine) { 2831 lineno = SrcNote::SetLine::getLine(sn, startLine); 2832 column = JS::LimitedColumnNumberOneOrigin(); 2833 } else if (type == SrcNoteType::SetLineColumn) { 2834 lineno = SrcNote::SetLineColumn::getLine(sn, startLine); 2835 column = SrcNote::SetLineColumn::getColumn(sn); 2836 } else if (type == SrcNoteType::NewLine) { 2837 lineno++; 2838 column = JS::LimitedColumnNumberOneOrigin(); 2839 } else if (type == SrcNoteType::NewLineColumn) { 2840 lineno++; 2841 column = SrcNote::NewLineColumn::getColumn(sn); 2842 } else if (type == SrcNoteType::ColSpan) { 2843 column += SrcNote::ColSpan::getSpan(sn); 2844 } 2845 } 2846 2847 if (columnp) { 2848 *columnp = column; 2849 } 2850 2851 return lineno; 2852 } 2853 2854 unsigned js::PCToLineNumber(JSScript* script, jsbytecode* pc, 2855 JS::LimitedColumnNumberOneOrigin* columnp) { 2856 /* Cope with InterpreterFrame.pc value prior to entering Interpret. */ 2857 if (!pc) { 2858 return 0; 2859 } 2860 2861 return PCToLineNumber( 2862 script->lineno(), JS::LimitedColumnNumberOneOrigin(script->column()), 2863 script->notes(), script->notesEnd(), script->code(), pc, columnp); 2864 } 2865 2866 jsbytecode* js::LineNumberToPC(JSScript* script, unsigned target) { 2867 ptrdiff_t offset = 0; 2868 ptrdiff_t best = -1; 2869 unsigned lineno = script->lineno(); 2870 unsigned bestdiff = SrcNote::MaxOperand; 2871 for (SrcNoteIterator iter(script->notes(), script->notesEnd()); !iter.atEnd(); 2872 ++iter) { 2873 const auto* sn = *iter; 2874 /* 2875 * Exact-match only if offset is not in the prologue; otherwise use 2876 * nearest greater-or-equal line number match. 2877 */ 2878 if (lineno == target && offset >= ptrdiff_t(script->mainOffset())) { 2879 goto out; 2880 } 2881 if (lineno >= target) { 2882 unsigned diff = lineno - target; 2883 if (diff < bestdiff) { 2884 bestdiff = diff; 2885 best = offset; 2886 } 2887 } 2888 offset += sn->delta(); 2889 SrcNoteType type = sn->type(); 2890 if (type == SrcNoteType::SetLine) { 2891 lineno = SrcNote::SetLine::getLine(sn, script->lineno()); 2892 } else if (type == SrcNoteType::SetLineColumn) { 2893 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno()); 2894 } else if (type == SrcNoteType::NewLine || 2895 type == SrcNoteType::NewLineColumn) { 2896 lineno++; 2897 } 2898 } 2899 if (best >= 0) { 2900 offset = best; 2901 } 2902 out: 2903 return script->offsetToPC(offset); 2904 } 2905 2906 JS_PUBLIC_API unsigned js::GetScriptLineExtent( 2907 JSScript* script, JS::LimitedColumnNumberOneOrigin* columnp) { 2908 unsigned lineno = script->lineno(); 2909 JS::LimitedColumnNumberOneOrigin column = script->column(); 2910 unsigned maxLineNo = lineno; 2911 for (SrcNoteIterator iter(script->notes(), script->notesEnd()); !iter.atEnd(); 2912 ++iter) { 2913 const auto* sn = *iter; 2914 SrcNoteType type = sn->type(); 2915 if (type == SrcNoteType::SetLine) { 2916 lineno = SrcNote::SetLine::getLine(sn, script->lineno()); 2917 column = JS::LimitedColumnNumberOneOrigin(); 2918 } else if (type == SrcNoteType::SetLineColumn) { 2919 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno()); 2920 column = SrcNote::SetLineColumn::getColumn(sn); 2921 } else if (type == SrcNoteType::NewLine) { 2922 lineno++; 2923 column = JS::LimitedColumnNumberOneOrigin(); 2924 } else if (type == SrcNoteType::NewLineColumn) { 2925 lineno++; 2926 column = SrcNote::NewLineColumn::getColumn(sn); 2927 } else if (type == SrcNoteType::ColSpan) { 2928 column += SrcNote::ColSpan::getSpan(sn); 2929 } 2930 2931 if (maxLineNo < lineno) { 2932 maxLineNo = lineno; 2933 } 2934 } 2935 2936 if (columnp) { 2937 *columnp = column; 2938 } 2939 2940 return 1 + maxLineNo - script->lineno(); 2941 } 2942 2943 #ifdef JS_CACHEIR_SPEW 2944 void js::maybeUpdateWarmUpCount(JSScript* script) { 2945 if (script->needsFinalWarmUpCount()) { 2946 ScriptFinalWarmUpCountMap* map = 2947 script->zone()->scriptFinalWarmUpCountMap.get(); 2948 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have 2949 // already been created and thus must be asserted. 2950 MOZ_ASSERT(map); 2951 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script); 2952 MOZ_ASSERT(p); 2953 2954 std::get<0>(p->value()) += script->jitScript()->warmUpCount(); 2955 } 2956 } 2957 2958 void js::maybeSpewScriptFinalWarmUpCount(JSScript* script) { 2959 if (script->needsFinalWarmUpCount()) { 2960 ScriptFinalWarmUpCountMap* map = 2961 script->zone()->scriptFinalWarmUpCountMap.get(); 2962 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have 2963 // already been created and thus must be asserted. 2964 MOZ_ASSERT(map); 2965 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script); 2966 MOZ_ASSERT(p); 2967 auto& tuple = p->value(); 2968 uint32_t warmUpCount = std::get<0>(tuple); 2969 SharedImmutableString& scriptName = std::get<1>(tuple); 2970 2971 JSContext* cx = TlsContext.get(); 2972 cx->spewer().enableSpewing(); 2973 2974 // In the case that we care about a script's final warmup count but the 2975 // spewer is not enabled, AutoSpewChannel automatically sets and unsets 2976 // the proper channel for the duration of spewing a health report's warm 2977 // up count. 2978 AutoSpewChannel channel(cx, SpewChannel::CacheIRHealthReport, script); 2979 jit::CacheIRHealth cih; 2980 cih.spewScriptFinalWarmUpCount(cx, scriptName.chars(), script, warmUpCount); 2981 2982 script->zone()->scriptFinalWarmUpCountMap->remove(script); 2983 script->setNeedsFinalWarmUpCount(false); 2984 } 2985 } 2986 #endif 2987 2988 void js::DescribeScriptedCallerForDirectEval(JSContext* cx, HandleScript script, 2989 jsbytecode* pc, const char** file, 2990 uint32_t* linenop, 2991 uint32_t* pcOffset, 2992 bool* mutedErrors) { 2993 MOZ_ASSERT(script->containsPC(pc)); 2994 2995 static_assert(JSOpLength_SpreadEval == JSOpLength_StrictSpreadEval, 2996 "next op after a spread must be at consistent offset"); 2997 static_assert(JSOpLength_Eval == JSOpLength_StrictEval, 2998 "next op after a direct eval must be at consistent offset"); 2999 3000 MOZ_ASSERT(JSOp(*pc) == JSOp::Eval || JSOp(*pc) == JSOp::StrictEval || 3001 JSOp(*pc) == JSOp::SpreadEval || 3002 JSOp(*pc) == JSOp::StrictSpreadEval); 3003 3004 bool isSpread = 3005 (JSOp(*pc) == JSOp::SpreadEval || JSOp(*pc) == JSOp::StrictSpreadEval); 3006 jsbytecode* nextpc = 3007 pc + (isSpread ? JSOpLength_SpreadEval : JSOpLength_Eval); 3008 MOZ_ASSERT(JSOp(*nextpc) == JSOp::Lineno); 3009 3010 *file = script->filename(); 3011 *linenop = GET_UINT32(nextpc); 3012 *pcOffset = script->pcToOffset(pc); 3013 *mutedErrors = script->mutedErrors(); 3014 } 3015 3016 void js::DescribeScriptedCallerForCompilation( 3017 JSContext* cx, MutableHandleScript maybeScript, const char** file, 3018 uint32_t* linenop, uint32_t* pcOffset, bool* mutedErrors) { 3019 NonBuiltinFrameIter iter(cx, cx->realm()->principals()); 3020 3021 if (iter.done()) { 3022 maybeScript.set(nullptr); 3023 *file = nullptr; 3024 *linenop = 0; 3025 *pcOffset = 0; 3026 *mutedErrors = false; 3027 return; 3028 } 3029 3030 *file = iter.filename(); 3031 *linenop = iter.computeLine(); 3032 *mutedErrors = iter.mutedErrors(); 3033 3034 // These values are only used for introducer fields which are debugging 3035 // information and can be safely left null for wasm frames. 3036 if (iter.hasScript()) { 3037 maybeScript.set(iter.script()); 3038 *pcOffset = iter.pc() - maybeScript->code(); 3039 } else { 3040 maybeScript.set(nullptr); 3041 *pcOffset = 0; 3042 } 3043 } 3044 3045 template <typename SourceSpan, typename TargetSpan> 3046 void CopySpan(const SourceSpan& source, TargetSpan target) { 3047 MOZ_ASSERT(source.size() == target.size()); 3048 std::copy(source.cbegin(), source.cend(), target.begin()); 3049 } 3050 3051 /* static */ 3052 js::UniquePtr<ImmutableScriptData> ImmutableScriptData::new_( 3053 FrontendContext* fc, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots, 3054 GCThingIndex bodyScopeIndex, uint32_t numICEntries, bool isFunction, 3055 uint16_t funLength, uint16_t propertyCountEstimate, 3056 mozilla::Span<const jsbytecode> code, mozilla::Span<const SrcNote> notes, 3057 mozilla::Span<const uint32_t> resumeOffsets, 3058 mozilla::Span<const ScopeNote> scopeNotes, 3059 mozilla::Span<const TryNote> tryNotes) { 3060 MOZ_RELEASE_ASSERT(code.Length() <= frontend::MaxBytecodeLength); 3061 3062 // There are 1-4 copies of SrcNoteType::Null appended after the source 3063 // notes. These are a combination of sentinel and padding values. 3064 static_assert(frontend::MaxSrcNotesLength <= UINT32_MAX - CodeNoteAlign, 3065 "Length + CodeNoteAlign shouldn't overflow UINT32_MAX"); 3066 size_t noteLength = notes.Length(); 3067 MOZ_RELEASE_ASSERT(noteLength <= frontend::MaxSrcNotesLength); 3068 3069 size_t notePaddingLength = ComputeNotePadding(code.Length(), noteLength); 3070 3071 // Allocate ImmutableScriptData 3072 js::UniquePtr<ImmutableScriptData> data(ImmutableScriptData::new_( 3073 fc, code.Length(), noteLength + notePaddingLength, resumeOffsets.Length(), 3074 scopeNotes.Length(), tryNotes.Length())); 3075 if (!data) { 3076 return data; 3077 } 3078 3079 // Initialize POD fields 3080 data->mainOffset = mainOffset; 3081 data->nfixed = nfixed; 3082 data->nslots = nslots; 3083 data->bodyScopeIndex = bodyScopeIndex; 3084 data->numICEntries = numICEntries; 3085 data->propertyCountEstimate = propertyCountEstimate; 3086 3087 if (isFunction) { 3088 data->funLength = funLength; 3089 } 3090 3091 // Initialize trailing arrays 3092 CopySpan(code, data->codeSpan()); 3093 CopySpan(notes, data->notesSpan().To(noteLength)); 3094 std::fill_n(data->notes() + noteLength, notePaddingLength, 3095 SrcNote::padding()); 3096 CopySpan(resumeOffsets, data->resumeOffsets()); 3097 CopySpan(scopeNotes, data->scopeNotes()); 3098 CopySpan(tryNotes, data->tryNotes()); 3099 3100 return data; 3101 } 3102 3103 void ScriptWarmUpData::trace(JSTracer* trc) { 3104 uintptr_t tag = data_ & TagMask; 3105 switch (tag) { 3106 case EnclosingScriptTag: { 3107 BaseScript* enclosingScript = toEnclosingScript(); 3108 BaseScript* prior = enclosingScript; 3109 TraceManuallyBarrieredEdge(trc, &enclosingScript, "enclosingScript"); 3110 if (enclosingScript != prior) { 3111 setTaggedPtr<EnclosingScriptTag>(enclosingScript); 3112 } 3113 break; 3114 } 3115 3116 case EnclosingScopeTag: { 3117 Scope* enclosingScope = toEnclosingScope(); 3118 Scope* prior = enclosingScope; 3119 TraceManuallyBarrieredEdge(trc, &enclosingScope, "enclosingScope"); 3120 if (enclosingScope != prior) { 3121 setTaggedPtr<EnclosingScopeTag>(enclosingScope); 3122 } 3123 break; 3124 } 3125 3126 case JitScriptTag: { 3127 toJitScript()->trace(trc); 3128 break; 3129 } 3130 3131 default: { 3132 MOZ_ASSERT(isWarmUpCount()); 3133 break; 3134 } 3135 } 3136 } 3137 3138 size_t JSScript::calculateLiveFixed(jsbytecode* pc) { 3139 size_t nlivefixed = numAlwaysLiveFixedSlots(); 3140 3141 if (nfixed() != nlivefixed) { 3142 Scope* scope = lookupScope(pc); 3143 if (scope) { 3144 scope = MaybeForwarded(scope); 3145 } 3146 3147 // Find the nearest LexicalScope in the same script. 3148 while (scope && scope->is<WithScope>()) { 3149 scope = scope->enclosing(); 3150 if (scope) { 3151 scope = MaybeForwarded(scope); 3152 } 3153 } 3154 3155 if (scope) { 3156 if (scope->is<LexicalScope>()) { 3157 nlivefixed = scope->as<LexicalScope>().nextFrameSlot(); 3158 } else if (scope->is<VarScope>()) { 3159 nlivefixed = scope->as<VarScope>().nextFrameSlot(); 3160 } else if (scope->is<ClassBodyScope>()) { 3161 nlivefixed = scope->as<ClassBodyScope>().nextFrameSlot(); 3162 } 3163 } 3164 } 3165 3166 MOZ_ASSERT(nlivefixed <= nfixed()); 3167 MOZ_ASSERT(nlivefixed >= numAlwaysLiveFixedSlots()); 3168 3169 return nlivefixed; 3170 } 3171 3172 Scope* JSScript::lookupScope(const jsbytecode* pc) const { 3173 MOZ_ASSERT(containsPC(pc)); 3174 3175 size_t offset = pc - code(); 3176 3177 auto notes = scopeNotes(); 3178 Scope* scope = nullptr; 3179 3180 // Find the innermost block chain using a binary search. 3181 size_t bottom = 0; 3182 size_t top = notes.size(); 3183 3184 while (bottom < top) { 3185 size_t mid = bottom + (top - bottom) / 2; 3186 const ScopeNote* note = ¬es[mid]; 3187 if (note->start <= offset) { 3188 // Block scopes are ordered in the list by their starting offset, and 3189 // since blocks form a tree ones earlier in the list may cover the pc even 3190 // if later blocks end before the pc. This only happens when the earlier 3191 // block is a parent of the later block, so we need to check parents of 3192 // |mid| in the searched range for coverage. 3193 size_t check = mid; 3194 while (check >= bottom) { 3195 const ScopeNote* checkNote = ¬es[check]; 3196 MOZ_ASSERT(checkNote->start <= offset); 3197 if (offset < checkNote->start + checkNote->length) { 3198 // We found a matching block chain but there may be inner ones 3199 // at a higher block chain index than mid. Continue the binary search. 3200 if (checkNote->index == ScopeNote::NoScopeIndex) { 3201 scope = nullptr; 3202 } else { 3203 scope = getScope(checkNote->index); 3204 } 3205 break; 3206 } 3207 if (checkNote->parent == UINT32_MAX) { 3208 break; 3209 } 3210 check = checkNote->parent; 3211 } 3212 bottom = mid + 1; 3213 } else { 3214 top = mid; 3215 } 3216 } 3217 3218 return scope; 3219 } 3220 3221 Scope* JSScript::innermostScope(const jsbytecode* pc) const { 3222 if (Scope* scope = lookupScope(pc)) { 3223 return scope; 3224 } 3225 return bodyScope(); 3226 } 3227 3228 void js::SetFrameArgumentsObject(JSContext* cx, AbstractFramePtr frame, 3229 JSObject* argsobj) { 3230 /* 3231 * If the arguments object was optimized out by scalar replacement, 3232 * we must recreate it when we bail out. Because 'arguments' may have 3233 * already been overwritten, we must check to see if the slot already 3234 * contains a value. 3235 */ 3236 3237 JSScript* script = frame.script(); 3238 3239 BindingIter bi(script); 3240 while (bi && bi.name() != cx->names().arguments) { 3241 bi++; 3242 } 3243 if (!bi) { 3244 return; 3245 } 3246 3247 if (bi.location().kind() == BindingLocation::Kind::Environment) { 3248 #ifdef DEBUG 3249 /* 3250 * If |arguments| lives in the call object, we should not have 3251 * optimized it. Scan the script to find the slot in the call 3252 * object that |arguments| is assigned to and verify that it 3253 * already exists. 3254 */ 3255 jsbytecode* pc = script->code(); 3256 while (JSOp(*pc) != JSOp::Arguments) { 3257 pc += GetBytecodeLength(pc); 3258 } 3259 pc += JSOpLength_Arguments; 3260 MOZ_ASSERT(JSOp(*pc) == JSOp::SetAliasedVar); 3261 3262 EnvironmentObject& env = frame.callObj().as<EnvironmentObject>(); 3263 MOZ_ASSERT(!env.aliasedBinding(bi).isMagic(JS_OPTIMIZED_OUT)); 3264 #endif 3265 return; 3266 } 3267 3268 MOZ_ASSERT(bi.location().kind() == BindingLocation::Kind::Frame); 3269 uint32_t frameSlot = bi.location().slot(); 3270 if (frame.unaliasedLocal(frameSlot).isMagic(JS_OPTIMIZED_OUT)) { 3271 frame.unaliasedLocal(frameSlot) = ObjectValue(*argsobj); 3272 } 3273 } 3274 3275 bool JSScript::formalIsAliased(unsigned argSlot) { 3276 if (functionHasParameterExprs()) { 3277 return false; 3278 } 3279 3280 for (PositionalFormalParameterIter fi(this); fi; fi++) { 3281 if (fi.argumentSlot() == argSlot) { 3282 return fi.closedOver(); 3283 } 3284 } 3285 MOZ_CRASH("Argument slot not found"); 3286 } 3287 3288 // Returns true if any formal argument is mapped by the arguments 3289 // object, but lives in the call object. 3290 bool JSScript::anyFormalIsForwarded() { 3291 if (!argsObjAliasesFormals()) { 3292 return false; 3293 } 3294 3295 for (PositionalFormalParameterIter fi(this); fi; fi++) { 3296 if (fi.closedOver()) { 3297 return true; 3298 } 3299 } 3300 return false; 3301 } 3302 3303 bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) { 3304 return argsObjAliasesFormals() && !formalIsAliased(argSlot); 3305 } 3306 3307 BaseScript::BaseScript(uint8_t* stubEntry, JSFunction* function, 3308 ScriptSourceObject* sourceObject, 3309 const SourceExtent& extent, uint32_t immutableFlags) 3310 : TenuredCellWithNonGCPointer(stubEntry), 3311 function_(function), 3312 sourceObject_(sourceObject), 3313 extent_(extent), 3314 immutableFlags_(immutableFlags) { 3315 MOZ_ASSERT(extent_.toStringStart <= extent_.sourceStart); 3316 MOZ_ASSERT(extent_.sourceStart <= extent_.sourceEnd); 3317 MOZ_ASSERT(extent_.sourceEnd <= extent_.toStringEnd); 3318 } 3319 3320 /* static */ 3321 BaseScript* BaseScript::New(JSContext* cx, JS::Handle<JSFunction*> function, 3322 Handle<ScriptSourceObject*> sourceObject, 3323 const SourceExtent& extent, 3324 uint32_t immutableFlags) { 3325 uint8_t* stubEntry = nullptr; 3326 if (jit::HasJitBackend()) { 3327 stubEntry = cx->runtime()->jitRuntime()->interpreterStub().value; 3328 } 3329 3330 MOZ_ASSERT_IF(function, 3331 function->compartment() == sourceObject->compartment()); 3332 MOZ_ASSERT_IF(function, function->realm() == sourceObject->realm()); 3333 3334 return cx->newCell<BaseScript>(stubEntry, function, sourceObject, extent, 3335 immutableFlags); 3336 } 3337 3338 /* static */ 3339 BaseScript* BaseScript::CreateRawLazy(JSContext* cx, uint32_t ngcthings, 3340 HandleFunction fun, 3341 Handle<ScriptSourceObject*> sourceObject, 3342 const SourceExtent& extent, 3343 uint32_t immutableFlags) { 3344 cx->check(fun); 3345 3346 BaseScript* lazy = New(cx, fun, sourceObject, extent, immutableFlags); 3347 if (!lazy) { 3348 return nullptr; 3349 } 3350 3351 // Allocate a PrivateScriptData if it will not be empty. Lazy class 3352 // constructors that use member initializers also need PrivateScriptData for 3353 // field data. 3354 // 3355 // This condition is implicit in BaseScript::hasPrivateScriptData, and should 3356 // be mirrored on InputScript::hasPrivateScriptData. 3357 if (ngcthings || lazy->useMemberInitializers()) { 3358 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings)); 3359 if (!data) { 3360 return nullptr; 3361 } 3362 lazy->swapData(data); 3363 MOZ_ASSERT(!data); 3364 } 3365 3366 return lazy; 3367 } 3368 3369 #ifdef ENABLE_PORTABLE_BASELINE_INTERP 3370 // This is an arbitrary non-null pointer that we use as a placeholder 3371 // for scripts that can be run in PBL: the rest of the engine expects 3372 // a "non-null jitcode pointer" but we'll never actually call it. We 3373 // have to ensure alignment to keep GC happy. 3374 static uint8_t* const PBLJitCodePtr = reinterpret_cast<uint8_t*>(8); 3375 #endif 3376 3377 void JSScript::updateJitCodeRaw(JSRuntime* rt) { 3378 MOZ_ASSERT(rt); 3379 if (hasBaselineScript() && baselineScript()->hasPendingIonCompileTask()) { 3380 MOZ_ASSERT(!isIonCompilingOffThread()); 3381 setJitCodeRaw(rt->jitRuntime()->lazyLinkStub().value); 3382 } else if (hasIonScript()) { 3383 jit::IonScript* ion = ionScript(); 3384 setJitCodeRaw(ion->method()->raw()); 3385 } else if (hasBaselineScript()) { 3386 setJitCodeRaw(baselineScript()->method()->raw()); 3387 } else if (hasJitScript() && js::jit::IsBaselineInterpreterEnabled()) { 3388 bool usingEntryTrampoline = false; 3389 if (js::jit::JitOptions.emitInterpreterEntryTrampoline) { 3390 auto p = rt->jitRuntime()->getInterpreterEntryMap()->lookup(this); 3391 if (p) { 3392 setJitCodeRaw(p->value().raw()); 3393 usingEntryTrampoline = true; 3394 } 3395 } 3396 if (!usingEntryTrampoline) { 3397 setJitCodeRaw(rt->jitRuntime()->baselineInterpreter().codeRaw()); 3398 } 3399 #ifdef ENABLE_PORTABLE_BASELINE_INTERP 3400 } else if (hasJitScript() && 3401 js::jit::IsPortableBaselineInterpreterEnabled()) { 3402 // The portable baseline interpreter does not dispatch on this 3403 // pointer, but it needs to be non-null to trigger the appropriate 3404 // code-paths, so we set it to a placeholder value here. 3405 setJitCodeRaw(PBLJitCodePtr); 3406 #endif // ENABLE_PORTABLE_BASELINE_INTERP 3407 } else if (!js::jit::IsBaselineInterpreterEnabled()) { 3408 setJitCodeRaw(nullptr); 3409 } else { 3410 setJitCodeRaw(rt->jitRuntime()->interpreterStub().value); 3411 } 3412 MOZ_ASSERT_IF(!js::jit::IsPortableBaselineInterpreterEnabled(), jitCodeRaw()); 3413 } 3414 3415 bool JSScript::hasLoops() { 3416 for (const TryNote& tn : trynotes()) { 3417 if (tn.isLoop()) { 3418 return true; 3419 } 3420 } 3421 return false; 3422 } 3423 3424 bool JSScript::mayReadFrameArgsDirectly() { 3425 return needsArgsObj() || usesArgumentsIntrinsics() || hasRest(); 3426 } 3427 3428 void JSScript::resetWarmUpCounterToDelayIonCompilation() { 3429 // Reset the warm-up count only if it's greater than the BaselineCompiler 3430 // threshold. We do this to ensure this has no effect on Baseline compilation 3431 // because we don't want scripts to get stuck in the (Baseline) interpreter in 3432 // pathological cases. 3433 3434 if (getWarmUpCount() > jit::JitOptions.baselineJitWarmUpThreshold) { 3435 incWarmUpResetCounter(); 3436 uint32_t newCount = jit::JitOptions.baselineJitWarmUpThreshold; 3437 if (warmUpData_.isWarmUpCount()) { 3438 warmUpData_.resetWarmUpCount(newCount); 3439 } else { 3440 warmUpData_.toJitScript()->resetWarmUpCount(newCount); 3441 } 3442 } 3443 } 3444 3445 #if defined(DEBUG) || defined(JS_JITSPEW) 3446 3447 void BaseScript::dumpStringContent(js::GenericPrinter& out) const { 3448 StringEscape esc('"'); 3449 EscapePrinter ep(out, esc); 3450 ep.printf("%s:%u:%u @ 0x%p", filename() ? filename() : "<null>", lineno(), 3451 column().oneOriginValue(), this); 3452 } 3453 3454 void JSScript::dump(JSContext* cx) { 3455 JS::Rooted<JSScript*> script(cx, this); 3456 3457 js::Sprinter sp(cx); 3458 if (!sp.init()) { 3459 return; 3460 } 3461 3462 DumpOptions options; 3463 options.runtimeData = true; 3464 if (!dump(cx, script, options, &sp)) { 3465 return; 3466 } 3467 3468 JS::UniqueChars str = sp.release(); 3469 if (!str) { 3470 return; 3471 } 3472 fprintf(stderr, "%s\n", str.get()); 3473 } 3474 3475 void JSScript::dumpRecursive(JSContext* cx) { 3476 JS::Rooted<JSScript*> script(cx, this); 3477 3478 js::Sprinter sp(cx); 3479 if (!sp.init()) { 3480 return; 3481 } 3482 3483 DumpOptions options; 3484 options.runtimeData = true; 3485 options.recursive = true; 3486 if (!dump(cx, script, options, &sp)) { 3487 return; 3488 } 3489 3490 JS::UniqueChars str = sp.release(); 3491 if (!str) { 3492 return; 3493 } 3494 fprintf(stderr, "%s\n", str.get()); 3495 } 3496 3497 static void DumpMutableScriptFlags(js::JSONPrinter& json, 3498 MutableScriptFlags mutableFlags) { 3499 // Skip warmup data. 3500 static_assert(int(MutableScriptFlagsEnum::WarmupResets_MASK) == 0xff); 3501 3502 for (uint32_t i = 0x100; i; i = i << 1) { 3503 if (uint32_t(mutableFlags) & i) { 3504 switch (MutableScriptFlagsEnum(i)) { 3505 case MutableScriptFlagsEnum::HasRunOnce: 3506 json.value("HasRunOnce"); 3507 break; 3508 case MutableScriptFlagsEnum::HasBeenCloned: 3509 json.value("HasBeenCloned"); 3510 break; 3511 case MutableScriptFlagsEnum::HasScriptCounts: 3512 json.value("HasScriptCounts"); 3513 break; 3514 case MutableScriptFlagsEnum::HasDebugScript: 3515 json.value("HasDebugScript"); 3516 break; 3517 case MutableScriptFlagsEnum::AllowRelazify: 3518 json.value("AllowRelazify"); 3519 break; 3520 case MutableScriptFlagsEnum::SpewEnabled: 3521 json.value("SpewEnabled"); 3522 break; 3523 case MutableScriptFlagsEnum::NeedsFinalWarmUpCount: 3524 json.value("NeedsFinalWarmUpCount"); 3525 break; 3526 case MutableScriptFlagsEnum::BaselineDisabled: 3527 json.value("BaselineDisabled"); 3528 break; 3529 case MutableScriptFlagsEnum::IonDisabled: 3530 json.value("IonDisabled"); 3531 break; 3532 case MutableScriptFlagsEnum::Uninlineable: 3533 json.value("Uninlineable"); 3534 break; 3535 case MutableScriptFlagsEnum::NoEagerBaselineHint: 3536 json.value("NoEagerBaselineHint"); 3537 break; 3538 case MutableScriptFlagsEnum::FailedBoundsCheck: 3539 json.value("FailedBoundsCheck"); 3540 break; 3541 case MutableScriptFlagsEnum::HadLICMInvalidation: 3542 json.value("HadLICMInvalidation"); 3543 break; 3544 case MutableScriptFlagsEnum::HadReorderingBailout: 3545 json.value("HadReorderingBailout"); 3546 break; 3547 case MutableScriptFlagsEnum::HadEagerTruncationBailout: 3548 json.value("HadEagerTruncationBailout"); 3549 break; 3550 case MutableScriptFlagsEnum::FailedLexicalCheck: 3551 json.value("FailedLexicalCheck"); 3552 break; 3553 case MutableScriptFlagsEnum::HadSpeculativePhiBailout: 3554 json.value("HadSpeculativePhiBailout"); 3555 break; 3556 case MutableScriptFlagsEnum::HadUnboxFoldingBailout: 3557 json.value("HadUnboxFoldingBailout"); 3558 break; 3559 default: 3560 json.value("Unknown(%x)", i); 3561 break; 3562 } 3563 } 3564 } 3565 } 3566 3567 /* static */ 3568 bool JSScript::dump(JSContext* cx, JS::Handle<JSScript*> script, 3569 DumpOptions& options, js::StringPrinter* sp) { 3570 { 3571 JSONPrinter json(*sp); 3572 3573 json.beginObject(); 3574 3575 if (const char* filename = script->filename()) { 3576 json.property("file", filename); 3577 } else { 3578 json.nullProperty("file"); 3579 } 3580 3581 json.property("lineno", script->lineno()); 3582 json.property("column", script->column().oneOriginValue()); 3583 3584 json.beginListProperty("immutableFlags"); 3585 DumpImmutableScriptFlags(json, script->immutableFlags()); 3586 json.endList(); 3587 3588 if (options.runtimeData) { 3589 json.beginListProperty("mutableFlags"); 3590 DumpMutableScriptFlags(json, script->mutableFlags_); 3591 json.endList(); 3592 } 3593 3594 if (script->isFunction()) { 3595 JS::Rooted<JSFunction*> fun(cx, script->function()); 3596 3597 JS::Rooted<JSAtom*> name(cx, fun->fullDisplayAtom()); 3598 if (name) { 3599 UniqueChars bytes = JS_EncodeStringToUTF8(cx, name); 3600 if (!bytes) { 3601 return false; 3602 } 3603 json.property("functionName", bytes.get()); 3604 } else { 3605 json.nullProperty("functionName"); 3606 } 3607 3608 json.beginListProperty("functionFlags"); 3609 DumpFunctionFlagsItems(json, fun->flags()); 3610 json.endList(); 3611 } 3612 3613 json.endObject(); 3614 } 3615 3616 if (sp->hadOutOfMemory()) { 3617 sp->forwardOutOfMemory(); 3618 return false; 3619 } 3620 3621 sp->put("\n"); 3622 3623 if (!Disassemble(cx, script, /* lines = */ true, sp)) { 3624 return false; 3625 } 3626 if (!dumpSrcNotes(cx, script, sp)) { 3627 return false; 3628 } 3629 if (!dumpTryNotes(cx, script, sp)) { 3630 return false; 3631 } 3632 if (!dumpScopeNotes(cx, script, sp)) { 3633 return false; 3634 } 3635 if (!dumpGCThings(cx, script, sp)) { 3636 return false; 3637 } 3638 3639 if (options.recursive) { 3640 for (JS::GCCellPtr gcThing : script->gcthings()) { 3641 if (!gcThing.is<JSObject>()) { 3642 continue; 3643 } 3644 3645 JSObject* obj = &gcThing.as<JSObject>(); 3646 if (obj->is<JSFunction>()) { 3647 sp->put("\n"); 3648 3649 JS::Rooted<JSFunction*> fun(cx, &obj->as<JSFunction>()); 3650 if (fun->isInterpreted()) { 3651 JS::Rooted<JSScript*> innerScript( 3652 cx, JSFunction::getOrCreateScript(cx, fun)); 3653 if (!innerScript) { 3654 return false; 3655 } 3656 if (!dump(cx, innerScript, options, sp)) { 3657 return false; 3658 } 3659 } else { 3660 sp->put("[native code]\n"); 3661 } 3662 } 3663 } 3664 } 3665 3666 return true; 3667 } 3668 3669 /* static */ 3670 bool JSScript::dumpSrcNotes(JSContext* cx, JS::Handle<JSScript*> script, 3671 js::GenericPrinter* sp) { 3672 sp->put("\nSource notes:\n"); 3673 sp->printf("%4s %4s %6s %5s %6s %-16s %s\n", "ofs", "line", "column", "pc", 3674 "delta", "desc", "args"); 3675 sp->put("---- ---- ------ ----- ------ ---------------- ------\n"); 3676 unsigned offset = 0; 3677 unsigned lineno = script->lineno(); 3678 JS::LimitedColumnNumberOneOrigin column = script->column(); 3679 SrcNote* notes = script->notes(); 3680 SrcNote* notesEnd = script->notesEnd(); 3681 for (SrcNoteIterator iter(notes, notesEnd); !iter.atEnd(); ++iter) { 3682 const auto* sn = *iter; 3683 3684 unsigned delta = sn->delta(); 3685 offset += delta; 3686 SrcNoteType type = sn->type(); 3687 const char* name = sn->name(); 3688 sp->printf("%3u: %4u %6u %5u [%4u] %-16s", unsigned(sn - notes), lineno, 3689 column.oneOriginValue(), offset, delta, name); 3690 3691 switch (type) { 3692 case SrcNoteType::Breakpoint: 3693 case SrcNoteType::BreakpointStepSep: 3694 case SrcNoteType::XDelta: 3695 break; 3696 3697 case SrcNoteType::ColSpan: { 3698 JS::ColumnNumberOffset colspan = SrcNote::ColSpan::getSpan(sn); 3699 sp->printf(" colspan %u", colspan.value()); 3700 column += colspan; 3701 break; 3702 } 3703 3704 case SrcNoteType::SetLine: 3705 lineno = SrcNote::SetLine::getLine(sn, script->lineno()); 3706 sp->printf(" lineno %u", lineno); 3707 column = JS::LimitedColumnNumberOneOrigin(); 3708 break; 3709 3710 case SrcNoteType::SetLineColumn: 3711 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno()); 3712 column = SrcNote::SetLineColumn::getColumn(sn); 3713 sp->printf(" lineno %u column %u", lineno, column.oneOriginValue()); 3714 break; 3715 3716 case SrcNoteType::NewLine: 3717 ++lineno; 3718 column = JS::LimitedColumnNumberOneOrigin(); 3719 break; 3720 3721 case SrcNoteType::NewLineColumn: 3722 column = SrcNote::NewLineColumn::getColumn(sn); 3723 sp->printf(" column %u", column.oneOriginValue()); 3724 ++lineno; 3725 break; 3726 3727 default: 3728 MOZ_ASSERT_UNREACHABLE("unrecognized srcnote"); 3729 } 3730 sp->put("\n"); 3731 } 3732 3733 return true; 3734 } 3735 3736 static const char* TryNoteName(TryNoteKind kind) { 3737 switch (kind) { 3738 case TryNoteKind::Catch: 3739 return "catch"; 3740 case TryNoteKind::Finally: 3741 return "finally"; 3742 case TryNoteKind::ForIn: 3743 return "for-in"; 3744 case TryNoteKind::ForOf: 3745 return "for-of"; 3746 case TryNoteKind::Loop: 3747 return "loop"; 3748 case TryNoteKind::ForOfIterClose: 3749 return "for-of-iterclose"; 3750 case TryNoteKind::Destructuring: 3751 return "destructuring"; 3752 } 3753 3754 MOZ_CRASH("Bad TryNoteKind"); 3755 } 3756 3757 /* static */ 3758 bool JSScript::dumpTryNotes(JSContext* cx, JS::Handle<JSScript*> script, 3759 js::GenericPrinter* sp) { 3760 sp->put("\nException table:\nkind stack start end\n"); 3761 3762 for (const js::TryNote& tn : script->trynotes()) { 3763 sp->printf(" %-16s %6u %8u %8u\n", TryNoteName(tn.kind()), tn.stackDepth, 3764 tn.start, tn.start + tn.length); 3765 } 3766 return true; 3767 } 3768 3769 /* static */ 3770 bool JSScript::dumpScopeNotes(JSContext* cx, JS::Handle<JSScript*> script, 3771 js::GenericPrinter* sp) { 3772 sp->put("\nScope notes:\n index parent start end\n"); 3773 3774 for (const ScopeNote& note : script->scopeNotes()) { 3775 if (note.index == ScopeNote::NoScopeIndex) { 3776 sp->printf("%8s ", "(none)"); 3777 } else { 3778 sp->printf("%8u ", note.index.index); 3779 } 3780 if (note.parent == ScopeNote::NoScopeIndex) { 3781 sp->printf("%8s ", "(none)"); 3782 } else { 3783 sp->printf("%8u ", note.parent); 3784 } 3785 sp->printf("%8u %8u\n", note.start, note.start + note.length); 3786 } 3787 return true; 3788 } 3789 3790 /* static */ 3791 bool JSScript::dumpGCThings(JSContext* cx, JS::Handle<JSScript*> script, 3792 js::GenericPrinter* sp) { 3793 sp->put("\nGC things:\n index type value\n"); 3794 3795 size_t i = 0; 3796 for (JS::GCCellPtr gcThing : script->gcthings()) { 3797 sp->printf("%8zu ", i); 3798 if (gcThing.is<JS::BigInt>()) { 3799 sp->put("BigInt "); 3800 gcThing.as<JS::BigInt>().dump(*sp); 3801 sp->put("\n"); 3802 } else if (gcThing.is<Scope>()) { 3803 sp->put("Scope "); 3804 JS::Rooted<Scope*> scope(cx, &gcThing.as<Scope>()); 3805 if (!Scope::dumpForDisassemble(cx, scope, *sp, 3806 " ")) { 3807 return false; 3808 } 3809 sp->put("\n"); 3810 } else if (gcThing.is<JSObject>()) { 3811 JSObject* obj = &gcThing.as<JSObject>(); 3812 if (obj->is<JSFunction>()) { 3813 sp->put("Function "); 3814 JS::Rooted<JSFunction*> fun(cx, &obj->as<JSFunction>()); 3815 if (fun->fullDisplayAtom()) { 3816 JS::Rooted<JSAtom*> name(cx, fun->fullDisplayAtom()); 3817 JS::UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, name); 3818 if (!utf8chars) { 3819 return false; 3820 } 3821 sp->put(utf8chars.get()); 3822 } else { 3823 sp->put("(anonymous)"); 3824 } 3825 3826 if (fun->hasBaseScript()) { 3827 BaseScript* script = fun->baseScript(); 3828 sp->printf(" @ %u:%u\n", script->lineno(), 3829 script->column().oneOriginValue()); 3830 } else { 3831 sp->put(" (no script)\n"); 3832 } 3833 } else { 3834 if (obj->is<RegExpObject>()) { 3835 sp->put("RegExp "); 3836 } else { 3837 sp->put("Object "); 3838 } 3839 3840 JS::Rooted<JS::Value> objValue(cx, ObjectValue(*obj)); 3841 JS::UniqueChars source = ToDisassemblySource(cx, objValue); 3842 if (!source) { 3843 return false; 3844 } 3845 sp->put(source.get()); 3846 sp->put("\n"); 3847 } 3848 } else if (gcThing.is<JSString>()) { 3849 JS::Rooted<JSString*> str(cx, &gcThing.as<JSString>()); 3850 if (str->isAtom()) { 3851 sp->put("Atom "); 3852 } else { 3853 sp->put("String "); 3854 } 3855 JS::UniqueChars chars = QuoteString(cx, str, '"'); 3856 if (!chars) { 3857 return false; 3858 } 3859 sp->put(chars.get()); 3860 sp->put("\n"); 3861 } else { 3862 sp->put("Unknown\n"); 3863 } 3864 i++; 3865 } 3866 3867 return true; 3868 } 3869 3870 #endif // defined(DEBUG) || defined(JS_JITSPEW) 3871 3872 void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun) { 3873 if (fun) { 3874 JSAutoRealm ar(cx_, fun); 3875 script_ = JSFunction::getOrCreateScript(cx_, fun); 3876 if (script_) { 3877 oldAllowRelazify_ = script_->allowRelazify(); 3878 script_->clearAllowRelazify(); 3879 } 3880 } 3881 } 3882 3883 void JSScript::AutoDelazify::dropScript() { 3884 if (script_) { 3885 script_->setAllowRelazify(oldAllowRelazify_); 3886 } 3887 script_ = nullptr; 3888 } 3889 3890 JS::ubi::Base::Size JS::ubi::Concrete<BaseScript>::size( 3891 mozilla::MallocSizeOf mallocSizeOf) const { 3892 BaseScript* base = &get(); 3893 3894 Size size = gc::Arena::thingSize(base->getAllocKind()); 3895 size += base->sizeOfExcludingThis(mallocSizeOf); 3896 3897 // Include any JIT data if it exists. 3898 if (base->hasJitScript()) { 3899 JSScript* script = base->asJSScript(); 3900 3901 size_t jitScriptSize = 0; 3902 size_t allocSitesSize = 0; 3903 script->addSizeOfJitScript(mallocSizeOf, &jitScriptSize, &allocSitesSize); 3904 size += jitScriptSize; 3905 size += allocSitesSize; 3906 3907 size_t baselineSize = 0; 3908 jit::AddSizeOfBaselineData(script, mallocSizeOf, &baselineSize); 3909 size += baselineSize; 3910 3911 size += jit::SizeOfIonData(script, mallocSizeOf); 3912 } 3913 3914 MOZ_ASSERT(size > 0); 3915 return size; 3916 } 3917 3918 const char* JS::ubi::Concrete<BaseScript>::scriptFilename() const { 3919 return get().filename(); 3920 }