Backed out 22 changesets (bug 1839396) for causing build bustages on js/Printer.h...
[gecko.git] / js / src / vm / JSScript.cpp
blob2d071c56e374aa477ce84448f96e587619766162
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/. */
7 /*
8 * JS script operations.
9 */
11 #include "vm/JSScript-inl.h"
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/PodOperations.h"
19 #include "mozilla/ScopeExit.h"
20 #include "mozilla/Span.h" // mozilla::{Span,Span}
21 #include "mozilla/Sprintf.h"
22 #include "mozilla/Utf8.h"
23 #include "mozilla/Vector.h"
25 #include <algorithm>
26 #include <new>
27 #include <string.h>
28 #include <type_traits>
29 #include <utility>
31 #include "jstypes.h"
33 #include "frontend/BytecodeSection.h"
34 #include "frontend/CompilationStencil.h" // frontend::CompilationStencil
35 #include "frontend/FrontendContext.h" // AutoReportFrontendContext
36 #include "frontend/ParseContext.h"
37 #include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator
38 #include "frontend/Stencil.h" // DumpFunctionFlagsItems, DumpImmutableScriptFlags
39 #include "frontend/StencilXdr.h" // XDRStencilEncoder
40 #include "gc/GCContext.h"
41 #include "jit/BaselineJIT.h"
42 #include "jit/CacheIRHealth.h"
43 #include "jit/Ion.h"
44 #include "jit/IonScript.h"
45 #include "jit/JitCode.h"
46 #include "jit/JitOptions.h"
47 #include "jit/JitRuntime.h"
48 #include "js/CharacterEncoding.h" // JS_EncodeStringToUTF8
49 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberZeroOrigin, JS::ColumnNumberOffset
50 #include "js/CompileOptions.h"
51 #include "js/experimental/SourceHook.h"
52 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
53 #include "js/HeapAPI.h" // JS::GCCellPtr
54 #include "js/MemoryMetrics.h"
55 #include "js/Printer.h" // js::GenericPrinter, js::Fprinter, js::Sprinter, js::QuoteString
56 #include "js/Transcoding.h"
57 #include "js/UniquePtr.h"
58 #include "js/Utility.h" // JS::UniqueChars
59 #include "js/Value.h" // JS::Value
60 #include "util/Poison.h"
61 #include "util/StringBuffer.h"
62 #include "util/Text.h"
63 #include "vm/BigIntType.h" // JS::BigInt
64 #include "vm/BytecodeIterator.h"
65 #include "vm/BytecodeLocation.h"
66 #include "vm/BytecodeUtil.h" // Disassemble
67 #include "vm/Compression.h"
68 #include "vm/HelperThreadState.h" // js::RunPendingSourceCompressions
69 #include "vm/JSContext.h"
70 #include "vm/JSFunction.h"
71 #include "vm/JSObject.h"
72 #include "vm/JSONPrinter.h" // JSONPrinter
73 #include "vm/Opcodes.h"
74 #include "vm/Scope.h" // Scope
75 #include "vm/SharedImmutableStringsCache.h"
76 #include "vm/StencilEnums.h" // TryNote, TryNoteKind, ScopeNote
77 #include "vm/StringType.h" // JSString, JSAtom
78 #include "vm/Time.h" // AutoIncrementalTimer
79 #include "vm/ToSource.h" // JS::ValueToSource
80 #ifdef MOZ_VTUNE
81 # include "vtune/VTuneWrapper.h"
82 #endif
84 #include "gc/Marking-inl.h"
85 #include "vm/BytecodeIterator-inl.h"
86 #include "vm/BytecodeLocation-inl.h"
87 #include "vm/Compartment-inl.h"
88 #include "vm/JSObject-inl.h"
89 #include "vm/SharedImmutableStringsCache-inl.h"
90 #include "vm/Stack-inl.h"
92 using namespace js;
94 using mozilla::CheckedInt;
95 using mozilla::Maybe;
96 using mozilla::PodCopy;
97 using mozilla::PointerRangeSize;
98 using mozilla::Utf8AsUnsignedChars;
99 using mozilla::Utf8Unit;
101 using JS::CompileOptions;
102 using JS::ReadOnlyCompileOptions;
103 using JS::SourceText;
105 bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime* rt) const {
106 return jitCodeRaw() == rt->jitRuntime()->interpreterStub().value;
109 js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const {
110 return MaybeForwarded(sourceObject())->source();
113 void js::BaseScript::setEnclosingScript(BaseScript* enclosingScript) {
114 MOZ_ASSERT(enclosingScript);
115 warmUpData_.initEnclosingScript(enclosingScript);
118 void js::BaseScript::setEnclosingScope(Scope* enclosingScope) {
119 if (warmUpData_.isEnclosingScript()) {
120 warmUpData_.clearEnclosingScript();
123 MOZ_ASSERT(enclosingScope);
124 warmUpData_.initEnclosingScope(enclosingScope);
127 void js::BaseScript::finalize(JS::GCContext* gcx) {
128 // Scripts with bytecode may have optional data stored in per-runtime or
129 // per-zone maps. Note that a failed compilation must not have entries since
130 // the script itself will not be marked as having bytecode.
131 if (hasBytecode()) {
132 JSScript* script = this->asJSScript();
134 if (coverage::IsLCovEnabled()) {
135 coverage::CollectScriptCoverage(script, true);
138 script->destroyScriptCounts();
142 JSRuntime* rt = gcx->runtime();
143 if (rt->hasJitRuntime() && rt->jitRuntime()->hasInterpreterEntryMap()) {
144 rt->jitRuntime()->getInterpreterEntryMap()->remove(this);
147 rt->geckoProfiler().onScriptFinalized(this);
150 #ifdef MOZ_VTUNE
151 if (zone()->scriptVTuneIdMap) {
152 // Note: we should only get here if the VTune JIT profiler is running.
153 zone()->scriptVTuneIdMap->remove(this);
155 #endif
157 if (warmUpData_.isJitScript()) {
158 JSScript* script = this->asJSScript();
159 #ifdef JS_CACHEIR_SPEW
160 maybeUpdateWarmUpCount(script);
161 #endif
162 script->releaseJitScriptOnFinalize(gcx);
165 #ifdef JS_CACHEIR_SPEW
166 if (hasBytecode()) {
167 maybeSpewScriptFinalWarmUpCount(this->asJSScript());
169 #endif
171 if (data_) {
172 // We don't need to triger any barriers here, just free the memory.
173 size_t size = data_->allocationSize();
174 AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size,
175 MemCheckKind::MakeNoAccess);
176 gcx->free_(this, data_, size, MemoryUse::ScriptPrivateData);
179 freeSharedData();
182 js::Scope* js::BaseScript::releaseEnclosingScope() {
183 Scope* enclosing = warmUpData_.toEnclosingScope();
184 warmUpData_.clearEnclosingScope();
185 return enclosing;
188 void js::BaseScript::swapData(UniquePtr<PrivateScriptData>& other) {
189 if (data_) {
190 RemoveCellMemory(this, data_->allocationSize(),
191 MemoryUse::ScriptPrivateData);
194 PrivateScriptData* old = data_;
195 data_.set(zone(), other.release());
196 other.reset(old);
198 if (data_) {
199 AddCellMemory(this, data_->allocationSize(), MemoryUse::ScriptPrivateData);
203 js::Scope* js::BaseScript::enclosingScope() const {
204 MOZ_ASSERT(!warmUpData_.isEnclosingScript(),
205 "Enclosing scope is not computed yet");
207 if (warmUpData_.isEnclosingScope()) {
208 return warmUpData_.toEnclosingScope();
211 MOZ_ASSERT(data_, "Script doesn't seem to be compiled");
213 return gcthings()[js::GCThingIndex::outermostScopeIndex()]
214 .as<Scope>()
215 .enclosing();
218 size_t JSScript::numAlwaysLiveFixedSlots() const {
219 if (bodyScope()->is<js::FunctionScope>()) {
220 return bodyScope()->as<js::FunctionScope>().nextFrameSlot();
222 if (bodyScope()->is<js::ModuleScope>()) {
223 return bodyScope()->as<js::ModuleScope>().nextFrameSlot();
225 if (bodyScope()->is<js::EvalScope>() &&
226 bodyScope()->kind() == ScopeKind::StrictEval) {
227 return bodyScope()->as<js::EvalScope>().nextFrameSlot();
229 return 0;
232 unsigned JSScript::numArgs() const {
233 if (bodyScope()->is<js::FunctionScope>()) {
234 return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters();
236 return 0;
239 bool JSScript::functionHasParameterExprs() const {
240 // Only functions have parameters.
241 js::Scope* scope = bodyScope();
242 if (!scope->is<js::FunctionScope>()) {
243 return false;
245 return scope->as<js::FunctionScope>().hasParameterExprs();
248 bool JSScript::isModule() const { return bodyScope()->is<js::ModuleScope>(); }
250 js::ModuleObject* JSScript::module() const {
251 MOZ_ASSERT(isModule());
252 return bodyScope()->as<js::ModuleScope>().module();
255 bool JSScript::isGlobalCode() const {
256 return bodyScope()->is<js::GlobalScope>();
259 js::VarScope* JSScript::functionExtraBodyVarScope() const {
260 MOZ_ASSERT(functionHasExtraBodyVarScope());
261 for (JS::GCCellPtr gcThing : gcthings()) {
262 if (!gcThing.is<js::Scope>()) {
263 continue;
265 js::Scope* scope = &gcThing.as<js::Scope>();
266 if (scope->kind() == js::ScopeKind::FunctionBodyVar) {
267 return &scope->as<js::VarScope>();
270 MOZ_CRASH("Function extra body var scope not found");
273 bool JSScript::needsBodyEnvironment() const {
274 for (JS::GCCellPtr gcThing : gcthings()) {
275 if (!gcThing.is<js::Scope>()) {
276 continue;
278 js::Scope* scope = &gcThing.as<js::Scope>();
279 if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) {
280 return true;
283 return false;
286 bool JSScript::isDirectEvalInFunction() const {
287 if (!isForEval()) {
288 return false;
290 return bodyScope()->hasOnChain(js::ScopeKind::Function);
293 // Initialize the optional arrays in the trailing allocation. This is a set of
294 // offsets that delimit each optional array followed by the arrays themselves.
295 // See comment before 'ImmutableScriptData' for more details.
296 void ImmutableScriptData::initOptionalArrays(Offset* pcursor,
297 uint32_t numResumeOffsets,
298 uint32_t numScopeNotes,
299 uint32_t numTryNotes) {
300 Offset cursor = (*pcursor);
302 // The byte arrays must have already been padded.
303 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor),
304 "Bytecode and source notes should be padded to keep alignment");
306 // Each non-empty optional array needs will need an offset to its end.
307 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
308 unsigned(numScopeNotes > 0) +
309 unsigned(numTryNotes > 0);
311 // Default-initialize the optional-offsets.
312 initElements<Offset>(cursor, numOptionalArrays);
313 cursor += numOptionalArrays * sizeof(Offset);
315 // Offset between optional-offsets table and the optional arrays. This is
316 // later used to access the optional-offsets table as well as first optional
317 // array.
318 optArrayOffset_ = cursor;
320 // Each optional array that follows must store an end-offset in the offset
321 // table. Assign table entries by using this 'offsetIndex'. The index 0 is
322 // reserved for implicit value 'optArrayOffset'.
323 int offsetIndex = 0;
325 // Default-initialize optional 'resumeOffsets'.
326 MOZ_ASSERT(resumeOffsetsOffset() == cursor);
327 if (numResumeOffsets > 0) {
328 initElements<uint32_t>(cursor, numResumeOffsets);
329 cursor += numResumeOffsets * sizeof(uint32_t);
330 setOptionalOffset(++offsetIndex, cursor);
332 flagsRef().resumeOffsetsEndIndex = offsetIndex;
334 // Default-initialize optional 'scopeNotes'.
335 MOZ_ASSERT(scopeNotesOffset() == cursor);
336 if (numScopeNotes > 0) {
337 initElements<ScopeNote>(cursor, numScopeNotes);
338 cursor += numScopeNotes * sizeof(ScopeNote);
339 setOptionalOffset(++offsetIndex, cursor);
341 flagsRef().scopeNotesEndIndex = offsetIndex;
343 // Default-initialize optional 'tryNotes'
344 MOZ_ASSERT(tryNotesOffset() == cursor);
345 if (numTryNotes > 0) {
346 initElements<TryNote>(cursor, numTryNotes);
347 cursor += numTryNotes * sizeof(TryNote);
348 setOptionalOffset(++offsetIndex, cursor);
350 flagsRef().tryNotesEndIndex = offsetIndex;
352 MOZ_ASSERT(endOffset() == cursor);
353 (*pcursor) = cursor;
356 ImmutableScriptData::ImmutableScriptData(uint32_t codeLength,
357 uint32_t noteLength,
358 uint32_t numResumeOffsets,
359 uint32_t numScopeNotes,
360 uint32_t numTryNotes)
361 : codeLength_(codeLength) {
362 // Variable-length data begins immediately after ImmutableScriptData itself.
363 Offset cursor = sizeof(ImmutableScriptData);
365 // The following arrays are byte-aligned with additional padding to ensure
366 // that together they maintain uint32_t-alignment.
368 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
370 // Zero-initialize 'flags'
371 MOZ_ASSERT(isAlignedOffset<Flags>(cursor));
372 new (offsetToPointer<void>(cursor)) Flags{};
373 cursor += sizeof(Flags);
375 initElements<jsbytecode>(cursor, codeLength);
376 cursor += codeLength * sizeof(jsbytecode);
378 initElements<SrcNote>(cursor, noteLength);
379 cursor += noteLength * sizeof(SrcNote);
381 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
384 // Initialization for remaining arrays.
385 initOptionalArrays(&cursor, numResumeOffsets, numScopeNotes, numTryNotes);
387 // Check that we correctly recompute the expected values.
388 MOZ_ASSERT(this->codeLength() == codeLength);
389 MOZ_ASSERT(this->noteLength() == noteLength);
391 // Sanity check
392 MOZ_ASSERT(endOffset() == cursor);
395 void js::FillImmutableFlagsFromCompileOptionsForTopLevel(
396 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
397 using ImmutableFlags = ImmutableScriptFlagsEnum;
399 js::FillImmutableFlagsFromCompileOptionsForFunction(options, flags);
401 flags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
402 flags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
405 void js::FillImmutableFlagsFromCompileOptionsForFunction(
406 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
407 using ImmutableFlags = ImmutableScriptFlagsEnum;
409 flags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
410 flags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode());
411 flags.setFlag(ImmutableFlags::HasNonSyntacticScope,
412 options.nonSyntacticScope);
415 // Check if flags matches to compile options for flags set by
416 // FillImmutableFlagsFromCompileOptionsForTopLevel above.
417 bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions& options,
418 ImmutableScriptFlags flags) {
419 using ImmutableFlags = ImmutableScriptFlagsEnum;
421 bool selfHosted = !!(flags & uint32_t(ImmutableFlags::SelfHosted));
422 bool forceStrict = !!(flags & uint32_t(ImmutableFlags::ForceStrict));
423 bool hasNonSyntacticScope =
424 !!(flags & uint32_t(ImmutableFlags::HasNonSyntacticScope));
425 bool noScriptRval = !!(flags & uint32_t(ImmutableFlags::NoScriptRval));
426 bool treatAsRunOnce = !!(flags & uint32_t(ImmutableFlags::TreatAsRunOnce));
428 return options.selfHostingMode == selfHosted &&
429 options.noScriptRval == noScriptRval &&
430 options.isRunOnce == treatAsRunOnce &&
431 options.forceStrictMode() == forceStrict &&
432 options.nonSyntacticScope == hasNonSyntacticScope;
435 JS_PUBLIC_API bool JS::CheckCompileOptionsMatch(
436 const ReadOnlyCompileOptions& options, JSScript* script) {
437 return js::CheckCompileOptionsMatch(options, script->immutableFlags());
440 bool JSScript::initScriptCounts(JSContext* cx) {
441 MOZ_ASSERT(!hasScriptCounts());
443 // Record all pc which are the first instruction of a basic block.
444 mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets;
446 js::BytecodeLocation main = mainLocation();
447 AllBytecodesIterable iterable(this);
448 for (auto& loc : iterable) {
449 if (loc.isJumpTarget() || loc == main) {
450 if (!jumpTargets.append(loc.toRawBytecode())) {
451 ReportOutOfMemory(cx);
452 return false;
457 // Initialize all PCCounts counters to 0.
458 ScriptCounts::PCCountsVector base;
459 if (!base.reserve(jumpTargets.length())) {
460 ReportOutOfMemory(cx);
461 return false;
464 for (size_t i = 0; i < jumpTargets.length(); i++) {
465 base.infallibleEmplaceBack(pcToOffset(jumpTargets[i]));
468 // Create zone's scriptCountsMap if necessary.
469 if (!zone()->scriptCountsMap) {
470 auto map = cx->make_unique<ScriptCountsMap>();
471 if (!map) {
472 return false;
475 zone()->scriptCountsMap = std::move(map);
478 // Allocate the ScriptCounts.
479 UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base));
480 if (!sc) {
481 return false;
484 MOZ_ASSERT(this->hasBytecode());
486 // Register the current ScriptCounts in the zone's map.
487 if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) {
488 ReportOutOfMemory(cx);
489 return false;
492 // safe to set this; we can't fail after this point.
493 setHasScriptCounts();
495 // Enable interrupts in any interpreter frames running on this script. This
496 // is used to let the interpreter increment the PCCounts, if present.
497 for (ActivationIterator iter(cx); !iter.done(); ++iter) {
498 if (iter->isInterpreter()) {
499 iter->asInterpreter()->enableInterruptsIfRunning(this);
503 return true;
506 static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) {
507 MOZ_ASSERT(script->hasScriptCounts());
508 ScriptCountsMap::Ptr p = script->zone()->scriptCountsMap->lookup(script);
509 MOZ_ASSERT(p);
510 return p;
513 ScriptCounts& JSScript::getScriptCounts() {
514 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
515 return *p->value();
518 js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) {
519 PCCounts searched = PCCounts(offset);
520 PCCounts* elem =
521 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
522 if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
523 return nullptr;
525 return elem;
528 const js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) const {
529 PCCounts searched = PCCounts(offset);
530 const PCCounts* elem =
531 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
532 if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
533 return nullptr;
535 return elem;
538 js::PCCounts* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset) {
539 PCCounts searched = PCCounts(offset);
540 PCCounts* elem =
541 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
542 if (elem == pcCounts_.end()) {
543 return &pcCounts_.back();
545 if (elem->pcOffset() == offset) {
546 return elem;
548 if (elem != pcCounts_.begin()) {
549 return elem - 1;
551 return nullptr;
554 const js::PCCounts* ScriptCounts::maybeGetThrowCounts(size_t offset) const {
555 PCCounts searched = PCCounts(offset);
556 const PCCounts* elem =
557 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
558 if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
559 return nullptr;
561 return elem;
564 const js::PCCounts* ScriptCounts::getImmediatePrecedingThrowCounts(
565 size_t offset) const {
566 PCCounts searched = PCCounts(offset);
567 const PCCounts* elem =
568 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
569 if (elem == throwCounts_.end()) {
570 if (throwCounts_.begin() == throwCounts_.end()) {
571 return nullptr;
573 return &throwCounts_.back();
575 if (elem->pcOffset() == offset) {
576 return elem;
578 if (elem != throwCounts_.begin()) {
579 return elem - 1;
581 return nullptr;
584 js::PCCounts* ScriptCounts::getThrowCounts(size_t offset) {
585 PCCounts searched = PCCounts(offset);
586 PCCounts* elem =
587 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
588 if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
589 elem = throwCounts_.insert(elem, searched);
591 return elem;
594 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
595 size_t size = mallocSizeOf(this);
596 size += pcCounts_.sizeOfExcludingThis(mallocSizeOf);
597 size += throwCounts_.sizeOfExcludingThis(mallocSizeOf);
598 if (ionCounts_) {
599 size += ionCounts_->sizeOfIncludingThis(mallocSizeOf);
601 return size;
604 js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
605 MOZ_ASSERT(containsPC(pc));
606 return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
609 const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
610 MOZ_ASSERT(containsPC(pc));
611 return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc));
614 js::PCCounts* JSScript::getThrowCounts(jsbytecode* pc) {
615 MOZ_ASSERT(containsPC(pc));
616 return getScriptCounts().getThrowCounts(pcToOffset(pc));
619 uint64_t JSScript::getHitCount(jsbytecode* pc) {
620 MOZ_ASSERT(containsPC(pc));
621 if (pc < main()) {
622 pc = main();
625 ScriptCounts& sc = getScriptCounts();
626 size_t targetOffset = pcToOffset(pc);
627 const js::PCCounts* baseCount =
628 sc.getImmediatePrecedingPCCounts(targetOffset);
629 if (!baseCount) {
630 return 0;
632 if (baseCount->pcOffset() == targetOffset) {
633 return baseCount->numExec();
635 MOZ_ASSERT(baseCount->pcOffset() < targetOffset);
636 uint64_t count = baseCount->numExec();
637 do {
638 const js::PCCounts* throwCount =
639 sc.getImmediatePrecedingThrowCounts(targetOffset);
640 if (!throwCount) {
641 return count;
643 if (throwCount->pcOffset() <= baseCount->pcOffset()) {
644 return count;
646 count -= throwCount->numExec();
647 targetOffset = throwCount->pcOffset() - 1;
648 } while (true);
651 void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) {
652 ScriptCounts& sc = getScriptCounts();
653 if (sc.ionCounts_) {
654 ionCounts->setPrevious(sc.ionCounts_);
656 sc.ionCounts_ = ionCounts;
659 jit::IonScriptCounts* JSScript::getIonCounts() {
660 return getScriptCounts().ionCounts_;
663 void JSScript::releaseScriptCounts(ScriptCounts* counts) {
664 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
665 *counts = std::move(*p->value().get());
666 zone()->scriptCountsMap->remove(p);
667 clearHasScriptCounts();
670 void JSScript::destroyScriptCounts() {
671 if (hasScriptCounts()) {
672 ScriptCounts scriptCounts;
673 releaseScriptCounts(&scriptCounts);
677 void JSScript::resetScriptCounts() {
678 if (!hasScriptCounts()) {
679 return;
682 ScriptCounts& sc = getScriptCounts();
684 for (PCCounts& elem : sc.pcCounts_) {
685 elem.numExec() = 0;
688 for (PCCounts& elem : sc.throwCounts_) {
689 elem.numExec() = 0;
693 void ScriptSourceObject::finalize(JS::GCContext* gcx, JSObject* obj) {
694 MOZ_ASSERT(gcx->onMainThread());
695 ScriptSourceObject* sso = &obj->as<ScriptSourceObject>();
696 sso->source()->Release();
698 // Clear the private value, calling the release hook if necessary.
699 sso->setPrivate(gcx->runtime(), UndefinedValue());
702 static const JSClassOps ScriptSourceObjectClassOps = {
703 nullptr, // addProperty
704 nullptr, // delProperty
705 nullptr, // enumerate
706 nullptr, // newEnumerate
707 nullptr, // resolve
708 nullptr, // mayResolve
709 ScriptSourceObject::finalize, // finalize
710 nullptr, // call
711 nullptr, // construct
712 nullptr, // trace
715 const JSClass ScriptSourceObject::class_ = {
716 "ScriptSource",
717 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
718 &ScriptSourceObjectClassOps};
720 ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
721 ScriptSource* source) {
722 ScriptSourceObject* obj =
723 NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr);
724 if (!obj) {
725 return nullptr;
728 // The matching decref is in ScriptSourceObject::finalize.
729 obj->initReservedSlot(SOURCE_SLOT, PrivateValue(do_AddRef(source).take()));
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));
736 return obj;
739 [[nodiscard]] static bool MaybeValidateFilename(
740 JSContext* cx, Handle<ScriptSourceObject*> sso,
741 const JS::InstantiateOptions& options) {
742 if (!gFilenameValidationCallback) {
743 return true;
746 const char* filename = sso->source()->filename();
747 if (!filename || options.skipFilenameValidation) {
748 return true;
751 if (gFilenameValidationCallback(cx, filename)) {
752 return true;
755 const char* utf8Filename;
756 if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename))) {
757 utf8Filename = filename;
758 } else {
759 utf8Filename = "(invalid UTF-8 filename)";
761 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME,
762 utf8Filename);
763 return false;
766 /* static */
767 bool ScriptSourceObject::initFromOptions(
768 JSContext* cx, Handle<ScriptSourceObject*> source,
769 const JS::InstantiateOptions& options) {
770 cx->releaseCheck(source);
771 MOZ_ASSERT(
772 source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC));
773 MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT)
774 .isMagic(JS_GENERIC_MAGIC));
776 if (!MaybeValidateFilename(cx, source, options)) {
777 return false;
780 if (options.deferDebugMetadata) {
781 return true;
784 // Initialize the element attribute slot and introduction script slot
785 // this marks the SSO as initialized for asserts.
787 RootedString elementAttributeName(cx);
788 if (!initElementProperties(cx, source, elementAttributeName)) {
789 return false;
792 RootedValue introductionScript(cx);
793 source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
795 return true;
798 /* static */
799 bool ScriptSourceObject::initElementProperties(
800 JSContext* cx, Handle<ScriptSourceObject*> source,
801 HandleString elementAttrName) {
802 RootedValue nameValue(cx);
803 if (elementAttrName) {
804 nameValue = StringValue(elementAttrName);
806 if (!cx->compartment()->wrap(cx, &nameValue)) {
807 return false;
810 source->setReservedSlot(ELEMENT_PROPERTY_SLOT, nameValue);
812 return true;
815 void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) {
816 // Update the private value, calling addRef/release hooks if necessary
817 // to allow the embedding to maintain a reference count for the
818 // private data.
819 JS::AutoSuppressGCAnalysis nogc;
820 Value prevValue = getReservedSlot(PRIVATE_SLOT);
821 rt->releaseScriptPrivate(prevValue);
822 setReservedSlot(PRIVATE_SLOT, value);
823 rt->addRefScriptPrivate(value);
826 void ScriptSourceObject::clearPrivate(JSRuntime* rt) {
827 // Clear the private value, calling release hook if necessary.
828 // |this| may be gray, be careful not to create edges to it.
829 JS::AutoSuppressGCAnalysis nogc;
830 Value prevValue = getReservedSlot(PRIVATE_SLOT);
831 rt->releaseScriptPrivate(prevValue);
832 getSlotRef(PRIVATE_SLOT).setUndefinedUnchecked();
835 class ScriptSource::LoadSourceMatcher {
836 JSContext* const cx_;
837 ScriptSource* const ss_;
838 bool* const loaded_;
840 public:
841 explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded)
842 : cx_(cx), ss_(ss), loaded_(loaded) {}
844 template <typename Unit, SourceRetrievable CanRetrieve>
845 bool operator()(const Compressed<Unit, CanRetrieve>&) const {
846 *loaded_ = true;
847 return true;
850 template <typename Unit, SourceRetrievable CanRetrieve>
851 bool operator()(const Uncompressed<Unit, CanRetrieve>&) const {
852 *loaded_ = true;
853 return true;
856 template <typename Unit>
857 bool operator()(const Retrievable<Unit>&) {
858 if (!cx_->runtime()->sourceHook.ref()) {
859 *loaded_ = false;
860 return true;
863 size_t length;
865 // The first argument is just for overloading -- its value doesn't matter.
866 if (!tryLoadAndSetSource(Unit('0'), &length)) {
867 return false;
870 return true;
873 bool operator()(const Missing&) const {
874 *loaded_ = false;
875 return true;
878 private:
879 bool tryLoadAndSetSource(const Utf8Unit&, size_t* length) const {
880 char* utf8Source;
881 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr,
882 &utf8Source, length)) {
883 return false;
886 if (!utf8Source) {
887 *loaded_ = false;
888 return true;
891 if (!ss_->setRetrievedSource(
892 cx_, EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)),
893 *length)) {
894 return false;
897 *loaded_ = true;
898 return true;
901 bool tryLoadAndSetSource(const char16_t&, size_t* length) const {
902 char16_t* utf16Source;
903 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source,
904 nullptr, length)) {
905 return false;
908 if (!utf16Source) {
909 *loaded_ = false;
910 return true;
913 if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source),
914 *length)) {
915 return false;
918 *loaded_ = true;
919 return true;
923 /* static */
924 bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) {
925 return ss->data.match(LoadSourceMatcher(cx, ss, loaded));
928 /* static */
929 JSLinearString* JSScript::sourceData(JSContext* cx, HandleScript script) {
930 MOZ_ASSERT(script->scriptSource()->hasSourceText());
931 return script->scriptSource()->substring(cx, script->sourceStart(),
932 script->sourceEnd());
935 bool BaseScript::appendSourceDataForToString(JSContext* cx, StringBuffer& buf) {
936 MOZ_ASSERT(scriptSource()->hasSourceText());
937 return scriptSource()->appendSubstring(cx, buf, toStringStart(),
938 toStringEnd());
941 void UncompressedSourceCache::holdEntry(AutoHoldEntry& holder,
942 const ScriptSourceChunk& ssc) {
943 MOZ_ASSERT(!holder_);
944 holder.holdEntry(this, ssc);
945 holder_ = &holder;
948 void UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder) {
949 MOZ_ASSERT(holder_ == &holder);
950 holder_ = nullptr;
953 template <typename Unit>
954 const Unit* UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc,
955 AutoHoldEntry& holder) {
956 MOZ_ASSERT(!holder_);
957 MOZ_ASSERT(ssc.ss->isCompressed<Unit>());
959 if (!map_) {
960 return nullptr;
963 if (Map::Ptr p = map_->lookup(ssc)) {
964 holdEntry(holder, ssc);
965 return static_cast<const Unit*>(p->value().get());
968 return nullptr;
971 bool UncompressedSourceCache::put(const ScriptSourceChunk& ssc, SourceData data,
972 AutoHoldEntry& holder) {
973 MOZ_ASSERT(!holder_);
975 if (!map_) {
976 map_ = MakeUnique<Map>();
977 if (!map_) {
978 return false;
982 if (!map_->put(ssc, std::move(data))) {
983 return false;
986 holdEntry(holder, ssc);
987 return true;
990 void UncompressedSourceCache::purge() {
991 if (!map_) {
992 return;
995 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
996 if (holder_ && r.front().key() == holder_->sourceChunk()) {
997 holder_->deferDelete(std::move(r.front().value()));
998 holder_ = nullptr;
1002 map_ = nullptr;
1005 size_t UncompressedSourceCache::sizeOfExcludingThis(
1006 mozilla::MallocSizeOf mallocSizeOf) {
1007 size_t n = 0;
1008 if (map_ && !map_->empty()) {
1009 n += map_->shallowSizeOfIncludingThis(mallocSizeOf);
1010 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
1011 n += mallocSizeOf(r.front().value().get());
1014 return n;
1017 template <typename Unit>
1018 const Unit* ScriptSource::chunkUnits(
1019 JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
1020 size_t chunk) {
1021 const CompressedData<Unit>& c = *compressedData<Unit>();
1023 ScriptSourceChunk ssc(this, chunk);
1024 if (const Unit* decompressed =
1025 cx->caches().uncompressedSourceCache.lookup<Unit>(ssc, holder)) {
1026 return decompressed;
1029 size_t totalLengthInBytes = length() * sizeof(Unit);
1030 size_t chunkBytes = Compressor::chunkSize(totalLengthInBytes, chunk);
1032 MOZ_ASSERT((chunkBytes % sizeof(Unit)) == 0);
1033 const size_t chunkLength = chunkBytes / sizeof(Unit);
1034 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(chunkLength));
1035 if (!decompressed) {
1036 JS_ReportOutOfMemory(cx);
1037 return nullptr;
1040 // Compression treats input and output memory as plain ol' bytes. These
1041 // reinterpret_cast<>s accord exactly with that.
1042 if (!DecompressStringChunk(
1043 reinterpret_cast<const unsigned char*>(c.raw.chars()), chunk,
1044 reinterpret_cast<unsigned char*>(decompressed.get()), chunkBytes)) {
1045 JS_ReportOutOfMemory(cx);
1046 return nullptr;
1049 const Unit* ret = decompressed.get();
1050 if (!cx->caches().uncompressedSourceCache.put(
1051 ssc, ToSourceData(std::move(decompressed)), holder)) {
1052 JS_ReportOutOfMemory(cx);
1053 return nullptr;
1055 return ret;
1058 template <typename Unit>
1059 void ScriptSource::convertToCompressedSource(SharedImmutableString compressed,
1060 size_t uncompressedLength) {
1061 MOZ_ASSERT(isUncompressed<Unit>());
1062 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
1064 if (data.is<Uncompressed<Unit, SourceRetrievable::Yes>>()) {
1065 data = SourceType(Compressed<Unit, SourceRetrievable::Yes>(
1066 std::move(compressed), uncompressedLength));
1067 } else {
1068 data = SourceType(Compressed<Unit, SourceRetrievable::No>(
1069 std::move(compressed), uncompressedLength));
1073 template <typename Unit>
1074 void ScriptSource::performDelayedConvertToCompressedSource(
1075 ExclusiveData<ReaderInstances>::Guard& g) {
1076 // There might not be a conversion to compressed source happening at all.
1077 if (g->pendingCompressed.empty()) {
1078 return;
1081 CompressedData<Unit>& pending =
1082 g->pendingCompressed.ref<CompressedData<Unit>>();
1084 convertToCompressedSource<Unit>(std::move(pending.raw),
1085 pending.uncompressedLength);
1087 g->pendingCompressed.destroy();
1090 void ScriptSource::PinnedUnitsBase::addReader() {
1091 auto guard = source_->readers_.lock();
1092 guard->count++;
1095 template <typename Unit>
1096 void ScriptSource::PinnedUnitsBase::removeReader() {
1097 // Note: We use a Mutex with Exclusive access, such that no PinnedUnits
1098 // instance is live while we are compressing the source.
1099 auto guard = source_->readers_.lock();
1100 MOZ_ASSERT(guard->count > 0);
1101 if (--guard->count) {
1102 source_->performDelayedConvertToCompressedSource<Unit>(guard);
1106 template <typename Unit>
1107 ScriptSource::PinnedUnits<Unit>::~PinnedUnits() {
1108 if (units_) {
1109 removeReader<Unit>();
1113 template <typename Unit>
1114 ScriptSource::PinnedUnitsIfUncompressed<Unit>::~PinnedUnitsIfUncompressed() {
1115 if (units_) {
1116 removeReader<Unit>();
1120 template <typename Unit>
1121 const Unit* ScriptSource::units(JSContext* cx,
1122 UncompressedSourceCache::AutoHoldEntry& holder,
1123 size_t begin, size_t len) {
1124 MOZ_ASSERT(begin <= length());
1125 MOZ_ASSERT(begin + len <= length());
1127 if (isUncompressed<Unit>()) {
1128 const Unit* units = uncompressedData<Unit>()->units();
1129 if (!units) {
1130 return nullptr;
1132 return units + begin;
1135 if (data.is<Missing>()) {
1136 MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source");
1139 if (data.is<Retrievable<Unit>>()) {
1140 MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
1143 MOZ_ASSERT(isCompressed<Unit>());
1145 // Determine first/last chunks, the offset (in bytes) into the first chunk
1146 // of the requested units, and the number of bytes in the last chunk.
1148 // Note that first and last chunk sizes are miscomputed and *must not be
1149 // used* when the first chunk is the last chunk.
1150 size_t firstChunk, firstChunkOffset, firstChunkSize;
1151 size_t lastChunk, lastChunkSize;
1152 Compressor::rangeToChunkAndOffset(
1153 begin * sizeof(Unit), (begin + len) * sizeof(Unit), &firstChunk,
1154 &firstChunkOffset, &firstChunkSize, &lastChunk, &lastChunkSize);
1155 MOZ_ASSERT(firstChunk <= lastChunk);
1156 MOZ_ASSERT(firstChunkOffset % sizeof(Unit) == 0);
1157 MOZ_ASSERT(firstChunkSize % sizeof(Unit) == 0);
1159 size_t firstUnit = firstChunkOffset / sizeof(Unit);
1161 // Directly return units within a single chunk. UncompressedSourceCache
1162 // and |holder| will hold the units alive past function return.
1163 if (firstChunk == lastChunk) {
1164 const Unit* units = chunkUnits<Unit>(cx, holder, firstChunk);
1165 if (!units) {
1166 return nullptr;
1169 return units + firstUnit;
1172 // Otherwise the units span multiple chunks. Copy successive chunks'
1173 // decompressed units into freshly-allocated memory to return.
1174 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(len));
1175 if (!decompressed) {
1176 JS_ReportOutOfMemory(cx);
1177 return nullptr;
1180 Unit* cursor;
1183 // |AutoHoldEntry| is single-shot, and a holder successfully filled in
1184 // by |chunkUnits| must be destroyed before another can be used. Thus
1185 // we can't use |holder| with |chunkUnits| when |chunkUnits| is used
1186 // with multiple chunks, and we must use and destroy distinct, fresh
1187 // holders for each chunk.
1188 UncompressedSourceCache::AutoHoldEntry firstHolder;
1189 const Unit* units = chunkUnits<Unit>(cx, firstHolder, firstChunk);
1190 if (!units) {
1191 return nullptr;
1194 cursor = std::copy_n(units + firstUnit, firstChunkSize / sizeof(Unit),
1195 decompressed.get());
1198 for (size_t i = firstChunk + 1; i < lastChunk; i++) {
1199 UncompressedSourceCache::AutoHoldEntry chunkHolder;
1200 const Unit* units = chunkUnits<Unit>(cx, chunkHolder, i);
1201 if (!units) {
1202 return nullptr;
1205 cursor = std::copy_n(units, Compressor::CHUNK_SIZE / sizeof(Unit), cursor);
1209 UncompressedSourceCache::AutoHoldEntry lastHolder;
1210 const Unit* units = chunkUnits<Unit>(cx, lastHolder, lastChunk);
1211 if (!units) {
1212 return nullptr;
1215 cursor = std::copy_n(units, lastChunkSize / sizeof(Unit), cursor);
1218 MOZ_ASSERT(PointerRangeSize(decompressed.get(), cursor) == len);
1220 // Transfer ownership to |holder|.
1221 const Unit* ret = decompressed.get();
1222 holder.holdUnits(std::move(decompressed));
1223 return ret;
1226 template <typename Unit>
1227 const Unit* ScriptSource::uncompressedUnits(size_t begin, size_t len) {
1228 MOZ_ASSERT(begin <= length());
1229 MOZ_ASSERT(begin + len <= length());
1231 if (!isUncompressed<Unit>()) {
1232 return nullptr;
1235 const Unit* units = uncompressedData<Unit>()->units();
1236 if (!units) {
1237 return nullptr;
1239 return units + begin;
1242 template <typename Unit>
1243 ScriptSource::PinnedUnits<Unit>::PinnedUnits(
1244 JSContext* cx, ScriptSource* source,
1245 UncompressedSourceCache::AutoHoldEntry& holder, size_t begin, size_t len)
1246 : PinnedUnitsBase(source) {
1247 MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type");
1249 units_ = source->units<Unit>(cx, holder, begin, len);
1250 if (units_) {
1251 addReader();
1255 template class ScriptSource::PinnedUnits<Utf8Unit>;
1256 template class ScriptSource::PinnedUnits<char16_t>;
1258 template <typename Unit>
1259 ScriptSource::PinnedUnitsIfUncompressed<Unit>::PinnedUnitsIfUncompressed(
1260 ScriptSource* source, size_t begin, size_t len)
1261 : PinnedUnitsBase(source) {
1262 MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type");
1264 units_ = source->uncompressedUnits<Unit>(begin, len);
1265 if (units_) {
1266 addReader();
1270 template class ScriptSource::PinnedUnitsIfUncompressed<Utf8Unit>;
1271 template class ScriptSource::PinnedUnitsIfUncompressed<char16_t>;
1273 JSLinearString* ScriptSource::substring(JSContext* cx, size_t start,
1274 size_t stop) {
1275 MOZ_ASSERT(start <= stop);
1277 size_t len = stop - start;
1278 if (!len) {
1279 return cx->emptyString();
1281 UncompressedSourceCache::AutoHoldEntry holder;
1283 // UTF-8 source text.
1284 if (hasSourceType<Utf8Unit>()) {
1285 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
1286 if (!units.asChars()) {
1287 return nullptr;
1290 const char* str = units.asChars();
1291 return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len));
1294 // UTF-16 source text.
1295 PinnedUnits<char16_t> units(cx, this, holder, start, len);
1296 if (!units.asChars()) {
1297 return nullptr;
1300 return NewStringCopyN<CanGC>(cx, units.asChars(), len);
1303 JSLinearString* ScriptSource::substringDontDeflate(JSContext* cx, size_t start,
1304 size_t stop) {
1305 MOZ_ASSERT(start <= stop);
1307 size_t len = stop - start;
1308 if (!len) {
1309 return cx->emptyString();
1311 UncompressedSourceCache::AutoHoldEntry holder;
1313 // UTF-8 source text.
1314 if (hasSourceType<Utf8Unit>()) {
1315 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
1316 if (!units.asChars()) {
1317 return nullptr;
1320 const char* str = units.asChars();
1322 // There doesn't appear to be a non-deflating UTF-8 string creation
1323 // function -- but then again, it's not entirely clear how current
1324 // callers benefit from non-deflation.
1325 return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len));
1328 // UTF-16 source text.
1329 PinnedUnits<char16_t> units(cx, this, holder, start, len);
1330 if (!units.asChars()) {
1331 return nullptr;
1334 return NewStringCopyNDontDeflate<CanGC>(cx, units.asChars(), len);
1337 bool ScriptSource::appendSubstring(JSContext* cx, StringBuffer& buf,
1338 size_t start, size_t stop) {
1339 MOZ_ASSERT(start <= stop);
1341 size_t len = stop - start;
1342 UncompressedSourceCache::AutoHoldEntry holder;
1344 if (hasSourceType<Utf8Unit>()) {
1345 PinnedUnits<Utf8Unit> pinned(cx, this, holder, start, len);
1346 if (!pinned.get()) {
1347 return false;
1349 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
1350 return false;
1353 const Utf8Unit* units = pinned.get();
1354 return buf.append(units, len);
1355 } else {
1356 PinnedUnits<char16_t> pinned(cx, this, holder, start, len);
1357 if (!pinned.get()) {
1358 return false;
1360 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
1361 return false;
1364 const char16_t* units = pinned.get();
1365 return buf.append(units, len);
1369 JSLinearString* ScriptSource::functionBodyString(JSContext* cx) {
1370 MOZ_ASSERT(isFunctionBody());
1372 size_t start = parameterListEnd_ + FunctionConstructorMedialSigils.length();
1373 size_t stop = length() - FunctionConstructorFinalBrace.length();
1374 return substring(cx, start, stop);
1377 template <typename ContextT, typename Unit>
1378 [[nodiscard]] bool ScriptSource::setUncompressedSourceHelper(
1379 ContextT* cx, EntryUnits<Unit>&& source, size_t length,
1380 SourceRetrievable retrievable) {
1381 auto& cache = SharedImmutableStringsCache::getSingleton();
1383 auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source));
1384 auto deduped = cache.getOrCreate(std::move(uniqueChars), length);
1385 if (!deduped) {
1386 ReportOutOfMemory(cx);
1387 return false;
1390 if (retrievable == SourceRetrievable::Yes) {
1391 data = SourceType(
1392 Uncompressed<Unit, SourceRetrievable::Yes>(std::move(deduped)));
1393 } else {
1394 data = SourceType(
1395 Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
1397 return true;
1400 template <typename Unit>
1401 [[nodiscard]] bool ScriptSource::setRetrievedSource(JSContext* cx,
1402 EntryUnits<Unit>&& source,
1403 size_t length) {
1404 MOZ_ASSERT(data.is<Retrievable<Unit>>(),
1405 "retrieved source can only overwrite the corresponding "
1406 "retrievable source");
1407 return setUncompressedSourceHelper(cx, std::move(source), length,
1408 SourceRetrievable::Yes);
1411 bool js::IsOffThreadSourceCompressionEnabled() {
1412 // If we don't have concurrent execution compression will contend with
1413 // main-thread execution, in which case we disable. Similarly we don't want to
1414 // block the thread pool if it is too small.
1415 return GetHelperThreadCPUCount() > 1 && GetHelperThreadCount() > 1 &&
1416 CanUseExtraThreads();
1419 bool ScriptSource::tryCompressOffThread(JSContext* cx) {
1420 // Beware: |js::SynchronouslyCompressSource| assumes that this function is
1421 // only called once, just after a script has been compiled, and it's never
1422 // called at some random time after that. If multiple calls of this can ever
1423 // occur, that function may require changes.
1425 // The SourceCompressionTask needs to record the major GC number for
1426 // scheduling.
1427 MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
1429 // If source compression was already attempted, do not queue a new task.
1430 if (hadCompressionTask_) {
1431 return true;
1434 if (!hasUncompressedSource()) {
1435 // This excludes compressed, missing, and retrievable source.
1436 return true;
1439 // There are several cases where source compression is not a good idea:
1440 // - If the script is tiny, then compression will save little or no space.
1441 // - If there is only one core, then compression will contend with JS
1442 // execution (which hurts benchmarketing).
1444 // Otherwise, enqueue a compression task to be processed when a major
1445 // GC is requested.
1447 if (length() < ScriptSource::MinimumCompressibleLength ||
1448 !IsOffThreadSourceCompressionEnabled()) {
1449 return true;
1452 // Heap allocate the task. It will be freed upon compression
1453 // completing in AttachFinishedCompressedSources.
1454 auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
1455 if (!task) {
1456 ReportOutOfMemory(cx);
1457 return false;
1459 return EnqueueOffThreadCompression(cx, std::move(task));
1462 template <typename Unit>
1463 void ScriptSource::triggerConvertToCompressedSource(
1464 SharedImmutableString compressed, size_t uncompressedLength) {
1465 MOZ_ASSERT(isUncompressed<Unit>(),
1466 "should only be triggering compressed source installation to "
1467 "overwrite identically-encoded uncompressed source");
1468 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
1470 // If units aren't pinned -- and they probably won't be, we'd have to have a
1471 // GC in the small window of time where a |PinnedUnits| was live -- then we
1472 // can immediately convert.
1474 auto guard = readers_.lock();
1475 if (MOZ_LIKELY(!guard->count)) {
1476 convertToCompressedSource<Unit>(std::move(compressed),
1477 uncompressedLength);
1478 return;
1481 // Otherwise, set aside the compressed-data info. The conversion is
1482 // performed when the last |PinnedUnits| dies.
1483 MOZ_ASSERT(guard->pendingCompressed.empty(),
1484 "shouldn't be multiple conversions happening");
1485 guard->pendingCompressed.construct<CompressedData<Unit>>(
1486 std::move(compressed), uncompressedLength);
1490 template <typename Unit>
1491 [[nodiscard]] bool ScriptSource::initializeWithUnretrievableCompressedSource(
1492 FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
1493 size_t sourceLength) {
1494 MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing");
1495 MOZ_ASSERT(compressed != nullptr);
1497 auto& cache = SharedImmutableStringsCache::getSingleton();
1498 auto deduped = cache.getOrCreate(std::move(compressed), rawLength);
1499 if (!deduped) {
1500 ReportOutOfMemory(fc);
1501 return false;
1504 #ifdef DEBUG
1506 auto guard = readers_.lock();
1507 MOZ_ASSERT(
1508 guard->count == 0,
1509 "shouldn't be initializing a ScriptSource while its characters "
1510 "are pinned -- that only makes sense with a ScriptSource actively "
1511 "being inspected");
1513 #endif
1515 data = SourceType(Compressed<Unit, SourceRetrievable::No>(std::move(deduped),
1516 sourceLength));
1518 return true;
1521 template bool ScriptSource::initializeWithUnretrievableCompressedSource<
1522 Utf8Unit>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
1523 size_t sourceLength);
1524 template bool ScriptSource::initializeWithUnretrievableCompressedSource<
1525 char16_t>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
1526 size_t sourceLength);
1528 template <typename Unit>
1529 bool ScriptSource::assignSource(FrontendContext* fc,
1530 const ReadOnlyCompileOptions& options,
1531 SourceText<Unit>& srcBuf) {
1532 MOZ_ASSERT(data.is<Missing>(),
1533 "source assignment should only occur on fresh ScriptSources");
1535 mutedErrors_ = options.mutedErrors();
1536 delazificationMode_ = options.eagerDelazificationStrategy();
1538 if (options.discardSource) {
1539 return true;
1542 if (options.sourceIsLazy) {
1543 data = SourceType(Retrievable<Unit>());
1544 return true;
1547 auto& cache = SharedImmutableStringsCache::getSingleton();
1548 auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() {
1549 using CharT = typename SourceTypeTraits<Unit>::CharT;
1550 return srcBuf.ownsUnits()
1551 ? UniquePtr<CharT[], JS::FreePolicy>(srcBuf.takeChars())
1552 : DuplicateString(srcBuf.get(), srcBuf.length());
1554 if (!deduped) {
1555 ReportOutOfMemory(fc);
1556 return false;
1559 data =
1560 SourceType(Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
1561 return true;
1564 template bool ScriptSource::assignSource(FrontendContext* fc,
1565 const ReadOnlyCompileOptions& options,
1566 SourceText<char16_t>& srcBuf);
1567 template bool ScriptSource::assignSource(FrontendContext* fc,
1568 const ReadOnlyCompileOptions& options,
1569 SourceText<Utf8Unit>& srcBuf);
1571 [[nodiscard]] static bool reallocUniquePtr(UniqueChars& unique, size_t size) {
1572 auto newPtr = static_cast<char*>(js_realloc(unique.get(), size));
1573 if (!newPtr) {
1574 return false;
1577 // Since the realloc succeeded, unique is now holding a freed pointer.
1578 (void)unique.release();
1579 unique.reset(newPtr);
1580 return true;
1583 template <typename Unit>
1584 void SourceCompressionTask::workEncodingSpecific() {
1585 MOZ_ASSERT(source_->isUncompressed<Unit>());
1587 // Try to keep the maximum memory usage down by only allocating half the
1588 // size of the string, first.
1589 size_t inputBytes = source_->length() * sizeof(Unit);
1590 size_t firstSize = inputBytes / 2;
1591 UniqueChars compressed(js_pod_malloc<char>(firstSize));
1592 if (!compressed) {
1593 return;
1596 const Unit* chars = source_->uncompressedData<Unit>()->units();
1597 Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes);
1598 if (!comp.init()) {
1599 return;
1602 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize);
1603 bool cont = true;
1604 bool reallocated = false;
1605 while (cont) {
1606 if (shouldCancel()) {
1607 return;
1610 switch (comp.compressMore()) {
1611 case Compressor::CONTINUE:
1612 break;
1613 case Compressor::MOREOUTPUT: {
1614 if (reallocated) {
1615 // The compressed string is longer than the original string.
1616 return;
1619 // The compressed output is greater than half the size of the
1620 // original string. Reallocate to the full size.
1621 if (!reallocUniquePtr(compressed, inputBytes)) {
1622 return;
1625 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()),
1626 inputBytes);
1627 reallocated = true;
1628 break;
1630 case Compressor::DONE:
1631 cont = false;
1632 break;
1633 case Compressor::OOM:
1634 return;
1638 size_t totalBytes = comp.totalBytesNeeded();
1640 // Shrink the buffer to the size of the compressed data.
1641 if (!reallocUniquePtr(compressed, totalBytes)) {
1642 return;
1645 comp.finish(compressed.get(), totalBytes);
1647 if (shouldCancel()) {
1648 return;
1651 auto& strings = SharedImmutableStringsCache::getSingleton();
1652 resultString_ = strings.getOrCreate(std::move(compressed), totalBytes);
1655 struct SourceCompressionTask::PerformTaskWork {
1656 SourceCompressionTask* const task_;
1658 explicit PerformTaskWork(SourceCompressionTask* task) : task_(task) {}
1660 template <typename Unit, SourceRetrievable CanRetrieve>
1661 void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) {
1662 task_->workEncodingSpecific<Unit>();
1665 template <typename T>
1666 void operator()(const T&) {
1667 MOZ_CRASH(
1668 "why are we compressing missing, missing-but-retrievable, "
1669 "or already-compressed source?");
1673 void ScriptSource::performTaskWork(SourceCompressionTask* task) {
1674 MOZ_ASSERT(hasUncompressedSource());
1675 data.match(SourceCompressionTask::PerformTaskWork(task));
1678 void SourceCompressionTask::runTask() {
1679 if (shouldCancel()) {
1680 return;
1683 MOZ_ASSERT(source_->hasUncompressedSource());
1685 source_->performTaskWork(this);
1688 void SourceCompressionTask::runHelperThreadTask(
1689 AutoLockHelperThreadState& locked) {
1691 AutoUnlockHelperThreadState unlock(locked);
1692 this->runTask();
1696 AutoEnterOOMUnsafeRegion oomUnsafe;
1697 if (!HelperThreadState().compressionFinishedList(locked).append(this)) {
1698 oomUnsafe.crash("SourceCompressionTask::runHelperThreadTask");
1703 void ScriptSource::triggerConvertToCompressedSourceFromTask(
1704 SharedImmutableString compressed) {
1705 data.match(TriggerConvertToCompressedSourceFromTask(this, compressed));
1708 void SourceCompressionTask::complete() {
1709 if (!shouldCancel() && resultString_) {
1710 source_->triggerConvertToCompressedSourceFromTask(std::move(resultString_));
1714 bool js::SynchronouslyCompressSource(JSContext* cx,
1715 JS::Handle<BaseScript*> script) {
1716 // Finish all pending source compressions, including the single compression
1717 // task that may have been created (by |ScriptSource::tryCompressOffThread|)
1718 // just after the script was compiled. Because we have flushed this queue,
1719 // no code below needs to synchronize with an off-thread parse task that
1720 // assumes the immutability of a |ScriptSource|'s data.
1722 // This *may* end up compressing |script|'s source. If it does -- we test
1723 // this below -- that takes care of things. But if it doesn't, we will
1724 // synchronously compress ourselves (and as noted above, this won't race
1725 // anything).
1726 RunPendingSourceCompressions(cx->runtime());
1728 ScriptSource* ss = script->scriptSource();
1729 #ifdef DEBUG
1731 auto guard = ss->readers_.lock();
1732 MOZ_ASSERT(guard->count == 0,
1733 "can't synchronously compress while source units are in use");
1735 #endif
1737 // In principle a previously-triggered compression on a helper thread could
1738 // have already completed. If that happens, there's nothing more to do.
1739 if (ss->hasCompressedSource()) {
1740 return true;
1743 MOZ_ASSERT(ss->hasUncompressedSource(),
1744 "shouldn't be compressing uncompressible source");
1746 // Use an explicit scope to delineate the lifetime of |task|, for simplicity.
1748 #ifdef DEBUG
1749 uint32_t sourceRefs = ss->refs;
1750 #endif
1751 MOZ_ASSERT(sourceRefs > 0, "at least |script| here should have a ref");
1753 // |SourceCompressionTask::shouldCancel| can periodically result in source
1754 // compression being canceled if we're not careful. Guarantee that two refs
1755 // to |ss| are always live in this function (at least one preexisting and
1756 // one held by the task) so that compression is never canceled.
1757 auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), ss);
1758 if (!task) {
1759 ReportOutOfMemory(cx);
1760 return false;
1763 MOZ_ASSERT(ss->refs > sourceRefs, "must have at least two refs now");
1765 // Attempt to compress. This may not succeed if OOM happens, but (because
1766 // it ordinarily happens on a helper thread) no error will ever be set here.
1767 MOZ_ASSERT(!cx->isExceptionPending());
1768 ss->performTaskWork(task.get());
1769 MOZ_ASSERT(!cx->isExceptionPending());
1771 // Convert |ss| from uncompressed to compressed data.
1772 task->complete();
1774 MOZ_ASSERT(!cx->isExceptionPending());
1777 // The only way source won't be compressed here is if OOM happened.
1778 return ss->hasCompressedSource();
1781 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
1782 JS::ScriptSourceInfo* info) const {
1783 info->misc += mallocSizeOf(this);
1784 info->numScripts++;
1787 bool ScriptSource::startIncrementalEncoding(
1788 JSContext* cx,
1789 UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
1790 // We don't support asm.js in XDR.
1791 // Encoding failures are reported by the xdrFinalizeEncoder function.
1792 if (initial->asmJS) {
1793 return true;
1796 // Remove the reference to the source, to avoid the circular reference.
1797 initial->source = nullptr;
1799 AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime);
1800 auto failureCase = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(); });
1802 if (!xdrEncoder_.setInitial(
1803 cx, std::forward<UniquePtr<frontend::ExtensibleCompilationStencil>>(
1804 initial))) {
1805 // On encoding failure, let failureCase destroy encoder and return true
1806 // to avoid failing any currently executing script.
1807 return false;
1810 failureCase.release();
1811 return true;
1814 bool ScriptSource::addDelazificationToIncrementalEncoding(
1815 JSContext* cx, const frontend::CompilationStencil& stencil) {
1816 MOZ_ASSERT(hasEncoder());
1817 AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime);
1818 auto failureCase = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(); });
1820 if (!xdrEncoder_.addDelazification(cx, stencil)) {
1821 // On encoding failure, let failureCase destroy encoder and return true
1822 // to avoid failing any currently executing script.
1823 return false;
1826 failureCase.release();
1827 return true;
1830 bool ScriptSource::xdrFinalizeEncoder(JSContext* cx,
1831 JS::TranscodeBuffer& buffer) {
1832 if (!hasEncoder()) {
1833 JS_ReportErrorASCII(cx, "XDR encoding failure");
1834 return false;
1837 auto cleanup = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(); });
1839 AutoReportFrontendContext fc(cx);
1840 XDRStencilEncoder encoder(&fc, buffer);
1842 frontend::BorrowingCompilationStencil borrowingStencil(
1843 xdrEncoder_.merger_->getResult());
1844 XDRResult res = encoder.codeStencil(this, borrowingStencil);
1845 if (res.isErr()) {
1846 if (JS::IsTranscodeFailureResult(res.unwrapErr())) {
1847 fc.clearAutoReport();
1848 JS_ReportErrorASCII(cx, "XDR encoding failure");
1850 return false;
1852 return true;
1855 void ScriptSource::xdrAbortEncoder() { xdrEncoder_.reset(); }
1857 template <typename Unit>
1858 [[nodiscard]] bool ScriptSource::initializeUnretrievableUncompressedSource(
1859 FrontendContext* fc, EntryUnits<Unit>&& source, size_t length) {
1860 MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource");
1861 return setUncompressedSourceHelper(fc, std::move(source), length,
1862 SourceRetrievable::No);
1865 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1866 FrontendContext* fc, EntryUnits<Utf8Unit>&& source, size_t length);
1867 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1868 FrontendContext* fc, EntryUnits<char16_t>&& source, size_t length);
1870 // Format and return a cx->pod_malloc'ed URL for a generated script like:
1871 // {filename} line {lineno} > {introducer}
1872 // For example:
1873 // foo.js line 7 > eval
1874 // indicating code compiled by the call to 'eval' on line 7 of foo.js.
1875 UniqueChars js::FormatIntroducedFilename(const char* filename, uint32_t lineno,
1876 const char* introducer) {
1877 // Compute the length of the string in advance, so we can allocate a
1878 // buffer of the right size on the first shot.
1880 // (JS_smprintf would be perfect, as that allocates the result
1881 // dynamically as it formats the string, but it won't allocate from cx,
1882 // and wants us to use a special free function.)
1883 char linenoBuf[15];
1884 size_t filenameLen = strlen(filename);
1885 size_t linenoLen = SprintfLiteral(linenoBuf, "%u", lineno);
1886 size_t introducerLen = strlen(introducer);
1887 size_t len = filenameLen + 6 /* == strlen(" line ") */ + linenoLen +
1888 3 /* == strlen(" > ") */ + introducerLen + 1 /* \0 */;
1889 UniqueChars formatted(js_pod_malloc<char>(len));
1890 if (!formatted) {
1891 return nullptr;
1894 mozilla::DebugOnly<size_t> checkLen = snprintf(
1895 formatted.get(), len, "%s line %s > %s", filename, linenoBuf, introducer);
1896 MOZ_ASSERT(checkLen == len - 1);
1898 return formatted;
1901 bool ScriptSource::initFromOptions(FrontendContext* fc,
1902 const ReadOnlyCompileOptions& options) {
1903 MOZ_ASSERT(!filename_);
1904 MOZ_ASSERT(!introducerFilename_);
1906 mutedErrors_ = options.mutedErrors();
1907 delazificationMode_ = options.eagerDelazificationStrategy();
1909 startLine_ = options.lineno;
1910 startColumn_ =
1911 JS::LimitedColumnNumberZeroOrigin::fromUnlimited(options.column);
1912 introductionType_ = options.introductionType;
1913 setIntroductionOffset(options.introductionOffset);
1914 // The parameterListEnd_ is initialized later by setParameterListEnd, before
1915 // we expose any scripts that use this ScriptSource to the debugger.
1917 if (options.hasIntroductionInfo) {
1918 MOZ_ASSERT(options.introductionType != nullptr);
1919 const char* filename =
1920 options.filename() ? options.filename().c_str() : "<unknown>";
1921 UniqueChars formatted = FormatIntroducedFilename(
1922 filename, options.introductionLineno, options.introductionType);
1923 if (!formatted) {
1924 ReportOutOfMemory(fc);
1925 return false;
1927 if (!setFilename(fc, std::move(formatted))) {
1928 return false;
1930 } else if (options.filename()) {
1931 if (!setFilename(fc, options.filename().c_str())) {
1932 return false;
1936 if (options.introducerFilename()) {
1937 if (!setIntroducerFilename(fc, options.introducerFilename().c_str())) {
1938 return false;
1942 return true;
1945 // Use the SharedImmutableString map to deduplicate input string. The input
1946 // string must be null-terminated.
1947 template <typename SharedT, typename CharT>
1948 static SharedT GetOrCreateStringZ(FrontendContext* fc,
1949 UniquePtr<CharT[], JS::FreePolicy>&& str) {
1950 size_t lengthWithNull = std::char_traits<CharT>::length(str.get()) + 1;
1951 auto res = SharedImmutableStringsCache::getSingleton().getOrCreate(
1952 std::move(str), lengthWithNull);
1953 if (!res) {
1954 ReportOutOfMemory(fc);
1956 return res;
1959 SharedImmutableString ScriptSource::getOrCreateStringZ(FrontendContext* fc,
1960 UniqueChars&& str) {
1961 return GetOrCreateStringZ<SharedImmutableString>(fc, std::move(str));
1964 SharedImmutableTwoByteString ScriptSource::getOrCreateStringZ(
1965 FrontendContext* fc, UniqueTwoByteChars&& str) {
1966 return GetOrCreateStringZ<SharedImmutableTwoByteString>(fc, std::move(str));
1969 bool ScriptSource::setFilename(FrontendContext* fc, const char* filename) {
1970 UniqueChars owned = DuplicateString(fc, filename);
1971 if (!owned) {
1972 return false;
1974 return setFilename(fc, std::move(owned));
1977 bool ScriptSource::setFilename(FrontendContext* fc, UniqueChars&& filename) {
1978 MOZ_ASSERT(!filename_);
1979 filename_ = getOrCreateStringZ(fc, std::move(filename));
1980 if (filename_) {
1981 filenameHash_ =
1982 mozilla::HashStringKnownLength(filename_.chars(), filename_.length());
1983 return true;
1985 return false;
1988 bool ScriptSource::setIntroducerFilename(FrontendContext* fc,
1989 const char* filename) {
1990 UniqueChars owned = DuplicateString(fc, filename);
1991 if (!owned) {
1992 return false;
1994 return setIntroducerFilename(fc, std::move(owned));
1997 bool ScriptSource::setIntroducerFilename(FrontendContext* fc,
1998 UniqueChars&& filename) {
1999 MOZ_ASSERT(!introducerFilename_);
2000 introducerFilename_ = getOrCreateStringZ(fc, std::move(filename));
2001 return bool(introducerFilename_);
2004 bool ScriptSource::setDisplayURL(FrontendContext* fc, const char16_t* url) {
2005 UniqueTwoByteChars owned = DuplicateString(fc, url);
2006 if (!owned) {
2007 return false;
2009 return setDisplayURL(fc, std::move(owned));
2012 bool ScriptSource::setDisplayURL(FrontendContext* fc,
2013 UniqueTwoByteChars&& url) {
2014 MOZ_ASSERT(!hasDisplayURL());
2015 MOZ_ASSERT(url);
2016 if (url[0] == '\0') {
2017 return true;
2020 displayURL_ = getOrCreateStringZ(fc, std::move(url));
2021 return bool(displayURL_);
2024 bool ScriptSource::setSourceMapURL(FrontendContext* fc, const char16_t* url) {
2025 UniqueTwoByteChars owned = DuplicateString(fc, url);
2026 if (!owned) {
2027 return false;
2029 return setSourceMapURL(fc, std::move(owned));
2032 bool ScriptSource::setSourceMapURL(FrontendContext* fc,
2033 UniqueTwoByteChars&& url) {
2034 MOZ_ASSERT(url);
2035 if (url[0] == '\0') {
2036 return true;
2039 sourceMapURL_ = getOrCreateStringZ(fc, std::move(url));
2040 return bool(sourceMapURL_);
2043 /* static */ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent>
2044 ScriptSource::idCount_;
2047 * [SMDOC] JSScript data layout (immutable)
2049 * Script data that shareable across processes. There are no pointers (GC or
2050 * otherwise) and the data is relocatable.
2052 * Array elements Pointed to by Length
2053 * -------------- ------------- ------
2054 * jsbytecode code() codeLength()
2055 * jsscrnote notes() noteLength()
2056 * uint32_t resumeOffsets()
2057 * ScopeNote scopeNotes()
2058 * TryNote tryNotes()
2061 /* static */ CheckedInt<uint32_t> ImmutableScriptData::sizeFor(
2062 uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets,
2063 uint32_t numScopeNotes, uint32_t numTryNotes) {
2064 // Take a count of which optional arrays will be used and need offset info.
2065 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
2066 unsigned(numScopeNotes > 0) +
2067 unsigned(numTryNotes > 0);
2069 // Compute size including trailing arrays.
2070 CheckedInt<uint32_t> size = sizeof(ImmutableScriptData);
2071 size += sizeof(Flags);
2072 size += CheckedInt<uint32_t>(codeLength) * sizeof(jsbytecode);
2073 size += CheckedInt<uint32_t>(noteLength) * sizeof(SrcNote);
2074 size += CheckedInt<uint32_t>(numOptionalArrays) * sizeof(Offset);
2075 size += CheckedInt<uint32_t>(numResumeOffsets) * sizeof(uint32_t);
2076 size += CheckedInt<uint32_t>(numScopeNotes) * sizeof(ScopeNote);
2077 size += CheckedInt<uint32_t>(numTryNotes) * sizeof(TryNote);
2079 return size;
2082 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_(
2083 FrontendContext* fc, uint32_t codeLength, uint32_t noteLength,
2084 uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) {
2085 auto size = sizeFor(codeLength, noteLength, numResumeOffsets, numScopeNotes,
2086 numTryNotes);
2087 if (!size.isValid()) {
2088 ReportAllocationOverflow(fc);
2089 return nullptr;
2092 // Allocate contiguous raw buffer.
2093 void* raw = fc->getAllocator()->pod_malloc<uint8_t>(size.value());
2094 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0);
2095 if (!raw) {
2096 return nullptr;
2099 // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but
2100 // GCPtrs are put into a safe state.
2101 UniquePtr<ImmutableScriptData> result(new (raw) ImmutableScriptData(
2102 codeLength, noteLength, numResumeOffsets, numScopeNotes, numTryNotes));
2103 if (!result) {
2104 return nullptr;
2107 // Sanity check
2108 MOZ_ASSERT(result->endOffset() == size.value());
2110 return result;
2113 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_(
2114 FrontendContext* fc, uint32_t totalSize) {
2115 void* raw = fc->getAllocator()->pod_malloc<uint8_t>(totalSize);
2116 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0);
2117 UniquePtr<ImmutableScriptData> result(
2118 reinterpret_cast<ImmutableScriptData*>(raw));
2119 return result;
2122 bool js::ImmutableScriptData::validateLayout(uint32_t expectedSize) {
2123 constexpr size_t HeaderSize = sizeof(js::ImmutableScriptData);
2124 constexpr size_t OptionalOffsetsMaxSize = 3 * sizeof(Offset);
2126 // Check that the optional-offsets array lies within the allocation before we
2127 // try to read from it while computing sizes. Remember that the array *ends*
2128 // at the `optArrayOffset_`.
2129 static_assert(OptionalOffsetsMaxSize <= HeaderSize);
2130 if (HeaderSize > optArrayOffset_) {
2131 return false;
2133 if (optArrayOffset_ > expectedSize) {
2134 return false;
2137 // Round-trip the size computation using `CheckedInt` to detect overflow. This
2138 // should indirectly validate most alignment, size, and ordering requirments.
2139 auto size = sizeFor(codeLength(), noteLength(), resumeOffsets().size(),
2140 scopeNotes().size(), tryNotes().size());
2141 return size.isValid() && (size.value() == expectedSize);
2144 /* static */
2145 SharedImmutableScriptData* SharedImmutableScriptData::create(
2146 FrontendContext* fc) {
2147 return fc->getAllocator()->new_<SharedImmutableScriptData>();
2150 /* static */
2151 SharedImmutableScriptData* SharedImmutableScriptData::createWith(
2152 FrontendContext* fc, js::UniquePtr<ImmutableScriptData>&& isd) {
2153 MOZ_ASSERT(isd.get());
2154 SharedImmutableScriptData* sisd = create(fc);
2155 if (!sisd) {
2156 return nullptr;
2159 sisd->setOwn(std::move(isd));
2160 return sisd;
2163 void JSScript::relazify(JSRuntime* rt) {
2164 js::Scope* scope = enclosingScope();
2165 UniquePtr<PrivateScriptData> scriptData;
2167 // Any JIT compiles should have been released, so we already point to the
2168 // interpreter trampoline which supports lazy scripts.
2169 MOZ_ASSERT_IF(jit::HasJitBackend(), isUsingInterpreterTrampoline(rt));
2171 // Without bytecode, the script counts are invalid so destroy them if they
2172 // still exist.
2173 destroyScriptCounts();
2175 // Release the bytecode and gcthings list.
2176 // NOTE: We clear the PrivateScriptData to nullptr. This is fine because we
2177 // only allowed relazification (via AllowRelazify) if the original lazy
2178 // script we compiled from had a nullptr PrivateScriptData.
2179 swapData(scriptData);
2180 freeSharedData();
2182 // We should not still be in any side-tables for the debugger or
2183 // code-coverage. The finalizer will not be able to clean them up once
2184 // bytecode is released. We check in JSFunction::maybeRelazify() for these
2185 // conditions before requesting relazification.
2186 MOZ_ASSERT(!coverage::IsLCovEnabled());
2187 MOZ_ASSERT(!hasScriptCounts());
2188 MOZ_ASSERT(!hasDebugScript());
2190 // Rollback warmUpData_ to have enclosingScope.
2191 MOZ_ASSERT(warmUpData_.isWarmUpCount(),
2192 "JitScript should already be released");
2193 warmUpData_.resetWarmUpCount(0);
2194 warmUpData_.initEnclosingScope(scope);
2196 MOZ_ASSERT(isReadyForDelazification());
2199 // Takes ownership of the passed SharedImmutableScriptData and either adds it
2200 // into the runtime's SharedImmutableScriptDataTable, or frees it if a matching
2201 // entry already exists and replaces the passed RefPtr with the existing entry.
2202 /* static */
2203 bool SharedImmutableScriptData::shareScriptData(
2204 FrontendContext* fc, RefPtr<SharedImmutableScriptData>& sisd) {
2205 MOZ_ASSERT(sisd);
2206 MOZ_ASSERT(sisd->refCount() == 1);
2208 SharedImmutableScriptData* data = sisd.get();
2210 SharedImmutableScriptData::Hasher::Lookup lookup(data);
2212 Maybe<AutoLockGlobalScriptData> lock;
2213 js::SharedImmutableScriptDataTable& table =
2214 fc->scriptDataTableHolder()->getMaybeLocked(lock);
2216 SharedImmutableScriptDataTable::AddPtr p = table.lookupForAdd(lookup);
2217 if (p) {
2218 MOZ_ASSERT(data != *p);
2219 sisd = *p;
2220 } else {
2221 if (!table.add(p, data)) {
2222 ReportOutOfMemory(fc);
2223 return false;
2226 // Being in the table counts as a reference on the script data.
2227 data->AddRef();
2230 // Refs: sisd argument, SharedImmutableScriptDataTable
2231 MOZ_ASSERT(sisd->refCount() >= 2);
2233 return true;
2236 static void SweepScriptDataTable(SharedImmutableScriptDataTable& table) {
2237 // Entries are removed from the table when their reference count is one,
2238 // i.e. when the only reference to them is from the table entry.
2240 for (SharedImmutableScriptDataTable::Enum e(table); !e.empty();
2241 e.popFront()) {
2242 SharedImmutableScriptData* sharedData = e.front();
2243 if (sharedData->refCount() == 1) {
2244 sharedData->Release();
2245 e.removeFront();
2250 void js::SweepScriptData(JSRuntime* rt) {
2251 SweepScriptDataTable(rt->scriptDataTableHolder().getWithoutLock());
2253 AutoLockGlobalScriptData lock;
2254 SweepScriptDataTable(js::globalSharedScriptDataTableHolder.get(lock));
2257 inline size_t PrivateScriptData::allocationSize() const { return endOffset(); }
2259 // Initialize and placement-new the trailing arrays.
2260 PrivateScriptData::PrivateScriptData(uint32_t ngcthings)
2261 : ngcthings(ngcthings) {
2262 // Variable-length data begins immediately after PrivateScriptData itself.
2263 // NOTE: Alignment is computed using cursor/offset so the alignment of
2264 // PrivateScriptData must be stricter than any trailing array type.
2265 Offset cursor = sizeof(PrivateScriptData);
2267 // Layout and initialize the gcthings array.
2269 initElements<JS::GCCellPtr>(cursor, ngcthings);
2270 cursor += ngcthings * sizeof(JS::GCCellPtr);
2273 // Sanity check.
2274 MOZ_ASSERT(endOffset() == cursor);
2277 /* static */
2278 PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings) {
2279 // Compute size including trailing arrays.
2280 CheckedInt<Offset> size = sizeof(PrivateScriptData);
2281 size += CheckedInt<Offset>(ngcthings) * sizeof(JS::GCCellPtr);
2282 if (!size.isValid()) {
2283 ReportAllocationOverflow(cx);
2284 return nullptr;
2287 // Allocate contiguous raw buffer for the trailing arrays.
2288 void* raw = cx->pod_malloc<uint8_t>(size.value());
2289 MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0);
2290 if (!raw) {
2291 return nullptr;
2294 // Constuct the PrivateScriptData. Trailing arrays are uninitialized but
2295 // GCPtrs are put into a safe state.
2296 PrivateScriptData* result = new (raw) PrivateScriptData(ngcthings);
2297 if (!result) {
2298 return nullptr;
2301 // Sanity check.
2302 MOZ_ASSERT(result->endOffset() == size.value());
2304 return result;
2307 /* static */
2308 bool PrivateScriptData::InitFromStencil(
2309 JSContext* cx, js::HandleScript script,
2310 const js::frontend::CompilationAtomCache& atomCache,
2311 const js::frontend::CompilationStencil& stencil,
2312 js::frontend::CompilationGCOutput& gcOutput,
2313 const js::frontend::ScriptIndex scriptIndex) {
2314 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex];
2315 uint32_t ngcthings = scriptStencil.gcThingsLength;
2317 MOZ_ASSERT(ngcthings <= INDEX_LIMIT);
2319 // Create and initialize PrivateScriptData
2320 if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) {
2321 return false;
2324 js::PrivateScriptData* data = script->data_;
2325 if (ngcthings) {
2326 if (!EmitScriptThingsVector(cx, atomCache, stencil, gcOutput,
2327 scriptStencil.gcthings(stencil),
2328 data->gcthings())) {
2329 return false;
2333 return true;
2336 void PrivateScriptData::trace(JSTracer* trc) {
2337 for (JS::GCCellPtr& elem : gcthings()) {
2338 TraceManuallyBarrieredGCCellPtr(trc, &elem, "script-gcthing");
2342 /*static*/
2343 JSScript* JSScript::Create(JSContext* cx, JS::Handle<JSFunction*> function,
2344 js::Handle<ScriptSourceObject*> sourceObject,
2345 const SourceExtent& extent,
2346 js::ImmutableScriptFlags flags) {
2347 return static_cast<JSScript*>(
2348 BaseScript::New(cx, function, sourceObject, extent, flags));
2351 #ifdef MOZ_VTUNE
2352 uint32_t JSScript::vtuneMethodID() {
2353 if (!zone()->scriptVTuneIdMap) {
2354 auto map = MakeUnique<ScriptVTuneIdMap>();
2355 if (!map) {
2356 MOZ_CRASH("Failed to allocate ScriptVTuneIdMap");
2359 zone()->scriptVTuneIdMap = std::move(map);
2362 ScriptVTuneIdMap::AddPtr p = zone()->scriptVTuneIdMap->lookupForAdd(this);
2363 if (p) {
2364 return p->value();
2367 MOZ_ASSERT(this->hasBytecode());
2369 uint32_t id = vtune::GenerateUniqueMethodID();
2370 if (!zone()->scriptVTuneIdMap->add(p, this, id)) {
2371 MOZ_CRASH("Failed to add vtune method id");
2374 return id;
2376 #endif
2378 /* static */
2379 bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script,
2380 uint32_t ngcthings) {
2381 cx->check(script);
2383 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings));
2384 if (!data) {
2385 return false;
2388 script->swapData(data);
2389 MOZ_ASSERT(!data);
2391 return true;
2394 /* static */
2395 bool JSScript::fullyInitFromStencil(
2396 JSContext* cx, const js::frontend::CompilationAtomCache& atomCache,
2397 const js::frontend::CompilationStencil& stencil,
2398 frontend::CompilationGCOutput& gcOutput, HandleScript script,
2399 const js::frontend::ScriptIndex scriptIndex) {
2400 MutableScriptFlags lazyMutableFlags;
2401 Rooted<Scope*> lazyEnclosingScope(cx);
2403 // A holder for the lazy PrivateScriptData that we must keep around in case
2404 // this process fails and we must return the script to its original state.
2406 // This is initialized by BaseScript::swapData() which will run pre-barriers
2407 // for us. On successful conversion to non-lazy script, the old script data
2408 // here will be released by the UniquePtr.
2409 Rooted<UniquePtr<PrivateScriptData>> lazyData(cx);
2411 // Whether we are a newborn script or an existing lazy script, we should
2412 // already be pointing to the interpreter trampoline.
2413 MOZ_ASSERT_IF(jit::HasJitBackend(),
2414 script->isUsingInterpreterTrampoline(cx->runtime()));
2416 // If we are using an existing lazy script, record enough info to be able to
2417 // rollback on failure.
2418 if (script->isReadyForDelazification()) {
2419 lazyMutableFlags = script->mutableFlags_;
2420 lazyEnclosingScope = script->releaseEnclosingScope();
2421 script->swapData(lazyData.get());
2422 MOZ_ASSERT(script->sharedData_ == nullptr);
2425 // Restore the script to lazy state on failure. If this was a fresh script, we
2426 // just need to clear bytecode to mark script as incomplete.
2427 auto rollbackGuard = mozilla::MakeScopeExit([&] {
2428 if (lazyEnclosingScope) {
2429 script->mutableFlags_ = lazyMutableFlags;
2430 script->warmUpData_.initEnclosingScope(lazyEnclosingScope);
2431 script->swapData(lazyData.get());
2432 script->sharedData_ = nullptr;
2434 MOZ_ASSERT(script->isReadyForDelazification());
2435 } else {
2436 script->sharedData_ = nullptr;
2440 // The counts of indexed things must be checked during code generation.
2441 MOZ_ASSERT(stencil.scriptData[scriptIndex].gcThingsLength <= INDEX_LIMIT);
2443 // Note: These flags should already be correct when the BaseScript was
2444 // allocated.
2445 MOZ_ASSERT_IF(stencil.isInitialStencil(),
2446 script->immutableFlags() ==
2447 stencil.scriptExtra[scriptIndex].immutableFlags);
2449 // Create and initialize PrivateScriptData
2450 if (!PrivateScriptData::InitFromStencil(cx, script, atomCache, stencil,
2451 gcOutput, scriptIndex)) {
2452 return false;
2455 // Member-initializer data is computed in initial parse only. If we are
2456 // delazifying, make sure to copy it off the `lazyData` before we throw it
2457 // away.
2458 if (script->useMemberInitializers()) {
2459 if (stencil.isInitialStencil()) {
2460 MemberInitializers initializers(
2461 stencil.scriptExtra[scriptIndex].memberInitializers());
2462 script->setMemberInitializers(initializers);
2463 } else {
2464 script->setMemberInitializers(lazyData.get()->getMemberInitializers());
2468 auto* scriptData = stencil.sharedData.get(scriptIndex);
2469 MOZ_ASSERT_IF(
2470 script->isGenerator() || script->isAsync(),
2471 scriptData->nfixed() <= frontend::ParseContext::Scope::FixedSlotLimit);
2473 script->initSharedData(scriptData);
2475 // NOTE: JSScript is now constructed and should be linked in.
2476 rollbackGuard.release();
2478 // Link Scope -> JSFunction -> BaseScript.
2479 if (script->isFunction()) {
2480 JSFunction* fun = gcOutput.getFunction(scriptIndex);
2481 script->bodyScope()->as<FunctionScope>().initCanonicalFunction(fun);
2482 if (fun->isIncomplete()) {
2483 fun->initScript(script);
2484 } else if (fun->hasSelfHostedLazyScript()) {
2485 fun->clearSelfHostedLazyScript();
2486 fun->initScript(script);
2487 } else {
2488 // We are delazifying in-place.
2489 MOZ_ASSERT(fun->baseScript() == script);
2493 // NOTE: The caller is responsible for linking ModuleObjects if this is a
2494 // module script.
2496 #ifdef JS_STRUCTURED_SPEW
2497 // We want this to happen after line number initialization to allow filtering
2498 // to work.
2499 script->setSpewEnabled(cx->spewer().enabled(script));
2500 #endif
2502 #ifdef DEBUG
2503 script->assertValidJumpTargets();
2504 #endif
2506 if (coverage::IsLCovEnabled()) {
2507 if (!coverage::InitScriptCoverage(cx, script)) {
2508 return false;
2512 return true;
2515 JSScript* JSScript::fromStencil(JSContext* cx,
2516 frontend::CompilationAtomCache& atomCache,
2517 const frontend::CompilationStencil& stencil,
2518 frontend::CompilationGCOutput& gcOutput,
2519 frontend::ScriptIndex scriptIndex) {
2520 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex];
2521 js::frontend::ScriptStencilExtra& scriptExtra =
2522 stencil.scriptExtra[scriptIndex];
2523 MOZ_ASSERT(scriptStencil.hasSharedData(),
2524 "Need generated bytecode to use JSScript::fromStencil");
2526 Rooted<JSFunction*> function(cx);
2527 if (scriptStencil.isFunction()) {
2528 function = gcOutput.getFunction(scriptIndex);
2531 Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);
2532 RootedScript script(cx, Create(cx, function, sourceObject, scriptExtra.extent,
2533 scriptExtra.immutableFlags));
2534 if (!script) {
2535 return nullptr;
2538 if (!fullyInitFromStencil(cx, atomCache, stencil, gcOutput, script,
2539 scriptIndex)) {
2540 return nullptr;
2543 return script;
2546 #ifdef DEBUG
2547 void JSScript::assertValidJumpTargets() const {
2548 BytecodeLocation mainLoc = mainLocation();
2549 BytecodeLocation endLoc = endLocation();
2550 AllBytecodesIterable iter(this);
2551 for (BytecodeLocation loc : iter) {
2552 // Check jump instructions' target.
2553 if (loc.isJump()) {
2554 BytecodeLocation target = loc.getJumpTarget();
2555 MOZ_ASSERT(mainLoc <= target && target < endLoc);
2556 MOZ_ASSERT(target.isJumpTarget());
2558 // All backward jumps must be to a JSOp::LoopHead op. This is an invariant
2559 // we want to maintain to simplify JIT compilation and bytecode analysis.
2560 MOZ_ASSERT_IF(target < loc, target.is(JSOp::LoopHead));
2561 MOZ_ASSERT_IF(target < loc, IsBackedgePC(loc.toRawBytecode()));
2563 // All forward jumps must be to a JSOp::JumpTarget op.
2564 MOZ_ASSERT_IF(target > loc, target.is(JSOp::JumpTarget));
2566 // Jumps must not cross scope boundaries.
2567 MOZ_ASSERT(loc.innermostScope(this) == target.innermostScope(this));
2569 // Check fallthrough of conditional jump instructions.
2570 if (loc.fallsThrough()) {
2571 BytecodeLocation fallthrough = loc.next();
2572 MOZ_ASSERT(mainLoc <= fallthrough && fallthrough < endLoc);
2573 MOZ_ASSERT(fallthrough.isJumpTarget());
2577 // Check table switch case labels.
2578 if (loc.is(JSOp::TableSwitch)) {
2579 BytecodeLocation target = loc.getTableSwitchDefaultTarget();
2581 // Default target.
2582 MOZ_ASSERT(mainLoc <= target && target < endLoc);
2583 MOZ_ASSERT(target.is(JSOp::JumpTarget));
2585 int32_t low = loc.getTableSwitchLow();
2586 int32_t high = loc.getTableSwitchHigh();
2588 for (int i = 0; i < high - low + 1; i++) {
2589 BytecodeLocation switchCase = loc.getTableSwitchCaseTarget(this, i);
2590 MOZ_ASSERT(mainLoc <= switchCase && switchCase < endLoc);
2591 MOZ_ASSERT(switchCase.is(JSOp::JumpTarget));
2596 // Check catch/finally blocks as jump targets.
2597 for (const TryNote& tn : trynotes()) {
2598 if (tn.kind() != TryNoteKind::Catch && tn.kind() != TryNoteKind::Finally) {
2599 continue;
2602 jsbytecode* tryStart = offsetToPC(tn.start);
2603 jsbytecode* tryPc = tryStart - JSOpLength_Try;
2604 MOZ_ASSERT(JSOp(*tryPc) == JSOp::Try);
2606 jsbytecode* tryTarget = tryStart + tn.length;
2607 MOZ_ASSERT(main() <= tryTarget && tryTarget < codeEnd());
2608 MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget)));
2611 #endif
2613 void JSScript::addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf,
2614 size_t* sizeOfJitScript,
2615 size_t* sizeOfBaselineFallbackStubs) const {
2616 if (!hasJitScript()) {
2617 return;
2620 jitScript()->addSizeOfIncludingThis(mallocSizeOf, sizeOfJitScript,
2621 sizeOfBaselineFallbackStubs);
2624 js::GlobalObject& JSScript::uninlinedGlobal() const { return global(); }
2626 unsigned js::PCToLineNumber(unsigned startLine,
2627 JS::LimitedColumnNumberZeroOrigin startCol,
2628 SrcNote* notes, SrcNote* notesEnd, jsbytecode* code,
2629 jsbytecode* pc,
2630 JS::LimitedColumnNumberZeroOrigin* columnp) {
2631 unsigned lineno = startLine;
2632 JS::LimitedColumnNumberZeroOrigin column = startCol;
2635 * Walk through source notes accumulating their deltas, keeping track of
2636 * line-number notes, until we pass the note for pc's offset within
2637 * script->code.
2639 ptrdiff_t offset = 0;
2640 ptrdiff_t target = pc - code;
2641 for (SrcNoteIterator iter(notes, notesEnd); !iter.atEnd(); ++iter) {
2642 const auto* sn = *iter;
2643 offset += sn->delta();
2644 if (offset > target) {
2645 break;
2648 SrcNoteType type = sn->type();
2649 if (type == SrcNoteType::SetLine) {
2650 lineno = SrcNote::SetLine::getLine(sn, startLine);
2651 column = JS::LimitedColumnNumberZeroOrigin::zero();
2652 } else if (type == SrcNoteType::SetLineColumn) {
2653 lineno = SrcNote::SetLineColumn::getLine(sn, startLine);
2654 column = SrcNote::SetLineColumn::getColumn(sn);
2655 } else if (type == SrcNoteType::NewLine) {
2656 lineno++;
2657 column = JS::LimitedColumnNumberZeroOrigin::zero();
2658 } else if (type == SrcNoteType::NewLineColumn) {
2659 lineno++;
2660 column = SrcNote::NewLineColumn::getColumn(sn);
2661 } else if (type == SrcNoteType::ColSpan) {
2662 column += SrcNote::ColSpan::getSpan(sn);
2666 if (columnp) {
2667 *columnp = column;
2670 return lineno;
2673 unsigned js::PCToLineNumber(JSScript* script, jsbytecode* pc,
2674 JS::LimitedColumnNumberZeroOrigin* columnp) {
2675 /* Cope with InterpreterFrame.pc value prior to entering Interpret. */
2676 if (!pc) {
2677 return 0;
2680 return PCToLineNumber(script->lineno(), script->column(), script->notes(),
2681 script->notesEnd(), script->code(), pc, columnp);
2684 jsbytecode* js::LineNumberToPC(JSScript* script, unsigned target) {
2685 ptrdiff_t offset = 0;
2686 ptrdiff_t best = -1;
2687 unsigned lineno = script->lineno();
2688 unsigned bestdiff = SrcNote::MaxOperand;
2689 for (SrcNoteIterator iter(script->notes(), script->notesEnd()); !iter.atEnd();
2690 ++iter) {
2691 const auto* sn = *iter;
2693 * Exact-match only if offset is not in the prologue; otherwise use
2694 * nearest greater-or-equal line number match.
2696 if (lineno == target && offset >= ptrdiff_t(script->mainOffset())) {
2697 goto out;
2699 if (lineno >= target) {
2700 unsigned diff = lineno - target;
2701 if (diff < bestdiff) {
2702 bestdiff = diff;
2703 best = offset;
2706 offset += sn->delta();
2707 SrcNoteType type = sn->type();
2708 if (type == SrcNoteType::SetLine) {
2709 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
2710 } else if (type == SrcNoteType::SetLineColumn) {
2711 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
2712 } else if (type == SrcNoteType::NewLine ||
2713 type == SrcNoteType::NewLineColumn) {
2714 lineno++;
2717 if (best >= 0) {
2718 offset = best;
2720 out:
2721 return script->offsetToPC(offset);
2724 JS_PUBLIC_API unsigned js::GetScriptLineExtent(JSScript* script) {
2725 unsigned lineno = script->lineno();
2726 unsigned maxLineNo = lineno;
2727 for (SrcNoteIterator iter(script->notes(), script->notesEnd()); !iter.atEnd();
2728 ++iter) {
2729 const auto* sn = *iter;
2730 SrcNoteType type = sn->type();
2731 if (type == SrcNoteType::SetLine) {
2732 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
2733 } else if (type == SrcNoteType::SetLineColumn) {
2734 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
2735 } else if (type == SrcNoteType::NewLine ||
2736 type == SrcNoteType::NewLineColumn) {
2737 lineno++;
2740 if (maxLineNo < lineno) {
2741 maxLineNo = lineno;
2745 return 1 + maxLineNo - script->lineno();
2748 #ifdef JS_CACHEIR_SPEW
2749 void js::maybeUpdateWarmUpCount(JSScript* script) {
2750 if (script->needsFinalWarmUpCount()) {
2751 ScriptFinalWarmUpCountMap* map =
2752 script->zone()->scriptFinalWarmUpCountMap.get();
2753 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2754 // already been created and thus must be asserted.
2755 MOZ_ASSERT(map);
2756 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script);
2757 MOZ_ASSERT(p);
2759 std::get<0>(p->value()) += script->jitScript()->warmUpCount();
2763 void js::maybeSpewScriptFinalWarmUpCount(JSScript* script) {
2764 if (script->needsFinalWarmUpCount()) {
2765 ScriptFinalWarmUpCountMap* map =
2766 script->zone()->scriptFinalWarmUpCountMap.get();
2767 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2768 // already been created and thus must be asserted.
2769 MOZ_ASSERT(map);
2770 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script);
2771 MOZ_ASSERT(p);
2772 auto& tuple = p->value();
2773 uint32_t warmUpCount = std::get<0>(tuple);
2774 SharedImmutableString& scriptName = std::get<1>(tuple);
2776 JSContext* cx = TlsContext.get();
2777 cx->spewer().enableSpewing();
2779 // In the case that we care about a script's final warmup count but the
2780 // spewer is not enabled, AutoSpewChannel automatically sets and unsets
2781 // the proper channel for the duration of spewing a health report's warm
2782 // up count.
2783 AutoSpewChannel channel(cx, SpewChannel::CacheIRHealthReport, script);
2784 jit::CacheIRHealth cih;
2785 cih.spewScriptFinalWarmUpCount(cx, scriptName.chars(), script, warmUpCount);
2787 script->zone()->scriptFinalWarmUpCountMap->remove(script);
2788 script->setNeedsFinalWarmUpCount(false);
2791 #endif
2793 void js::DescribeScriptedCallerForDirectEval(JSContext* cx, HandleScript script,
2794 jsbytecode* pc, const char** file,
2795 uint32_t* linenop,
2796 uint32_t* pcOffset,
2797 bool* mutedErrors) {
2798 MOZ_ASSERT(script->containsPC(pc));
2800 static_assert(JSOpLength_SpreadEval == JSOpLength_StrictSpreadEval,
2801 "next op after a spread must be at consistent offset");
2802 static_assert(JSOpLength_Eval == JSOpLength_StrictEval,
2803 "next op after a direct eval must be at consistent offset");
2805 MOZ_ASSERT(JSOp(*pc) == JSOp::Eval || JSOp(*pc) == JSOp::StrictEval ||
2806 JSOp(*pc) == JSOp::SpreadEval ||
2807 JSOp(*pc) == JSOp::StrictSpreadEval);
2809 bool isSpread =
2810 (JSOp(*pc) == JSOp::SpreadEval || JSOp(*pc) == JSOp::StrictSpreadEval);
2811 jsbytecode* nextpc =
2812 pc + (isSpread ? JSOpLength_SpreadEval : JSOpLength_Eval);
2813 MOZ_ASSERT(JSOp(*nextpc) == JSOp::Lineno);
2815 *file = script->filename();
2816 *linenop = GET_UINT32(nextpc);
2817 *pcOffset = script->pcToOffset(pc);
2818 *mutedErrors = script->mutedErrors();
2821 void js::DescribeScriptedCallerForCompilation(
2822 JSContext* cx, MutableHandleScript maybeScript, const char** file,
2823 uint32_t* linenop, uint32_t* pcOffset, bool* mutedErrors) {
2824 NonBuiltinFrameIter iter(cx, cx->realm()->principals());
2826 if (iter.done()) {
2827 maybeScript.set(nullptr);
2828 *file = nullptr;
2829 *linenop = 0;
2830 *pcOffset = 0;
2831 *mutedErrors = false;
2832 return;
2835 *file = iter.filename();
2836 *linenop = iter.computeLine();
2837 *mutedErrors = iter.mutedErrors();
2839 // These values are only used for introducer fields which are debugging
2840 // information and can be safely left null for wasm frames.
2841 if (iter.hasScript()) {
2842 maybeScript.set(iter.script());
2843 *pcOffset = iter.pc() - maybeScript->code();
2844 } else {
2845 maybeScript.set(nullptr);
2846 *pcOffset = 0;
2850 template <typename SourceSpan, typename TargetSpan>
2851 void CopySpan(const SourceSpan& source, TargetSpan target) {
2852 MOZ_ASSERT(source.size() == target.size());
2853 std::copy(source.cbegin(), source.cend(), target.begin());
2856 /* static */
2857 js::UniquePtr<ImmutableScriptData> ImmutableScriptData::new_(
2858 FrontendContext* fc, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots,
2859 GCThingIndex bodyScopeIndex, uint32_t numICEntries, bool isFunction,
2860 uint16_t funLength, uint16_t propertyCountEstimate,
2861 mozilla::Span<const jsbytecode> code, mozilla::Span<const SrcNote> notes,
2862 mozilla::Span<const uint32_t> resumeOffsets,
2863 mozilla::Span<const ScopeNote> scopeNotes,
2864 mozilla::Span<const TryNote> tryNotes) {
2865 MOZ_RELEASE_ASSERT(code.Length() <= frontend::MaxBytecodeLength);
2867 // There are 1-4 copies of SrcNoteType::Null appended after the source
2868 // notes. These are a combination of sentinel and padding values.
2869 static_assert(frontend::MaxSrcNotesLength <= UINT32_MAX - CodeNoteAlign,
2870 "Length + CodeNoteAlign shouldn't overflow UINT32_MAX");
2871 size_t noteLength = notes.Length();
2872 MOZ_RELEASE_ASSERT(noteLength <= frontend::MaxSrcNotesLength);
2874 size_t notePaddingLength = ComputeNotePadding(code.Length(), noteLength);
2876 // Allocate ImmutableScriptData
2877 js::UniquePtr<ImmutableScriptData> data(ImmutableScriptData::new_(
2878 fc, code.Length(), noteLength + notePaddingLength, resumeOffsets.Length(),
2879 scopeNotes.Length(), tryNotes.Length()));
2880 if (!data) {
2881 return data;
2884 // Initialize POD fields
2885 data->mainOffset = mainOffset;
2886 data->nfixed = nfixed;
2887 data->nslots = nslots;
2888 data->bodyScopeIndex = bodyScopeIndex;
2889 data->numICEntries = numICEntries;
2890 data->propertyCountEstimate = propertyCountEstimate;
2892 if (isFunction) {
2893 data->funLength = funLength;
2896 // Initialize trailing arrays
2897 CopySpan(code, data->codeSpan());
2898 CopySpan(notes, data->notesSpan().To(noteLength));
2899 std::fill_n(data->notes() + noteLength, notePaddingLength,
2900 SrcNote::padding());
2901 CopySpan(resumeOffsets, data->resumeOffsets());
2902 CopySpan(scopeNotes, data->scopeNotes());
2903 CopySpan(tryNotes, data->tryNotes());
2905 return data;
2908 void ScriptWarmUpData::trace(JSTracer* trc) {
2909 uintptr_t tag = data_ & TagMask;
2910 switch (tag) {
2911 case EnclosingScriptTag: {
2912 BaseScript* enclosingScript = toEnclosingScript();
2913 BaseScript* prior = enclosingScript;
2914 TraceManuallyBarrieredEdge(trc, &enclosingScript, "enclosingScript");
2915 if (enclosingScript != prior) {
2916 setTaggedPtr<EnclosingScriptTag>(enclosingScript);
2918 break;
2921 case EnclosingScopeTag: {
2922 Scope* enclosingScope = toEnclosingScope();
2923 Scope* prior = enclosingScope;
2924 TraceManuallyBarrieredEdge(trc, &enclosingScope, "enclosingScope");
2925 if (enclosingScope != prior) {
2926 setTaggedPtr<EnclosingScopeTag>(enclosingScope);
2928 break;
2931 case JitScriptTag: {
2932 toJitScript()->trace(trc);
2933 break;
2936 default: {
2937 MOZ_ASSERT(isWarmUpCount());
2938 break;
2943 size_t JSScript::calculateLiveFixed(jsbytecode* pc) {
2944 size_t nlivefixed = numAlwaysLiveFixedSlots();
2946 if (nfixed() != nlivefixed) {
2947 Scope* scope = lookupScope(pc);
2948 if (scope) {
2949 scope = MaybeForwarded(scope);
2952 // Find the nearest LexicalScope in the same script.
2953 while (scope && scope->is<WithScope>()) {
2954 scope = scope->enclosing();
2955 if (scope) {
2956 scope = MaybeForwarded(scope);
2960 if (scope) {
2961 if (scope->is<LexicalScope>()) {
2962 nlivefixed = scope->as<LexicalScope>().nextFrameSlot();
2963 } else if (scope->is<VarScope>()) {
2964 nlivefixed = scope->as<VarScope>().nextFrameSlot();
2965 } else if (scope->is<ClassBodyScope>()) {
2966 nlivefixed = scope->as<ClassBodyScope>().nextFrameSlot();
2971 MOZ_ASSERT(nlivefixed <= nfixed());
2972 MOZ_ASSERT(nlivefixed >= numAlwaysLiveFixedSlots());
2974 return nlivefixed;
2977 Scope* JSScript::lookupScope(const jsbytecode* pc) const {
2978 MOZ_ASSERT(containsPC(pc));
2980 size_t offset = pc - code();
2982 auto notes = scopeNotes();
2983 Scope* scope = nullptr;
2985 // Find the innermost block chain using a binary search.
2986 size_t bottom = 0;
2987 size_t top = notes.size();
2989 while (bottom < top) {
2990 size_t mid = bottom + (top - bottom) / 2;
2991 const ScopeNote* note = &notes[mid];
2992 if (note->start <= offset) {
2993 // Block scopes are ordered in the list by their starting offset, and
2994 // since blocks form a tree ones earlier in the list may cover the pc even
2995 // if later blocks end before the pc. This only happens when the earlier
2996 // block is a parent of the later block, so we need to check parents of
2997 // |mid| in the searched range for coverage.
2998 size_t check = mid;
2999 while (check >= bottom) {
3000 const ScopeNote* checkNote = &notes[check];
3001 MOZ_ASSERT(checkNote->start <= offset);
3002 if (offset < checkNote->start + checkNote->length) {
3003 // We found a matching block chain but there may be inner ones
3004 // at a higher block chain index than mid. Continue the binary search.
3005 if (checkNote->index == ScopeNote::NoScopeIndex) {
3006 scope = nullptr;
3007 } else {
3008 scope = getScope(checkNote->index);
3010 break;
3012 if (checkNote->parent == UINT32_MAX) {
3013 break;
3015 check = checkNote->parent;
3017 bottom = mid + 1;
3018 } else {
3019 top = mid;
3023 return scope;
3026 Scope* JSScript::innermostScope(const jsbytecode* pc) const {
3027 if (Scope* scope = lookupScope(pc)) {
3028 return scope;
3030 return bodyScope();
3033 void js::SetFrameArgumentsObject(JSContext* cx, AbstractFramePtr frame,
3034 HandleScript script, JSObject* argsobj) {
3036 * If the arguments object was optimized out by scalar replacement,
3037 * we must recreate it when we bail out. Because 'arguments' may have
3038 * already been overwritten, we must check to see if the slot already
3039 * contains a value.
3042 Rooted<BindingIter> bi(cx, BindingIter(script));
3043 while (bi && bi.name() != cx->names().arguments) {
3044 bi++;
3046 if (!bi) {
3047 return;
3050 if (bi.location().kind() == BindingLocation::Kind::Environment) {
3051 #ifdef DEBUG
3053 * If |arguments| lives in the call object, we should not have
3054 * optimized it. Scan the script to find the slot in the call
3055 * object that |arguments| is assigned to and verify that it
3056 * already exists.
3058 jsbytecode* pc = script->code();
3059 while (JSOp(*pc) != JSOp::Arguments) {
3060 pc += GetBytecodeLength(pc);
3062 pc += JSOpLength_Arguments;
3063 MOZ_ASSERT(JSOp(*pc) == JSOp::SetAliasedVar);
3065 EnvironmentObject& env = frame.callObj().as<EnvironmentObject>();
3066 MOZ_ASSERT(!env.aliasedBinding(bi).isMagic(JS_OPTIMIZED_OUT));
3067 #endif
3068 return;
3071 MOZ_ASSERT(bi.location().kind() == BindingLocation::Kind::Frame);
3072 uint32_t frameSlot = bi.location().slot();
3073 if (frame.unaliasedLocal(frameSlot).isMagic(JS_OPTIMIZED_OUT)) {
3074 frame.unaliasedLocal(frameSlot) = ObjectValue(*argsobj);
3078 bool JSScript::formalIsAliased(unsigned argSlot) {
3079 if (functionHasParameterExprs()) {
3080 return false;
3083 for (PositionalFormalParameterIter fi(this); fi; fi++) {
3084 if (fi.argumentSlot() == argSlot) {
3085 return fi.closedOver();
3088 MOZ_CRASH("Argument slot not found");
3091 // Returns true if any formal argument is mapped by the arguments
3092 // object, but lives in the call object.
3093 bool JSScript::anyFormalIsForwarded() {
3094 if (!argsObjAliasesFormals()) {
3095 return false;
3098 for (PositionalFormalParameterIter fi(this); fi; fi++) {
3099 if (fi.closedOver()) {
3100 return true;
3103 return false;
3106 bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) {
3107 return argsObjAliasesFormals() && !formalIsAliased(argSlot);
3110 BaseScript::BaseScript(uint8_t* stubEntry, JSFunction* function,
3111 ScriptSourceObject* sourceObject,
3112 const SourceExtent& extent, uint32_t immutableFlags)
3113 : TenuredCellWithNonGCPointer(stubEntry),
3114 function_(function),
3115 sourceObject_(sourceObject),
3116 extent_(extent),
3117 immutableFlags_(immutableFlags) {
3118 MOZ_ASSERT(extent_.toStringStart <= extent_.sourceStart);
3119 MOZ_ASSERT(extent_.sourceStart <= extent_.sourceEnd);
3120 MOZ_ASSERT(extent_.sourceEnd <= extent_.toStringEnd);
3123 /* static */
3124 BaseScript* BaseScript::New(JSContext* cx, JS::Handle<JSFunction*> function,
3125 Handle<ScriptSourceObject*> sourceObject,
3126 const SourceExtent& extent,
3127 uint32_t immutableFlags) {
3128 uint8_t* stubEntry = nullptr;
3129 if (jit::HasJitBackend()) {
3130 stubEntry = cx->runtime()->jitRuntime()->interpreterStub().value;
3133 MOZ_ASSERT_IF(function,
3134 function->compartment() == sourceObject->compartment());
3135 MOZ_ASSERT_IF(function, function->realm() == sourceObject->realm());
3137 return cx->newCell<BaseScript>(stubEntry, function, sourceObject, extent,
3138 immutableFlags);
3141 /* static */
3142 BaseScript* BaseScript::CreateRawLazy(JSContext* cx, uint32_t ngcthings,
3143 HandleFunction fun,
3144 Handle<ScriptSourceObject*> sourceObject,
3145 const SourceExtent& extent,
3146 uint32_t immutableFlags) {
3147 cx->check(fun);
3149 BaseScript* lazy = New(cx, fun, sourceObject, extent, immutableFlags);
3150 if (!lazy) {
3151 return nullptr;
3154 // Allocate a PrivateScriptData if it will not be empty. Lazy class
3155 // constructors that use member initializers also need PrivateScriptData for
3156 // field data.
3158 // This condition is implicit in BaseScript::hasPrivateScriptData, and should
3159 // be mirrored on InputScript::hasPrivateScriptData.
3160 if (ngcthings || lazy->useMemberInitializers()) {
3161 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings));
3162 if (!data) {
3163 return nullptr;
3165 lazy->swapData(data);
3166 MOZ_ASSERT(!data);
3169 return lazy;
3172 void JSScript::updateJitCodeRaw(JSRuntime* rt) {
3173 MOZ_ASSERT(rt);
3174 if (hasBaselineScript() && baselineScript()->hasPendingIonCompileTask()) {
3175 MOZ_ASSERT(!isIonCompilingOffThread());
3176 setJitCodeRaw(rt->jitRuntime()->lazyLinkStub().value);
3177 } else if (hasIonScript()) {
3178 jit::IonScript* ion = ionScript();
3179 setJitCodeRaw(ion->method()->raw());
3180 } else if (hasBaselineScript()) {
3181 setJitCodeRaw(baselineScript()->method()->raw());
3182 } else if (hasJitScript() && js::jit::IsBaselineInterpreterEnabled()) {
3183 bool usingEntryTrampoline = false;
3184 if (js::jit::JitOptions.emitInterpreterEntryTrampoline) {
3185 auto p = rt->jitRuntime()->getInterpreterEntryMap()->lookup(this);
3186 if (p) {
3187 setJitCodeRaw(p->value().raw());
3188 usingEntryTrampoline = true;
3191 if (!usingEntryTrampoline) {
3192 setJitCodeRaw(rt->jitRuntime()->baselineInterpreter().codeRaw());
3194 } else {
3195 setJitCodeRaw(rt->jitRuntime()->interpreterStub().value);
3197 MOZ_ASSERT(jitCodeRaw());
3200 bool JSScript::hasLoops() {
3201 for (const TryNote& tn : trynotes()) {
3202 if (tn.isLoop()) {
3203 return true;
3206 return false;
3209 bool JSScript::mayReadFrameArgsDirectly() {
3210 return needsArgsObj() || usesArgumentsIntrinsics() || hasRest();
3213 void JSScript::resetWarmUpCounterToDelayIonCompilation() {
3214 // Reset the warm-up count only if it's greater than the BaselineCompiler
3215 // threshold. We do this to ensure this has no effect on Baseline compilation
3216 // because we don't want scripts to get stuck in the (Baseline) interpreter in
3217 // pathological cases.
3219 if (getWarmUpCount() > jit::JitOptions.baselineJitWarmUpThreshold) {
3220 incWarmUpResetCounter();
3221 uint32_t newCount = jit::JitOptions.baselineJitWarmUpThreshold;
3222 if (warmUpData_.isWarmUpCount()) {
3223 warmUpData_.resetWarmUpCount(newCount);
3224 } else {
3225 warmUpData_.toJitScript()->resetWarmUpCount(newCount);
3230 gc::AllocSite* JSScript::createAllocSite() {
3231 return jitScript()->createAllocSite(this);
3234 #if defined(DEBUG) || defined(JS_JITSPEW)
3236 void JSScript::dump(JSContext* cx) {
3237 JS::Rooted<JSScript*> script(cx, this);
3239 js::Sprinter sp(cx);
3240 if (!sp.init()) {
3241 return;
3244 DumpOptions options;
3245 options.runtimeData = true;
3246 if (!dump(cx, script, options, &sp)) {
3247 return;
3250 fprintf(stderr, "%s\n", sp.string());
3253 void JSScript::dumpRecursive(JSContext* cx) {
3254 JS::Rooted<JSScript*> script(cx, this);
3256 js::Sprinter sp(cx);
3257 if (!sp.init()) {
3258 return;
3261 DumpOptions options;
3262 options.runtimeData = true;
3263 options.recursive = true;
3264 if (!dump(cx, script, options, &sp)) {
3265 return;
3268 fprintf(stderr, "%s\n", sp.string());
3271 static void DumpMutableScriptFlags(js::JSONPrinter& json,
3272 MutableScriptFlags mutableFlags) {
3273 // Skip warmup data.
3274 static_assert(int(MutableScriptFlagsEnum::WarmupResets_MASK) == 0xff);
3276 for (uint32_t i = 0x100; i; i = i << 1) {
3277 if (uint32_t(mutableFlags) & i) {
3278 switch (MutableScriptFlagsEnum(i)) {
3279 case MutableScriptFlagsEnum::HasRunOnce:
3280 json.value("HasRunOnce");
3281 break;
3282 case MutableScriptFlagsEnum::HasBeenCloned:
3283 json.value("HasBeenCloned");
3284 break;
3285 case MutableScriptFlagsEnum::HasScriptCounts:
3286 json.value("HasScriptCounts");
3287 break;
3288 case MutableScriptFlagsEnum::HasDebugScript:
3289 json.value("HasDebugScript");
3290 break;
3291 case MutableScriptFlagsEnum::AllowRelazify:
3292 json.value("AllowRelazify");
3293 break;
3294 case MutableScriptFlagsEnum::SpewEnabled:
3295 json.value("SpewEnabled");
3296 break;
3297 case MutableScriptFlagsEnum::NeedsFinalWarmUpCount:
3298 json.value("NeedsFinalWarmUpCount");
3299 break;
3300 case MutableScriptFlagsEnum::BaselineDisabled:
3301 json.value("BaselineDisabled");
3302 break;
3303 case MutableScriptFlagsEnum::IonDisabled:
3304 json.value("IonDisabled");
3305 break;
3306 case MutableScriptFlagsEnum::Uninlineable:
3307 json.value("Uninlineable");
3308 break;
3309 case MutableScriptFlagsEnum::NoEagerBaselineHint:
3310 json.value("NoEagerBaselineHint");
3311 break;
3312 case MutableScriptFlagsEnum::FailedBoundsCheck:
3313 json.value("FailedBoundsCheck");
3314 break;
3315 case MutableScriptFlagsEnum::HadLICMInvalidation:
3316 json.value("HadLICMInvalidation");
3317 break;
3318 case MutableScriptFlagsEnum::HadReorderingBailout:
3319 json.value("HadReorderingBailout");
3320 break;
3321 case MutableScriptFlagsEnum::HadEagerTruncationBailout:
3322 json.value("HadEagerTruncationBailout");
3323 break;
3324 case MutableScriptFlagsEnum::FailedLexicalCheck:
3325 json.value("FailedLexicalCheck");
3326 break;
3327 case MutableScriptFlagsEnum::HadSpeculativePhiBailout:
3328 json.value("HadSpeculativePhiBailout");
3329 break;
3330 case MutableScriptFlagsEnum::HadUnboxFoldingBailout:
3331 json.value("HadUnboxFoldingBailout");
3332 break;
3333 default:
3334 json.value("Unknown(%x)", i);
3335 break;
3341 /* static */
3342 bool JSScript::dump(JSContext* cx, JS::Handle<JSScript*> script,
3343 DumpOptions& options, js::Sprinter* sp) {
3345 JSONPrinter json(*sp);
3347 json.beginObject();
3349 if (const char* filename = script->filename()) {
3350 json.property("file", filename);
3351 } else {
3352 json.nullProperty("file");
3355 json.property("lineno", script->lineno());
3356 json.property("column", script->column().zeroOriginValue());
3358 json.beginListProperty("immutableFlags");
3359 DumpImmutableScriptFlags(json, script->immutableFlags());
3360 json.endList();
3362 if (options.runtimeData) {
3363 json.beginListProperty("mutableFlags");
3364 DumpMutableScriptFlags(json, script->mutableFlags_);
3365 json.endList();
3368 if (script->isFunction()) {
3369 JS::Rooted<JSFunction*> fun(cx, script->function());
3371 JS::Rooted<JSAtom*> name(cx, fun->displayAtom());
3372 if (name) {
3373 UniqueChars bytes = JS_EncodeStringToUTF8(cx, name);
3374 if (!bytes) {
3375 return false;
3377 json.property("functionName", bytes.get());
3378 } else {
3379 json.nullProperty("functionName");
3382 json.beginListProperty("functionFlags");
3383 DumpFunctionFlagsItems(json, fun->flags());
3384 json.endList();
3387 json.endObject();
3390 if (sp->hadOutOfMemory()) {
3391 return false;
3394 if (!sp->put("\n")) {
3395 return false;
3398 if (!Disassemble(cx, script, /* lines = */ true, sp)) {
3399 return false;
3401 if (!dumpSrcNotes(cx, script, sp)) {
3402 return false;
3404 if (!dumpTryNotes(cx, script, sp)) {
3405 return false;
3407 if (!dumpScopeNotes(cx, script, sp)) {
3408 return false;
3410 if (!dumpGCThings(cx, script, sp)) {
3411 return false;
3414 if (options.recursive) {
3415 for (JS::GCCellPtr gcThing : script->gcthings()) {
3416 if (!gcThing.is<JSObject>()) {
3417 continue;
3420 JSObject* obj = &gcThing.as<JSObject>();
3421 if (obj->is<JSFunction>()) {
3422 if (!sp->put("\n")) {
3423 return false;
3426 JS::Rooted<JSFunction*> fun(cx, &obj->as<JSFunction>());
3427 if (fun->isInterpreted()) {
3428 JS::Rooted<JSScript*> innerScript(
3429 cx, JSFunction::getOrCreateScript(cx, fun));
3430 if (!innerScript) {
3431 return false;
3433 if (!dump(cx, innerScript, options, sp)) {
3434 return false;
3436 } else {
3437 if (!sp->put("[native code]\n")) {
3438 return false;
3445 return true;
3448 /* static */
3449 bool JSScript::dumpSrcNotes(JSContext* cx, JS::Handle<JSScript*> script,
3450 js::Sprinter* sp) {
3451 if (!sp->put("\nSource notes:\n") ||
3452 !sp->jsprintf("%4s %4s %6s %5s %6s %-16s %s\n", "ofs", "line", "column",
3453 "pc", "delta", "desc", "args") ||
3454 !sp->put("---- ---- ------ ----- ------ ---------------- ------\n")) {
3455 return false;
3458 unsigned offset = 0;
3459 unsigned lineno = script->lineno();
3460 JS::LimitedColumnNumberZeroOrigin column = script->column();
3461 SrcNote* notes = script->notes();
3462 SrcNote* notesEnd = script->notesEnd();
3463 for (SrcNoteIterator iter(notes, notesEnd); !iter.atEnd(); ++iter) {
3464 const auto* sn = *iter;
3466 unsigned delta = sn->delta();
3467 offset += delta;
3468 SrcNoteType type = sn->type();
3469 const char* name = sn->name();
3470 if (!sp->jsprintf("%3u: %4u %6u %5u [%4u] %-16s", unsigned(sn - notes),
3471 lineno, column.zeroOriginValue(), offset, delta, name)) {
3472 return false;
3475 switch (type) {
3476 case SrcNoteType::Breakpoint:
3477 case SrcNoteType::BreakpointStepSep:
3478 case SrcNoteType::XDelta:
3479 break;
3481 case SrcNoteType::ColSpan: {
3482 JS::ColumnNumberOffset colspan = SrcNote::ColSpan::getSpan(sn);
3483 if (!sp->jsprintf(" colspan %u", colspan.value())) {
3484 return false;
3486 column += colspan;
3487 break;
3490 case SrcNoteType::SetLine:
3491 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
3492 if (!sp->jsprintf(" lineno %u", lineno)) {
3493 return false;
3495 column = JS::LimitedColumnNumberZeroOrigin::zero();
3496 break;
3498 case SrcNoteType::SetLineColumn:
3499 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
3500 column = SrcNote::SetLineColumn::getColumn(sn);
3501 if (!sp->jsprintf(" lineno %u column %u", lineno,
3502 column.zeroOriginValue())) {
3503 return false;
3505 break;
3507 case SrcNoteType::NewLine:
3508 ++lineno;
3509 column = JS::LimitedColumnNumberZeroOrigin::zero();
3510 break;
3512 case SrcNoteType::NewLineColumn:
3513 column = SrcNote::NewLineColumn::getColumn(sn);
3514 if (!sp->jsprintf(" column %u", column.zeroOriginValue())) {
3515 return false;
3518 ++lineno;
3519 break;
3521 default:
3522 MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
3524 if (!sp->put("\n")) {
3525 return false;
3529 return true;
3532 static const char* TryNoteName(TryNoteKind kind) {
3533 switch (kind) {
3534 case TryNoteKind::Catch:
3535 return "catch";
3536 case TryNoteKind::Finally:
3537 return "finally";
3538 case TryNoteKind::ForIn:
3539 return "for-in";
3540 case TryNoteKind::ForOf:
3541 return "for-of";
3542 case TryNoteKind::Loop:
3543 return "loop";
3544 case TryNoteKind::ForOfIterClose:
3545 return "for-of-iterclose";
3546 case TryNoteKind::Destructuring:
3547 return "destructuring";
3550 MOZ_CRASH("Bad TryNoteKind");
3553 /* static */
3554 bool JSScript::dumpTryNotes(JSContext* cx, JS::Handle<JSScript*> script,
3555 js::Sprinter* sp) {
3556 if (!sp->put(
3557 "\nException table:\nkind stack start end\n")) {
3558 return false;
3561 for (const js::TryNote& tn : script->trynotes()) {
3562 if (!sp->jsprintf(" %-16s %6u %8u %8u\n", TryNoteName(tn.kind()),
3563 tn.stackDepth, tn.start, tn.start + tn.length)) {
3564 return false;
3567 return true;
3570 /* static */
3571 bool JSScript::dumpScopeNotes(JSContext* cx, JS::Handle<JSScript*> script,
3572 js::Sprinter* sp) {
3573 if (!sp->put("\nScope notes:\n index parent start end\n")) {
3574 return false;
3577 for (const ScopeNote& note : script->scopeNotes()) {
3578 if (note.index == ScopeNote::NoScopeIndex) {
3579 if (!sp->jsprintf("%8s ", "(none)")) {
3580 return false;
3582 } else {
3583 if (!sp->jsprintf("%8u ", note.index.index)) {
3584 return false;
3587 if (note.parent == ScopeNote::NoScopeIndex) {
3588 if (!sp->jsprintf("%8s ", "(none)")) {
3589 return false;
3591 } else {
3592 if (!sp->jsprintf("%8u ", note.parent)) {
3593 return false;
3596 if (!sp->jsprintf("%8u %8u\n", note.start, note.start + note.length)) {
3597 return false;
3600 return true;
3603 /* static */
3604 bool JSScript::dumpGCThings(JSContext* cx, JS::Handle<JSScript*> script,
3605 js::Sprinter* sp) {
3606 if (!sp->put("\nGC things:\n index type value\n")) {
3607 return false;
3610 size_t i = 0;
3611 for (JS::GCCellPtr gcThing : script->gcthings()) {
3612 if (!sp->jsprintf("%8zu ", i)) {
3613 return false;
3615 if (gcThing.is<JS::BigInt>()) {
3616 if (!sp->put("BigInt ")) {
3617 return false;
3619 gcThing.as<JS::BigInt>().dump(*sp);
3620 if (!sp->put("\n")) {
3621 return false;
3623 } else if (gcThing.is<Scope>()) {
3624 if (!sp->put("Scope ")) {
3625 return false;
3627 JS::Rooted<Scope*> scope(cx, &gcThing.as<Scope>());
3628 if (!Scope::dumpForDisassemble(cx, scope, *sp,
3629 " ")) {
3630 return false;
3632 if (!sp->put("\n")) {
3633 return false;
3635 } else if (gcThing.is<JSObject>()) {
3636 JSObject* obj = &gcThing.as<JSObject>();
3637 if (obj->is<JSFunction>()) {
3638 if (!sp->put("Function ")) {
3639 return false;
3641 JS::Rooted<JSFunction*> fun(cx, &obj->as<JSFunction>());
3642 if (fun->displayAtom()) {
3643 JS::Rooted<JSAtom*> name(cx, fun->displayAtom());
3644 JS::UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, name);
3645 if (!utf8chars) {
3646 return false;
3648 if (!sp->put(utf8chars.get())) {
3649 return false;
3651 } else {
3652 if (!sp->put("(anonymous)")) {
3653 return false;
3657 if (fun->hasBaseScript()) {
3658 BaseScript* script = fun->baseScript();
3659 if (!sp->jsprintf(" @ %u:%u\n", script->lineno(),
3660 script->column().zeroOriginValue())) {
3661 return false;
3663 } else {
3664 if (!sp->put(" (no script)\n")) {
3665 return false;
3668 } else {
3669 if (obj->is<RegExpObject>()) {
3670 if (!sp->put("RegExp ")) {
3671 return false;
3673 } else {
3674 if (!sp->put("Object ")) {
3675 return false;
3679 JS::Rooted<JS::Value> objValue(cx, ObjectValue(*obj));
3680 JS::Rooted<JSString*> str(cx, ValueToSource(cx, objValue));
3681 if (!str) {
3682 return false;
3684 JS::UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
3685 if (!utf8chars) {
3686 return false;
3688 if (!sp->put(utf8chars.get())) {
3689 return false;
3692 if (!sp->put("\n")) {
3693 return false;
3696 } else if (gcThing.is<JSString>()) {
3697 JS::Rooted<JSString*> str(cx, &gcThing.as<JSString>());
3698 if (str->isAtom()) {
3699 if (!sp->put("Atom ")) {
3700 return false;
3702 } else {
3703 if (!sp->put("String ")) {
3704 return false;
3707 JS::UniqueChars chars = QuoteString(cx, str, '"');
3708 if (!chars) {
3709 return false;
3711 if (!sp->put(chars.get())) {
3712 return false;
3714 if (!sp->put("\n")) {
3715 return false;
3717 } else {
3718 if (!sp->put("Unknown\n")) {
3719 return false;
3722 i++;
3725 return true;
3728 #endif // defined(DEBUG) || defined(JS_JITSPEW)
3730 void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun) {
3731 if (fun) {
3732 JSAutoRealm ar(cx_, fun);
3733 script_ = JSFunction::getOrCreateScript(cx_, fun);
3734 if (script_) {
3735 oldAllowRelazify_ = script_->allowRelazify();
3736 script_->clearAllowRelazify();
3741 void JSScript::AutoDelazify::dropScript() {
3742 if (script_) {
3743 script_->setAllowRelazify(oldAllowRelazify_);
3745 script_ = nullptr;
3748 JS::ubi::Base::Size JS::ubi::Concrete<BaseScript>::size(
3749 mozilla::MallocSizeOf mallocSizeOf) const {
3750 BaseScript* base = &get();
3752 Size size = gc::Arena::thingSize(base->getAllocKind());
3753 size += base->sizeOfExcludingThis(mallocSizeOf);
3755 // Include any JIT data if it exists.
3756 if (base->hasJitScript()) {
3757 JSScript* script = base->asJSScript();
3759 size_t jitScriptSize = 0;
3760 size_t fallbackStubSize = 0;
3761 script->addSizeOfJitScript(mallocSizeOf, &jitScriptSize, &fallbackStubSize);
3762 size += jitScriptSize;
3763 size += fallbackStubSize;
3765 size_t baselineSize = 0;
3766 jit::AddSizeOfBaselineData(script, mallocSizeOf, &baselineSize);
3767 size += baselineSize;
3769 size += jit::SizeOfIonData(script, mallocSizeOf);
3772 MOZ_ASSERT(size > 0);
3773 return size;
3776 const char* JS::ubi::Concrete<BaseScript>::scriptFilename() const {
3777 return get().filename();