Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / JSScript.cpp
blob539f9eb55b2f83fa3aee2971b7c7cc29c8e1ec0b
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::LimitedColumnNumberOneOrigin, JS::ColumnNumberOneOrigin, 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/JSFunction.h"
70 #include "vm/JSObject.h"
71 #include "vm/JSONPrinter.h" // JSONPrinter
72 #include "vm/Opcodes.h"
73 #include "vm/PortableBaselineInterpret.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/JSContext-inl.h"
89 #include "vm/JSObject-inl.h"
90 #include "vm/SharedImmutableStringsCache-inl.h"
91 #include "vm/Stack-inl.h"
93 using namespace js;
95 using mozilla::CheckedInt;
96 using mozilla::Maybe;
97 using mozilla::PodCopy;
98 using mozilla::PointerRangeSize;
99 using mozilla::Utf8AsUnsignedChars;
100 using mozilla::Utf8Unit;
102 using JS::CompileOptions;
103 using JS::ReadOnlyCompileOptions;
104 using JS::SourceText;
106 bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime* rt) const {
107 return jitCodeRaw() == rt->jitRuntime()->interpreterStub().value;
110 js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const {
111 return MaybeForwarded(sourceObject())->source();
114 void js::BaseScript::setEnclosingScript(BaseScript* enclosingScript) {
115 MOZ_ASSERT(enclosingScript);
116 warmUpData_.initEnclosingScript(enclosingScript);
119 void js::BaseScript::setEnclosingScope(Scope* enclosingScope) {
120 if (warmUpData_.isEnclosingScript()) {
121 warmUpData_.clearEnclosingScript();
124 MOZ_ASSERT(enclosingScope);
125 warmUpData_.initEnclosingScope(enclosingScope);
128 void js::BaseScript::finalize(JS::GCContext* gcx) {
129 // Scripts with bytecode may have optional data stored in per-runtime or
130 // per-zone maps. Note that a failed compilation must not have entries since
131 // the script itself will not be marked as having bytecode.
132 if (hasBytecode()) {
133 JSScript* script = this->asJSScript();
135 if (coverage::IsLCovEnabled()) {
136 coverage::CollectScriptCoverage(script, true);
139 script->destroyScriptCounts();
143 JSRuntime* rt = gcx->runtime();
144 if (rt->hasJitRuntime() && rt->jitRuntime()->hasInterpreterEntryMap()) {
145 rt->jitRuntime()->getInterpreterEntryMap()->remove(this);
148 rt->geckoProfiler().onScriptFinalized(this);
151 #ifdef MOZ_VTUNE
152 if (zone()->scriptVTuneIdMap) {
153 // Note: we should only get here if the VTune JIT profiler is running.
154 zone()->scriptVTuneIdMap->remove(this);
156 #endif
158 if (warmUpData_.isJitScript()) {
159 JSScript* script = this->asJSScript();
160 #ifdef JS_CACHEIR_SPEW
161 maybeUpdateWarmUpCount(script);
162 #endif
163 script->releaseJitScriptOnFinalize(gcx);
166 #ifdef JS_CACHEIR_SPEW
167 if (hasBytecode()) {
168 maybeSpewScriptFinalWarmUpCount(this->asJSScript());
170 #endif
172 if (data_) {
173 // We don't need to triger any barriers here, just free the memory.
174 size_t size = data_->allocationSize();
175 AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size,
176 MemCheckKind::MakeNoAccess);
177 gcx->free_(this, data_, size, MemoryUse::ScriptPrivateData);
180 freeSharedData();
183 js::Scope* js::BaseScript::releaseEnclosingScope() {
184 Scope* enclosing = warmUpData_.toEnclosingScope();
185 warmUpData_.clearEnclosingScope();
186 return enclosing;
189 void js::BaseScript::swapData(UniquePtr<PrivateScriptData>& other) {
190 if (data_) {
191 RemoveCellMemory(this, data_->allocationSize(),
192 MemoryUse::ScriptPrivateData);
195 PrivateScriptData* old = data_;
196 data_.set(zone(), other.release());
197 other.reset(old);
199 if (data_) {
200 AddCellMemory(this, data_->allocationSize(), MemoryUse::ScriptPrivateData);
204 js::Scope* js::BaseScript::enclosingScope() const {
205 MOZ_ASSERT(!warmUpData_.isEnclosingScript(),
206 "Enclosing scope is not computed yet");
208 if (warmUpData_.isEnclosingScope()) {
209 return warmUpData_.toEnclosingScope();
212 MOZ_ASSERT(data_, "Script doesn't seem to be compiled");
214 return gcthings()[js::GCThingIndex::outermostScopeIndex()]
215 .as<Scope>()
216 .enclosing();
219 size_t JSScript::numAlwaysLiveFixedSlots() const {
220 if (bodyScope()->is<js::FunctionScope>()) {
221 return bodyScope()->as<js::FunctionScope>().nextFrameSlot();
223 if (bodyScope()->is<js::ModuleScope>()) {
224 return bodyScope()->as<js::ModuleScope>().nextFrameSlot();
226 if (bodyScope()->is<js::EvalScope>() &&
227 bodyScope()->kind() == ScopeKind::StrictEval) {
228 return bodyScope()->as<js::EvalScope>().nextFrameSlot();
230 return 0;
233 unsigned JSScript::numArgs() const {
234 if (bodyScope()->is<js::FunctionScope>()) {
235 return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters();
237 return 0;
240 bool JSScript::functionHasParameterExprs() const {
241 // Only functions have parameters.
242 js::Scope* scope = bodyScope();
243 if (!scope->is<js::FunctionScope>()) {
244 return false;
246 return scope->as<js::FunctionScope>().hasParameterExprs();
249 bool JSScript::isModule() const { return bodyScope()->is<js::ModuleScope>(); }
251 js::ModuleObject* JSScript::module() const {
252 MOZ_ASSERT(isModule());
253 return bodyScope()->as<js::ModuleScope>().module();
256 bool JSScript::isGlobalCode() const {
257 return bodyScope()->is<js::GlobalScope>();
260 js::VarScope* JSScript::functionExtraBodyVarScope() const {
261 MOZ_ASSERT(functionHasExtraBodyVarScope());
262 for (JS::GCCellPtr gcThing : gcthings()) {
263 if (!gcThing.is<js::Scope>()) {
264 continue;
266 js::Scope* scope = &gcThing.as<js::Scope>();
267 if (scope->kind() == js::ScopeKind::FunctionBodyVar) {
268 return &scope->as<js::VarScope>();
271 MOZ_CRASH("Function extra body var scope not found");
274 bool JSScript::needsBodyEnvironment() const {
275 for (JS::GCCellPtr gcThing : gcthings()) {
276 if (!gcThing.is<js::Scope>()) {
277 continue;
279 js::Scope* scope = &gcThing.as<js::Scope>();
280 if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) {
281 return true;
284 return false;
287 bool JSScript::isDirectEvalInFunction() const {
288 if (!isForEval()) {
289 return false;
291 return bodyScope()->hasOnChain(js::ScopeKind::Function);
294 // Initialize the optional arrays in the trailing allocation. This is a set of
295 // offsets that delimit each optional array followed by the arrays themselves.
296 // See comment before 'ImmutableScriptData' for more details.
297 void ImmutableScriptData::initOptionalArrays(Offset* pcursor,
298 uint32_t numResumeOffsets,
299 uint32_t numScopeNotes,
300 uint32_t numTryNotes) {
301 Offset cursor = (*pcursor);
303 // The byte arrays must have already been padded.
304 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor),
305 "Bytecode and source notes should be padded to keep alignment");
307 // Each non-empty optional array needs will need an offset to its end.
308 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
309 unsigned(numScopeNotes > 0) +
310 unsigned(numTryNotes > 0);
312 // Default-initialize the optional-offsets.
313 initElements<Offset>(cursor, numOptionalArrays);
314 cursor += numOptionalArrays * sizeof(Offset);
316 // Offset between optional-offsets table and the optional arrays. This is
317 // later used to access the optional-offsets table as well as first optional
318 // array.
319 optArrayOffset_ = cursor;
321 // Each optional array that follows must store an end-offset in the offset
322 // table. Assign table entries by using this 'offsetIndex'. The index 0 is
323 // reserved for implicit value 'optArrayOffset'.
324 int offsetIndex = 0;
326 // Default-initialize optional 'resumeOffsets'.
327 MOZ_ASSERT(resumeOffsetsOffset() == cursor);
328 if (numResumeOffsets > 0) {
329 initElements<uint32_t>(cursor, numResumeOffsets);
330 cursor += numResumeOffsets * sizeof(uint32_t);
331 setOptionalOffset(++offsetIndex, cursor);
333 flagsRef().resumeOffsetsEndIndex = offsetIndex;
335 // Default-initialize optional 'scopeNotes'.
336 MOZ_ASSERT(scopeNotesOffset() == cursor);
337 if (numScopeNotes > 0) {
338 initElements<ScopeNote>(cursor, numScopeNotes);
339 cursor += numScopeNotes * sizeof(ScopeNote);
340 setOptionalOffset(++offsetIndex, cursor);
342 flagsRef().scopeNotesEndIndex = offsetIndex;
344 // Default-initialize optional 'tryNotes'
345 MOZ_ASSERT(tryNotesOffset() == cursor);
346 if (numTryNotes > 0) {
347 initElements<TryNote>(cursor, numTryNotes);
348 cursor += numTryNotes * sizeof(TryNote);
349 setOptionalOffset(++offsetIndex, cursor);
351 flagsRef().tryNotesEndIndex = offsetIndex;
353 MOZ_ASSERT(endOffset() == cursor);
354 (*pcursor) = cursor;
357 ImmutableScriptData::ImmutableScriptData(uint32_t codeLength,
358 uint32_t noteLength,
359 uint32_t numResumeOffsets,
360 uint32_t numScopeNotes,
361 uint32_t numTryNotes)
362 : codeLength_(codeLength) {
363 // Variable-length data begins immediately after ImmutableScriptData itself.
364 Offset cursor = sizeof(ImmutableScriptData);
366 // The following arrays are byte-aligned with additional padding to ensure
367 // that together they maintain uint32_t-alignment.
369 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
371 // Zero-initialize 'flags'
372 MOZ_ASSERT(isAlignedOffset<Flags>(cursor));
373 new (offsetToPointer<void>(cursor)) Flags{};
374 cursor += sizeof(Flags);
376 initElements<jsbytecode>(cursor, codeLength);
377 cursor += codeLength * sizeof(jsbytecode);
379 initElements<SrcNote>(cursor, noteLength);
380 cursor += noteLength * sizeof(SrcNote);
382 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
385 // Initialization for remaining arrays.
386 initOptionalArrays(&cursor, numResumeOffsets, numScopeNotes, numTryNotes);
388 // Check that we correctly recompute the expected values.
389 MOZ_ASSERT(this->codeLength() == codeLength);
390 MOZ_ASSERT(this->noteLength() == noteLength);
392 // Sanity check
393 MOZ_ASSERT(endOffset() == cursor);
396 void js::FillImmutableFlagsFromCompileOptionsForTopLevel(
397 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
398 using ImmutableFlags = ImmutableScriptFlagsEnum;
400 js::FillImmutableFlagsFromCompileOptionsForFunction(options, flags);
402 flags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
403 flags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
406 void js::FillImmutableFlagsFromCompileOptionsForFunction(
407 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
408 using ImmutableFlags = ImmutableScriptFlagsEnum;
410 flags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
411 flags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode());
412 flags.setFlag(ImmutableFlags::HasNonSyntacticScope,
413 options.nonSyntacticScope);
416 // Check if flags matches to compile options for flags set by
417 // FillImmutableFlagsFromCompileOptionsForTopLevel above.
418 bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions& options,
419 ImmutableScriptFlags flags) {
420 using ImmutableFlags = ImmutableScriptFlagsEnum;
422 bool selfHosted = !!(flags & uint32_t(ImmutableFlags::SelfHosted));
423 bool forceStrict = !!(flags & uint32_t(ImmutableFlags::ForceStrict));
424 bool hasNonSyntacticScope =
425 !!(flags & uint32_t(ImmutableFlags::HasNonSyntacticScope));
426 bool noScriptRval = !!(flags & uint32_t(ImmutableFlags::NoScriptRval));
427 bool treatAsRunOnce = !!(flags & uint32_t(ImmutableFlags::TreatAsRunOnce));
429 return options.selfHostingMode == selfHosted &&
430 options.noScriptRval == noScriptRval &&
431 options.isRunOnce == treatAsRunOnce &&
432 options.forceStrictMode() == forceStrict &&
433 options.nonSyntacticScope == hasNonSyntacticScope;
436 JS_PUBLIC_API bool JS::CheckCompileOptionsMatch(
437 const ReadOnlyCompileOptions& options, JSScript* script) {
438 return js::CheckCompileOptionsMatch(options, script->immutableFlags());
441 bool JSScript::initScriptCounts(JSContext* cx) {
442 MOZ_ASSERT(!hasScriptCounts());
444 // Record all pc which are the first instruction of a basic block.
445 mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets;
447 js::BytecodeLocation main = mainLocation();
448 AllBytecodesIterable iterable(this);
449 for (auto& loc : iterable) {
450 if (loc.isJumpTarget() || loc == main) {
451 if (!jumpTargets.append(loc.toRawBytecode())) {
452 ReportOutOfMemory(cx);
453 return false;
458 // Initialize all PCCounts counters to 0.
459 ScriptCounts::PCCountsVector base;
460 if (!base.reserve(jumpTargets.length())) {
461 ReportOutOfMemory(cx);
462 return false;
465 for (size_t i = 0; i < jumpTargets.length(); i++) {
466 base.infallibleEmplaceBack(pcToOffset(jumpTargets[i]));
469 // Create zone's scriptCountsMap if necessary.
470 if (!zone()->scriptCountsMap) {
471 auto map = cx->make_unique<ScriptCountsMap>();
472 if (!map) {
473 return false;
476 zone()->scriptCountsMap = std::move(map);
479 // Allocate the ScriptCounts.
480 UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base));
481 if (!sc) {
482 return false;
485 MOZ_ASSERT(this->hasBytecode());
487 // Register the current ScriptCounts in the zone's map.
488 if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) {
489 ReportOutOfMemory(cx);
490 return false;
493 // safe to set this; we can't fail after this point.
494 setHasScriptCounts();
496 // Enable interrupts in any interpreter frames running on this script. This
497 // is used to let the interpreter increment the PCCounts, if present.
498 for (ActivationIterator iter(cx); !iter.done(); ++iter) {
499 if (iter->isInterpreter()) {
500 iter->asInterpreter()->enableInterruptsIfRunning(this);
504 return true;
507 static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) {
508 MOZ_ASSERT(script->hasScriptCounts());
509 ScriptCountsMap::Ptr p = script->zone()->scriptCountsMap->lookup(script);
510 MOZ_ASSERT(p);
511 return p;
514 ScriptCounts& JSScript::getScriptCounts() {
515 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
516 return *p->value();
519 js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) {
520 PCCounts searched = PCCounts(offset);
521 PCCounts* elem =
522 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
523 if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
524 return nullptr;
526 return elem;
529 const js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) const {
530 PCCounts searched = PCCounts(offset);
531 const PCCounts* elem =
532 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
533 if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
534 return nullptr;
536 return elem;
539 js::PCCounts* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset) {
540 PCCounts searched = PCCounts(offset);
541 PCCounts* elem =
542 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
543 if (elem == pcCounts_.end()) {
544 return &pcCounts_.back();
546 if (elem->pcOffset() == offset) {
547 return elem;
549 if (elem != pcCounts_.begin()) {
550 return elem - 1;
552 return nullptr;
555 const js::PCCounts* ScriptCounts::maybeGetThrowCounts(size_t offset) const {
556 PCCounts searched = PCCounts(offset);
557 const PCCounts* elem =
558 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
559 if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
560 return nullptr;
562 return elem;
565 const js::PCCounts* ScriptCounts::getImmediatePrecedingThrowCounts(
566 size_t offset) const {
567 PCCounts searched = PCCounts(offset);
568 const PCCounts* elem =
569 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
570 if (elem == throwCounts_.end()) {
571 if (throwCounts_.begin() == throwCounts_.end()) {
572 return nullptr;
574 return &throwCounts_.back();
576 if (elem->pcOffset() == offset) {
577 return elem;
579 if (elem != throwCounts_.begin()) {
580 return elem - 1;
582 return nullptr;
585 js::PCCounts* ScriptCounts::getThrowCounts(size_t offset) {
586 PCCounts searched = PCCounts(offset);
587 PCCounts* elem =
588 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
589 if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
590 elem = throwCounts_.insert(elem, searched);
592 return elem;
595 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
596 size_t size = mallocSizeOf(this);
597 size += pcCounts_.sizeOfExcludingThis(mallocSizeOf);
598 size += throwCounts_.sizeOfExcludingThis(mallocSizeOf);
599 if (ionCounts_) {
600 size += ionCounts_->sizeOfIncludingThis(mallocSizeOf);
602 return size;
605 js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
606 MOZ_ASSERT(containsPC(pc));
607 return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
610 const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
611 MOZ_ASSERT(containsPC(pc));
612 return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc));
615 js::PCCounts* JSScript::getThrowCounts(jsbytecode* pc) {
616 MOZ_ASSERT(containsPC(pc));
617 return getScriptCounts().getThrowCounts(pcToOffset(pc));
620 uint64_t JSScript::getHitCount(jsbytecode* pc) {
621 MOZ_ASSERT(containsPC(pc));
622 if (pc < main()) {
623 pc = main();
626 ScriptCounts& sc = getScriptCounts();
627 size_t targetOffset = pcToOffset(pc);
628 const js::PCCounts* baseCount =
629 sc.getImmediatePrecedingPCCounts(targetOffset);
630 if (!baseCount) {
631 return 0;
633 if (baseCount->pcOffset() == targetOffset) {
634 return baseCount->numExec();
636 MOZ_ASSERT(baseCount->pcOffset() < targetOffset);
637 uint64_t count = baseCount->numExec();
638 do {
639 const js::PCCounts* throwCount =
640 sc.getImmediatePrecedingThrowCounts(targetOffset);
641 if (!throwCount) {
642 return count;
644 if (throwCount->pcOffset() <= baseCount->pcOffset()) {
645 return count;
647 count -= throwCount->numExec();
648 targetOffset = throwCount->pcOffset() - 1;
649 } while (true);
652 void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) {
653 ScriptCounts& sc = getScriptCounts();
654 if (sc.ionCounts_) {
655 ionCounts->setPrevious(sc.ionCounts_);
657 sc.ionCounts_ = ionCounts;
660 jit::IonScriptCounts* JSScript::getIonCounts() {
661 return getScriptCounts().ionCounts_;
664 void JSScript::releaseScriptCounts(ScriptCounts* counts) {
665 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
666 *counts = std::move(*p->value().get());
667 zone()->scriptCountsMap->remove(p);
668 clearHasScriptCounts();
671 void JSScript::destroyScriptCounts() {
672 if (hasScriptCounts()) {
673 ScriptCounts scriptCounts;
674 releaseScriptCounts(&scriptCounts);
678 void JSScript::resetScriptCounts() {
679 if (!hasScriptCounts()) {
680 return;
683 ScriptCounts& sc = getScriptCounts();
685 for (PCCounts& elem : sc.pcCounts_) {
686 elem.numExec() = 0;
689 for (PCCounts& elem : sc.throwCounts_) {
690 elem.numExec() = 0;
694 void ScriptSourceObject::finalize(JS::GCContext* gcx, JSObject* obj) {
695 MOZ_ASSERT(gcx->onMainThread());
696 ScriptSourceObject* sso = &obj->as<ScriptSourceObject>();
697 sso->source()->Release();
699 // Clear the private value, calling the release hook if necessary.
700 sso->setPrivate(gcx->runtime(), UndefinedValue());
703 static const JSClassOps ScriptSourceObjectClassOps = {
704 nullptr, // addProperty
705 nullptr, // delProperty
706 nullptr, // enumerate
707 nullptr, // newEnumerate
708 nullptr, // resolve
709 nullptr, // mayResolve
710 ScriptSourceObject::finalize, // finalize
711 nullptr, // call
712 nullptr, // construct
713 nullptr, // trace
716 const JSClass ScriptSourceObject::class_ = {
717 "ScriptSource",
718 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
719 &ScriptSourceObjectClassOps};
721 ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
722 ScriptSource* source) {
723 ScriptSourceObject* obj =
724 NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr);
725 if (!obj) {
726 return nullptr;
729 // The matching decref is in ScriptSourceObject::finalize.
730 obj->initReservedSlot(SOURCE_SLOT, PrivateValue(do_AddRef(source).take()));
732 // The slots below should be populated by a call to initFromOptions. Poison
733 // them.
734 obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC));
735 obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC));
737 return obj;
740 [[nodiscard]] static bool MaybeValidateFilename(
741 JSContext* cx, Handle<ScriptSourceObject*> sso,
742 const JS::InstantiateOptions& options) {
743 if (!gFilenameValidationCallback) {
744 return true;
747 const char* filename = sso->source()->filename();
748 if (!filename || options.skipFilenameValidation) {
749 return true;
752 if (gFilenameValidationCallback(cx, filename)) {
753 return true;
756 const char* utf8Filename;
757 if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename))) {
758 utf8Filename = filename;
759 } else {
760 utf8Filename = "(invalid UTF-8 filename)";
762 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME,
763 utf8Filename);
764 return false;
767 /* static */
768 bool ScriptSourceObject::initFromOptions(
769 JSContext* cx, Handle<ScriptSourceObject*> source,
770 const JS::InstantiateOptions& options) {
771 cx->releaseCheck(source);
772 MOZ_ASSERT(
773 source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC));
774 MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT)
775 .isMagic(JS_GENERIC_MAGIC));
777 if (!MaybeValidateFilename(cx, source, options)) {
778 return false;
781 if (options.deferDebugMetadata) {
782 return true;
785 // Initialize the element attribute slot and introduction script slot
786 // this marks the SSO as initialized for asserts.
788 RootedString elementAttributeName(cx);
789 if (!initElementProperties(cx, source, elementAttributeName)) {
790 return false;
793 RootedValue introductionScript(cx);
794 source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
796 return true;
799 /* static */
800 bool ScriptSourceObject::initElementProperties(
801 JSContext* cx, Handle<ScriptSourceObject*> source,
802 HandleString elementAttrName) {
803 RootedValue nameValue(cx);
804 if (elementAttrName) {
805 nameValue = StringValue(elementAttrName);
807 if (!cx->compartment()->wrap(cx, &nameValue)) {
808 return false;
811 source->setReservedSlot(ELEMENT_PROPERTY_SLOT, nameValue);
813 return true;
816 void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) {
817 // Update the private value, calling addRef/release hooks if necessary
818 // to allow the embedding to maintain a reference count for the
819 // private data.
820 JS::AutoSuppressGCAnalysis nogc;
821 Value prevValue = getReservedSlot(PRIVATE_SLOT);
822 rt->releaseScriptPrivate(prevValue);
823 setReservedSlot(PRIVATE_SLOT, value);
824 rt->addRefScriptPrivate(value);
827 void ScriptSourceObject::clearPrivate(JSRuntime* rt) {
828 // Clear the private value, calling release hook if necessary.
829 // |this| may be gray, be careful not to create edges to it.
830 JS::AutoSuppressGCAnalysis nogc;
831 Value prevValue = getReservedSlot(PRIVATE_SLOT);
832 rt->releaseScriptPrivate(prevValue);
833 getSlotRef(PRIVATE_SLOT).setUndefinedUnchecked();
836 class ScriptSource::LoadSourceMatcher {
837 JSContext* const cx_;
838 ScriptSource* const ss_;
839 bool* const loaded_;
841 public:
842 explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded)
843 : cx_(cx), ss_(ss), loaded_(loaded) {}
845 template <typename Unit, SourceRetrievable CanRetrieve>
846 bool operator()(const Compressed<Unit, CanRetrieve>&) const {
847 *loaded_ = true;
848 return true;
851 template <typename Unit, SourceRetrievable CanRetrieve>
852 bool operator()(const Uncompressed<Unit, CanRetrieve>&) const {
853 *loaded_ = true;
854 return true;
857 template <typename Unit>
858 bool operator()(const Retrievable<Unit>&) {
859 if (!cx_->runtime()->sourceHook.ref()) {
860 *loaded_ = false;
861 return true;
864 size_t length;
866 // The first argument is just for overloading -- its value doesn't matter.
867 if (!tryLoadAndSetSource(Unit('0'), &length)) {
868 return false;
871 return true;
874 bool operator()(const Missing&) const {
875 *loaded_ = false;
876 return true;
879 private:
880 bool tryLoadAndSetSource(const Utf8Unit&, size_t* length) const {
881 char* utf8Source;
882 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr,
883 &utf8Source, length)) {
884 return false;
887 if (!utf8Source) {
888 *loaded_ = false;
889 return true;
892 if (!ss_->setRetrievedSource(
893 cx_, EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)),
894 *length)) {
895 return false;
898 *loaded_ = true;
899 return true;
902 bool tryLoadAndSetSource(const char16_t&, size_t* length) const {
903 char16_t* utf16Source;
904 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source,
905 nullptr, length)) {
906 return false;
909 if (!utf16Source) {
910 *loaded_ = false;
911 return true;
914 if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source),
915 *length)) {
916 return false;
919 *loaded_ = true;
920 return true;
924 /* static */
925 bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) {
926 return ss->data.match(LoadSourceMatcher(cx, ss, loaded));
929 /* static */
930 JSLinearString* JSScript::sourceData(JSContext* cx, HandleScript script) {
931 MOZ_ASSERT(script->scriptSource()->hasSourceText());
932 return script->scriptSource()->substring(cx, script->sourceStart(),
933 script->sourceEnd());
936 bool BaseScript::appendSourceDataForToString(JSContext* cx, StringBuffer& buf) {
937 MOZ_ASSERT(scriptSource()->hasSourceText());
938 return scriptSource()->appendSubstring(cx, buf, toStringStart(),
939 toStringEnd());
942 void UncompressedSourceCache::holdEntry(AutoHoldEntry& holder,
943 const ScriptSourceChunk& ssc) {
944 MOZ_ASSERT(!holder_);
945 holder.holdEntry(this, ssc);
946 holder_ = &holder;
949 void UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder) {
950 MOZ_ASSERT(holder_ == &holder);
951 holder_ = nullptr;
954 template <typename Unit>
955 const Unit* UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc,
956 AutoHoldEntry& holder) {
957 MOZ_ASSERT(!holder_);
958 MOZ_ASSERT(ssc.ss->isCompressed<Unit>());
960 if (!map_) {
961 return nullptr;
964 if (Map::Ptr p = map_->lookup(ssc)) {
965 holdEntry(holder, ssc);
966 return static_cast<const Unit*>(p->value().get());
969 return nullptr;
972 bool UncompressedSourceCache::put(const ScriptSourceChunk& ssc, SourceData data,
973 AutoHoldEntry& holder) {
974 MOZ_ASSERT(!holder_);
976 if (!map_) {
977 map_ = MakeUnique<Map>();
978 if (!map_) {
979 return false;
983 if (!map_->put(ssc, std::move(data))) {
984 return false;
987 holdEntry(holder, ssc);
988 return true;
991 void UncompressedSourceCache::purge() {
992 if (!map_) {
993 return;
996 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
997 if (holder_ && r.front().key() == holder_->sourceChunk()) {
998 holder_->deferDelete(std::move(r.front().value()));
999 holder_ = nullptr;
1003 map_ = nullptr;
1006 size_t UncompressedSourceCache::sizeOfExcludingThis(
1007 mozilla::MallocSizeOf mallocSizeOf) {
1008 size_t n = 0;
1009 if (map_ && !map_->empty()) {
1010 n += map_->shallowSizeOfIncludingThis(mallocSizeOf);
1011 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
1012 n += mallocSizeOf(r.front().value().get());
1015 return n;
1018 template <typename Unit>
1019 const Unit* ScriptSource::chunkUnits(
1020 JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
1021 size_t chunk) {
1022 const CompressedData<Unit>& c = *compressedData<Unit>();
1024 ScriptSourceChunk ssc(this, chunk);
1025 if (const Unit* decompressed =
1026 cx->caches().uncompressedSourceCache.lookup<Unit>(ssc, holder)) {
1027 return decompressed;
1030 size_t totalLengthInBytes = length() * sizeof(Unit);
1031 size_t chunkBytes = Compressor::chunkSize(totalLengthInBytes, chunk);
1033 MOZ_ASSERT((chunkBytes % sizeof(Unit)) == 0);
1034 const size_t chunkLength = chunkBytes / sizeof(Unit);
1035 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(chunkLength));
1036 if (!decompressed) {
1037 JS_ReportOutOfMemory(cx);
1038 return nullptr;
1041 // Compression treats input and output memory as plain ol' bytes. These
1042 // reinterpret_cast<>s accord exactly with that.
1043 if (!DecompressStringChunk(
1044 reinterpret_cast<const unsigned char*>(c.raw.chars()), chunk,
1045 reinterpret_cast<unsigned char*>(decompressed.get()), chunkBytes)) {
1046 JS_ReportOutOfMemory(cx);
1047 return nullptr;
1050 const Unit* ret = decompressed.get();
1051 if (!cx->caches().uncompressedSourceCache.put(
1052 ssc, ToSourceData(std::move(decompressed)), holder)) {
1053 JS_ReportOutOfMemory(cx);
1054 return nullptr;
1056 return ret;
1059 template <typename Unit>
1060 void ScriptSource::convertToCompressedSource(SharedImmutableString compressed,
1061 size_t uncompressedLength) {
1062 MOZ_ASSERT(isUncompressed<Unit>());
1063 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
1065 if (data.is<Uncompressed<Unit, SourceRetrievable::Yes>>()) {
1066 data = SourceType(Compressed<Unit, SourceRetrievable::Yes>(
1067 std::move(compressed), uncompressedLength));
1068 } else {
1069 data = SourceType(Compressed<Unit, SourceRetrievable::No>(
1070 std::move(compressed), uncompressedLength));
1074 template <typename Unit>
1075 void ScriptSource::performDelayedConvertToCompressedSource(
1076 ExclusiveData<ReaderInstances>::Guard& g) {
1077 // There might not be a conversion to compressed source happening at all.
1078 if (g->pendingCompressed.empty()) {
1079 return;
1082 CompressedData<Unit>& pending =
1083 g->pendingCompressed.ref<CompressedData<Unit>>();
1085 convertToCompressedSource<Unit>(std::move(pending.raw),
1086 pending.uncompressedLength);
1088 g->pendingCompressed.destroy();
1091 void ScriptSource::PinnedUnitsBase::addReader() {
1092 auto guard = source_->readers_.lock();
1093 guard->count++;
1096 template <typename Unit>
1097 void ScriptSource::PinnedUnitsBase::removeReader() {
1098 // Note: We use a Mutex with Exclusive access, such that no PinnedUnits
1099 // instance is live while we are compressing the source.
1100 auto guard = source_->readers_.lock();
1101 MOZ_ASSERT(guard->count > 0);
1102 if (--guard->count) {
1103 source_->performDelayedConvertToCompressedSource<Unit>(guard);
1107 template <typename Unit>
1108 ScriptSource::PinnedUnits<Unit>::~PinnedUnits() {
1109 if (units_) {
1110 removeReader<Unit>();
1114 template <typename Unit>
1115 ScriptSource::PinnedUnitsIfUncompressed<Unit>::~PinnedUnitsIfUncompressed() {
1116 if (units_) {
1117 removeReader<Unit>();
1121 template <typename Unit>
1122 const Unit* ScriptSource::units(JSContext* cx,
1123 UncompressedSourceCache::AutoHoldEntry& holder,
1124 size_t begin, size_t len) {
1125 MOZ_ASSERT(begin <= length());
1126 MOZ_ASSERT(begin + len <= length());
1128 if (isUncompressed<Unit>()) {
1129 const Unit* units = uncompressedData<Unit>()->units();
1130 if (!units) {
1131 return nullptr;
1133 return units + begin;
1136 if (data.is<Missing>()) {
1137 MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source");
1140 if (data.is<Retrievable<Unit>>()) {
1141 MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
1144 MOZ_ASSERT(isCompressed<Unit>());
1146 // Determine first/last chunks, the offset (in bytes) into the first chunk
1147 // of the requested units, and the number of bytes in the last chunk.
1149 // Note that first and last chunk sizes are miscomputed and *must not be
1150 // used* when the first chunk is the last chunk.
1151 size_t firstChunk, firstChunkOffset, firstChunkSize;
1152 size_t lastChunk, lastChunkSize;
1153 Compressor::rangeToChunkAndOffset(
1154 begin * sizeof(Unit), (begin + len) * sizeof(Unit), &firstChunk,
1155 &firstChunkOffset, &firstChunkSize, &lastChunk, &lastChunkSize);
1156 MOZ_ASSERT(firstChunk <= lastChunk);
1157 MOZ_ASSERT(firstChunkOffset % sizeof(Unit) == 0);
1158 MOZ_ASSERT(firstChunkSize % sizeof(Unit) == 0);
1160 size_t firstUnit = firstChunkOffset / sizeof(Unit);
1162 // Directly return units within a single chunk. UncompressedSourceCache
1163 // and |holder| will hold the units alive past function return.
1164 if (firstChunk == lastChunk) {
1165 const Unit* units = chunkUnits<Unit>(cx, holder, firstChunk);
1166 if (!units) {
1167 return nullptr;
1170 return units + firstUnit;
1173 // Otherwise the units span multiple chunks. Copy successive chunks'
1174 // decompressed units into freshly-allocated memory to return.
1175 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(len));
1176 if (!decompressed) {
1177 JS_ReportOutOfMemory(cx);
1178 return nullptr;
1181 Unit* cursor;
1184 // |AutoHoldEntry| is single-shot, and a holder successfully filled in
1185 // by |chunkUnits| must be destroyed before another can be used. Thus
1186 // we can't use |holder| with |chunkUnits| when |chunkUnits| is used
1187 // with multiple chunks, and we must use and destroy distinct, fresh
1188 // holders for each chunk.
1189 UncompressedSourceCache::AutoHoldEntry firstHolder;
1190 const Unit* units = chunkUnits<Unit>(cx, firstHolder, firstChunk);
1191 if (!units) {
1192 return nullptr;
1195 cursor = std::copy_n(units + firstUnit, firstChunkSize / sizeof(Unit),
1196 decompressed.get());
1199 for (size_t i = firstChunk + 1; i < lastChunk; i++) {
1200 UncompressedSourceCache::AutoHoldEntry chunkHolder;
1201 const Unit* units = chunkUnits<Unit>(cx, chunkHolder, i);
1202 if (!units) {
1203 return nullptr;
1206 cursor = std::copy_n(units, Compressor::CHUNK_SIZE / sizeof(Unit), cursor);
1210 UncompressedSourceCache::AutoHoldEntry lastHolder;
1211 const Unit* units = chunkUnits<Unit>(cx, lastHolder, lastChunk);
1212 if (!units) {
1213 return nullptr;
1216 cursor = std::copy_n(units, lastChunkSize / sizeof(Unit), cursor);
1219 MOZ_ASSERT(PointerRangeSize(decompressed.get(), cursor) == len);
1221 // Transfer ownership to |holder|.
1222 const Unit* ret = decompressed.get();
1223 holder.holdUnits(std::move(decompressed));
1224 return ret;
1227 template <typename Unit>
1228 const Unit* ScriptSource::uncompressedUnits(size_t begin, size_t len) {
1229 MOZ_ASSERT(begin <= length());
1230 MOZ_ASSERT(begin + len <= length());
1232 if (!isUncompressed<Unit>()) {
1233 return nullptr;
1236 const Unit* units = uncompressedData<Unit>()->units();
1237 if (!units) {
1238 return nullptr;
1240 return units + begin;
1243 template <typename Unit>
1244 ScriptSource::PinnedUnits<Unit>::PinnedUnits(
1245 JSContext* cx, ScriptSource* source,
1246 UncompressedSourceCache::AutoHoldEntry& holder, size_t begin, size_t len)
1247 : PinnedUnitsBase(source) {
1248 MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type");
1250 units_ = source->units<Unit>(cx, holder, begin, len);
1251 if (units_) {
1252 addReader();
1256 template class ScriptSource::PinnedUnits<Utf8Unit>;
1257 template class ScriptSource::PinnedUnits<char16_t>;
1259 template <typename Unit>
1260 ScriptSource::PinnedUnitsIfUncompressed<Unit>::PinnedUnitsIfUncompressed(
1261 ScriptSource* source, size_t begin, size_t len)
1262 : PinnedUnitsBase(source) {
1263 MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type");
1265 units_ = source->uncompressedUnits<Unit>(begin, len);
1266 if (units_) {
1267 addReader();
1271 template class ScriptSource::PinnedUnitsIfUncompressed<Utf8Unit>;
1272 template class ScriptSource::PinnedUnitsIfUncompressed<char16_t>;
1274 JSLinearString* ScriptSource::substring(JSContext* cx, size_t start,
1275 size_t stop) {
1276 MOZ_ASSERT(start <= stop);
1278 size_t len = stop - start;
1279 if (!len) {
1280 return cx->emptyString();
1282 UncompressedSourceCache::AutoHoldEntry holder;
1284 // UTF-8 source text.
1285 if (hasSourceType<Utf8Unit>()) {
1286 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
1287 if (!units.asChars()) {
1288 return nullptr;
1291 const char* str = units.asChars();
1292 return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len));
1295 // UTF-16 source text.
1296 PinnedUnits<char16_t> units(cx, this, holder, start, len);
1297 if (!units.asChars()) {
1298 return nullptr;
1301 return NewStringCopyN<CanGC>(cx, units.asChars(), len);
1304 JSLinearString* ScriptSource::substringDontDeflate(JSContext* cx, size_t start,
1305 size_t stop) {
1306 MOZ_ASSERT(start <= stop);
1308 size_t len = stop - start;
1309 if (!len) {
1310 return cx->emptyString();
1312 UncompressedSourceCache::AutoHoldEntry holder;
1314 // UTF-8 source text.
1315 if (hasSourceType<Utf8Unit>()) {
1316 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
1317 if (!units.asChars()) {
1318 return nullptr;
1321 const char* str = units.asChars();
1323 // There doesn't appear to be a non-deflating UTF-8 string creation
1324 // function -- but then again, it's not entirely clear how current
1325 // callers benefit from non-deflation.
1326 return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len));
1329 // UTF-16 source text.
1330 PinnedUnits<char16_t> units(cx, this, holder, start, len);
1331 if (!units.asChars()) {
1332 return nullptr;
1335 return NewStringCopyNDontDeflate<CanGC>(cx, units.asChars(), len);
1338 bool ScriptSource::appendSubstring(JSContext* cx, StringBuffer& buf,
1339 size_t start, size_t stop) {
1340 MOZ_ASSERT(start <= stop);
1342 size_t len = stop - start;
1343 UncompressedSourceCache::AutoHoldEntry holder;
1345 if (hasSourceType<Utf8Unit>()) {
1346 PinnedUnits<Utf8Unit> pinned(cx, this, holder, start, len);
1347 if (!pinned.get()) {
1348 return false;
1350 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
1351 return false;
1354 const Utf8Unit* units = pinned.get();
1355 return buf.append(units, len);
1356 } else {
1357 PinnedUnits<char16_t> pinned(cx, this, holder, start, len);
1358 if (!pinned.get()) {
1359 return false;
1361 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
1362 return false;
1365 const char16_t* units = pinned.get();
1366 return buf.append(units, len);
1370 JSLinearString* ScriptSource::functionBodyString(JSContext* cx) {
1371 MOZ_ASSERT(isFunctionBody());
1373 size_t start = parameterListEnd_ + FunctionConstructorMedialSigils.length();
1374 size_t stop = length() - FunctionConstructorFinalBrace.length();
1375 return substring(cx, start, stop);
1378 template <typename ContextT, typename Unit>
1379 [[nodiscard]] bool ScriptSource::setUncompressedSourceHelper(
1380 ContextT* cx, EntryUnits<Unit>&& source, size_t length,
1381 SourceRetrievable retrievable) {
1382 auto& cache = SharedImmutableStringsCache::getSingleton();
1384 auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source));
1385 auto deduped = cache.getOrCreate(std::move(uniqueChars), length);
1386 if (!deduped) {
1387 ReportOutOfMemory(cx);
1388 return false;
1391 if (retrievable == SourceRetrievable::Yes) {
1392 data = SourceType(
1393 Uncompressed<Unit, SourceRetrievable::Yes>(std::move(deduped)));
1394 } else {
1395 data = SourceType(
1396 Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
1398 return true;
1401 template <typename Unit>
1402 [[nodiscard]] bool ScriptSource::setRetrievedSource(JSContext* cx,
1403 EntryUnits<Unit>&& source,
1404 size_t length) {
1405 MOZ_ASSERT(data.is<Retrievable<Unit>>(),
1406 "retrieved source can only overwrite the corresponding "
1407 "retrievable source");
1408 return setUncompressedSourceHelper(cx, std::move(source), length,
1409 SourceRetrievable::Yes);
1412 bool js::IsOffThreadSourceCompressionEnabled() {
1413 // If we don't have concurrent execution compression will contend with
1414 // main-thread execution, in which case we disable. Similarly we don't want to
1415 // block the thread pool if it is too small.
1416 return GetHelperThreadCPUCount() > 1 && GetHelperThreadCount() > 1 &&
1417 CanUseExtraThreads();
1420 bool ScriptSource::tryCompressOffThread(JSContext* cx) {
1421 // Beware: |js::SynchronouslyCompressSource| assumes that this function is
1422 // only called once, just after a script has been compiled, and it's never
1423 // called at some random time after that. If multiple calls of this can ever
1424 // occur, that function may require changes.
1426 // The SourceCompressionTask needs to record the major GC number for
1427 // scheduling.
1428 MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
1430 // If source compression was already attempted, do not queue a new task.
1431 if (hadCompressionTask_) {
1432 return true;
1435 if (!hasUncompressedSource()) {
1436 // This excludes compressed, missing, and retrievable source.
1437 return true;
1440 // There are several cases where source compression is not a good idea:
1441 // - If the script is tiny, then compression will save little or no space.
1442 // - If there is only one core, then compression will contend with JS
1443 // execution (which hurts benchmarketing).
1445 // Otherwise, enqueue a compression task to be processed when a major
1446 // GC is requested.
1448 if (length() < ScriptSource::MinimumCompressibleLength ||
1449 !IsOffThreadSourceCompressionEnabled()) {
1450 return true;
1453 // Heap allocate the task. It will be freed upon compression
1454 // completing in AttachFinishedCompressedSources.
1455 auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
1456 if (!task) {
1457 ReportOutOfMemory(cx);
1458 return false;
1460 return EnqueueOffThreadCompression(cx, std::move(task));
1463 template <typename Unit>
1464 void ScriptSource::triggerConvertToCompressedSource(
1465 SharedImmutableString compressed, size_t uncompressedLength) {
1466 MOZ_ASSERT(isUncompressed<Unit>(),
1467 "should only be triggering compressed source installation to "
1468 "overwrite identically-encoded uncompressed source");
1469 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
1471 // If units aren't pinned -- and they probably won't be, we'd have to have a
1472 // GC in the small window of time where a |PinnedUnits| was live -- then we
1473 // can immediately convert.
1475 auto guard = readers_.lock();
1476 if (MOZ_LIKELY(!guard->count)) {
1477 convertToCompressedSource<Unit>(std::move(compressed),
1478 uncompressedLength);
1479 return;
1482 // Otherwise, set aside the compressed-data info. The conversion is
1483 // performed when the last |PinnedUnits| dies.
1484 MOZ_ASSERT(guard->pendingCompressed.empty(),
1485 "shouldn't be multiple conversions happening");
1486 guard->pendingCompressed.construct<CompressedData<Unit>>(
1487 std::move(compressed), uncompressedLength);
1491 template <typename Unit>
1492 [[nodiscard]] bool ScriptSource::initializeWithUnretrievableCompressedSource(
1493 FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
1494 size_t sourceLength) {
1495 MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing");
1496 MOZ_ASSERT(compressed != nullptr);
1498 auto& cache = SharedImmutableStringsCache::getSingleton();
1499 auto deduped = cache.getOrCreate(std::move(compressed), rawLength);
1500 if (!deduped) {
1501 ReportOutOfMemory(fc);
1502 return false;
1505 #ifdef DEBUG
1507 auto guard = readers_.lock();
1508 MOZ_ASSERT(
1509 guard->count == 0,
1510 "shouldn't be initializing a ScriptSource while its characters "
1511 "are pinned -- that only makes sense with a ScriptSource actively "
1512 "being inspected");
1514 #endif
1516 data = SourceType(Compressed<Unit, SourceRetrievable::No>(std::move(deduped),
1517 sourceLength));
1519 return true;
1522 template bool ScriptSource::initializeWithUnretrievableCompressedSource<
1523 Utf8Unit>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
1524 size_t sourceLength);
1525 template bool ScriptSource::initializeWithUnretrievableCompressedSource<
1526 char16_t>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
1527 size_t sourceLength);
1529 template <typename Unit>
1530 bool ScriptSource::assignSource(FrontendContext* fc,
1531 const ReadOnlyCompileOptions& options,
1532 SourceText<Unit>& srcBuf) {
1533 MOZ_ASSERT(data.is<Missing>(),
1534 "source assignment should only occur on fresh ScriptSources");
1536 mutedErrors_ = options.mutedErrors();
1537 delazificationMode_ = options.eagerDelazificationStrategy();
1539 if (options.discardSource) {
1540 return true;
1543 if (options.sourceIsLazy) {
1544 data = SourceType(Retrievable<Unit>());
1545 return true;
1548 auto& cache = SharedImmutableStringsCache::getSingleton();
1549 auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() {
1550 using CharT = typename SourceTypeTraits<Unit>::CharT;
1551 return srcBuf.ownsUnits()
1552 ? UniquePtr<CharT[], JS::FreePolicy>(srcBuf.takeChars())
1553 : DuplicateString(srcBuf.get(), srcBuf.length());
1555 if (!deduped) {
1556 ReportOutOfMemory(fc);
1557 return false;
1560 data =
1561 SourceType(Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
1562 return true;
1565 template bool ScriptSource::assignSource(FrontendContext* fc,
1566 const ReadOnlyCompileOptions& options,
1567 SourceText<char16_t>& srcBuf);
1568 template bool ScriptSource::assignSource(FrontendContext* fc,
1569 const ReadOnlyCompileOptions& options,
1570 SourceText<Utf8Unit>& srcBuf);
1572 [[nodiscard]] static bool reallocUniquePtr(UniqueChars& unique, size_t size) {
1573 auto newPtr = static_cast<char*>(js_realloc(unique.get(), size));
1574 if (!newPtr) {
1575 return false;
1578 // Since the realloc succeeded, unique is now holding a freed pointer.
1579 (void)unique.release();
1580 unique.reset(newPtr);
1581 return true;
1584 template <typename Unit>
1585 void SourceCompressionTask::workEncodingSpecific() {
1586 MOZ_ASSERT(source_->isUncompressed<Unit>());
1588 // Try to keep the maximum memory usage down by only allocating half the
1589 // size of the string, first.
1590 size_t inputBytes = source_->length() * sizeof(Unit);
1591 size_t firstSize = inputBytes / 2;
1592 UniqueChars compressed(js_pod_malloc<char>(firstSize));
1593 if (!compressed) {
1594 return;
1597 const Unit* chars = source_->uncompressedData<Unit>()->units();
1598 Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes);
1599 if (!comp.init()) {
1600 return;
1603 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize);
1604 bool cont = true;
1605 bool reallocated = false;
1606 while (cont) {
1607 if (shouldCancel()) {
1608 return;
1611 switch (comp.compressMore()) {
1612 case Compressor::CONTINUE:
1613 break;
1614 case Compressor::MOREOUTPUT: {
1615 if (reallocated) {
1616 // The compressed string is longer than the original string.
1617 return;
1620 // The compressed output is greater than half the size of the
1621 // original string. Reallocate to the full size.
1622 if (!reallocUniquePtr(compressed, inputBytes)) {
1623 return;
1626 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()),
1627 inputBytes);
1628 reallocated = true;
1629 break;
1631 case Compressor::DONE:
1632 cont = false;
1633 break;
1634 case Compressor::OOM:
1635 return;
1639 size_t totalBytes = comp.totalBytesNeeded();
1641 // Shrink the buffer to the size of the compressed data.
1642 if (!reallocUniquePtr(compressed, totalBytes)) {
1643 return;
1646 comp.finish(compressed.get(), totalBytes);
1648 if (shouldCancel()) {
1649 return;
1652 auto& strings = SharedImmutableStringsCache::getSingleton();
1653 resultString_ = strings.getOrCreate(std::move(compressed), totalBytes);
1656 struct SourceCompressionTask::PerformTaskWork {
1657 SourceCompressionTask* const task_;
1659 explicit PerformTaskWork(SourceCompressionTask* task) : task_(task) {}
1661 template <typename Unit, SourceRetrievable CanRetrieve>
1662 void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) {
1663 task_->workEncodingSpecific<Unit>();
1666 template <typename T>
1667 void operator()(const T&) {
1668 MOZ_CRASH(
1669 "why are we compressing missing, missing-but-retrievable, "
1670 "or already-compressed source?");
1674 void ScriptSource::performTaskWork(SourceCompressionTask* task) {
1675 MOZ_ASSERT(hasUncompressedSource());
1676 data.match(SourceCompressionTask::PerformTaskWork(task));
1679 void SourceCompressionTask::runTask() {
1680 if (shouldCancel()) {
1681 return;
1684 MOZ_ASSERT(source_->hasUncompressedSource());
1686 source_->performTaskWork(this);
1689 void SourceCompressionTask::runHelperThreadTask(
1690 AutoLockHelperThreadState& locked) {
1692 AutoUnlockHelperThreadState unlock(locked);
1693 this->runTask();
1697 AutoEnterOOMUnsafeRegion oomUnsafe;
1698 if (!HelperThreadState().compressionFinishedList(locked).append(this)) {
1699 oomUnsafe.crash("SourceCompressionTask::runHelperThreadTask");
1704 void ScriptSource::triggerConvertToCompressedSourceFromTask(
1705 SharedImmutableString compressed) {
1706 data.match(TriggerConvertToCompressedSourceFromTask(this, compressed));
1709 void SourceCompressionTask::complete() {
1710 if (!shouldCancel() && resultString_) {
1711 source_->triggerConvertToCompressedSourceFromTask(std::move(resultString_));
1715 bool js::SynchronouslyCompressSource(JSContext* cx,
1716 JS::Handle<BaseScript*> script) {
1717 // Finish all pending source compressions, including the single compression
1718 // task that may have been created (by |ScriptSource::tryCompressOffThread|)
1719 // just after the script was compiled. Because we have flushed this queue,
1720 // no code below needs to synchronize with an off-thread parse task that
1721 // assumes the immutability of a |ScriptSource|'s data.
1723 // This *may* end up compressing |script|'s source. If it does -- we test
1724 // this below -- that takes care of things. But if it doesn't, we will
1725 // synchronously compress ourselves (and as noted above, this won't race
1726 // anything).
1727 RunPendingSourceCompressions(cx->runtime());
1729 ScriptSource* ss = script->scriptSource();
1730 #ifdef DEBUG
1732 auto guard = ss->readers_.lock();
1733 MOZ_ASSERT(guard->count == 0,
1734 "can't synchronously compress while source units are in use");
1736 #endif
1738 // In principle a previously-triggered compression on a helper thread could
1739 // have already completed. If that happens, there's nothing more to do.
1740 if (ss->hasCompressedSource()) {
1741 return true;
1744 MOZ_ASSERT(ss->hasUncompressedSource(),
1745 "shouldn't be compressing uncompressible source");
1747 // Use an explicit scope to delineate the lifetime of |task|, for simplicity.
1749 #ifdef DEBUG
1750 uint32_t sourceRefs = ss->refs;
1751 #endif
1752 MOZ_ASSERT(sourceRefs > 0, "at least |script| here should have a ref");
1754 // |SourceCompressionTask::shouldCancel| can periodically result in source
1755 // compression being canceled if we're not careful. Guarantee that two refs
1756 // to |ss| are always live in this function (at least one preexisting and
1757 // one held by the task) so that compression is never canceled.
1758 auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), ss);
1759 if (!task) {
1760 ReportOutOfMemory(cx);
1761 return false;
1764 MOZ_ASSERT(ss->refs > sourceRefs, "must have at least two refs now");
1766 // Attempt to compress. This may not succeed if OOM happens, but (because
1767 // it ordinarily happens on a helper thread) no error will ever be set here.
1768 MOZ_ASSERT(!cx->isExceptionPending());
1769 ss->performTaskWork(task.get());
1770 MOZ_ASSERT(!cx->isExceptionPending());
1772 // Convert |ss| from uncompressed to compressed data.
1773 task->complete();
1775 MOZ_ASSERT(!cx->isExceptionPending());
1778 // The only way source won't be compressed here is if OOM happened.
1779 return ss->hasCompressedSource();
1782 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
1783 JS::ScriptSourceInfo* info) const {
1784 info->misc += mallocSizeOf(this);
1785 info->numScripts++;
1788 bool ScriptSource::startIncrementalEncoding(
1789 JSContext* cx,
1790 UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
1791 // We don't support asm.js in XDR.
1792 // Encoding failures are reported by the xdrFinalizeEncoder function.
1793 if (initial->asmJS) {
1794 return true;
1797 // Remove the reference to the source, to avoid the circular reference.
1798 initial->source = nullptr;
1800 AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime);
1801 auto failureCase = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(); });
1803 if (!xdrEncoder_.setInitial(
1804 cx, std::forward<UniquePtr<frontend::ExtensibleCompilationStencil>>(
1805 initial))) {
1806 // On encoding failure, let failureCase destroy encoder and return true
1807 // to avoid failing any currently executing script.
1808 return false;
1811 failureCase.release();
1812 return true;
1815 bool ScriptSource::addDelazificationToIncrementalEncoding(
1816 JSContext* cx, const frontend::CompilationStencil& stencil) {
1817 MOZ_ASSERT(hasEncoder());
1818 AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime);
1819 auto failureCase = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(); });
1821 if (!xdrEncoder_.addDelazification(cx, stencil)) {
1822 // On encoding failure, let failureCase destroy encoder and return true
1823 // to avoid failing any currently executing script.
1824 return false;
1827 failureCase.release();
1828 return true;
1831 bool ScriptSource::xdrFinalizeEncoder(JSContext* cx,
1832 JS::TranscodeBuffer& buffer) {
1833 if (!hasEncoder()) {
1834 JS_ReportErrorASCII(cx, "XDR encoding failure");
1835 return false;
1838 auto cleanup = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(); });
1840 AutoReportFrontendContext fc(cx);
1841 XDRStencilEncoder encoder(&fc, buffer);
1843 frontend::BorrowingCompilationStencil borrowingStencil(
1844 xdrEncoder_.merger_->getResult());
1845 XDRResult res = encoder.codeStencil(this, borrowingStencil);
1846 if (res.isErr()) {
1847 if (JS::IsTranscodeFailureResult(res.unwrapErr())) {
1848 fc.clearAutoReport();
1849 JS_ReportErrorASCII(cx, "XDR encoding failure");
1851 return false;
1853 return true;
1856 void ScriptSource::xdrAbortEncoder() { xdrEncoder_.reset(); }
1858 template <typename Unit>
1859 [[nodiscard]] bool ScriptSource::initializeUnretrievableUncompressedSource(
1860 FrontendContext* fc, EntryUnits<Unit>&& source, size_t length) {
1861 MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource");
1862 return setUncompressedSourceHelper(fc, std::move(source), length,
1863 SourceRetrievable::No);
1866 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1867 FrontendContext* fc, EntryUnits<Utf8Unit>&& source, size_t length);
1868 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1869 FrontendContext* fc, EntryUnits<char16_t>&& source, size_t length);
1871 // Format and return a cx->pod_malloc'ed URL for a generated script like:
1872 // {filename} line {lineno} > {introducer}
1873 // For example:
1874 // foo.js line 7 > eval
1875 // indicating code compiled by the call to 'eval' on line 7 of foo.js.
1876 UniqueChars js::FormatIntroducedFilename(const char* filename, uint32_t lineno,
1877 const char* introducer) {
1878 // Compute the length of the string in advance, so we can allocate a
1879 // buffer of the right size on the first shot.
1881 // (JS_smprintf would be perfect, as that allocates the result
1882 // dynamically as it formats the string, but it won't allocate from cx,
1883 // and wants us to use a special free function.)
1884 char linenoBuf[15];
1885 size_t filenameLen = strlen(filename);
1886 size_t linenoLen = SprintfLiteral(linenoBuf, "%u", lineno);
1887 size_t introducerLen = strlen(introducer);
1888 size_t len = filenameLen + 6 /* == strlen(" line ") */ + linenoLen +
1889 3 /* == strlen(" > ") */ + introducerLen + 1 /* \0 */;
1890 UniqueChars formatted(js_pod_malloc<char>(len));
1891 if (!formatted) {
1892 return nullptr;
1895 mozilla::DebugOnly<size_t> checkLen = snprintf(
1896 formatted.get(), len, "%s line %s > %s", filename, linenoBuf, introducer);
1897 MOZ_ASSERT(checkLen == len - 1);
1899 return formatted;
1902 bool ScriptSource::initFromOptions(FrontendContext* fc,
1903 const ReadOnlyCompileOptions& options) {
1904 MOZ_ASSERT(!filename_);
1905 MOZ_ASSERT(!introducerFilename_);
1907 mutedErrors_ = options.mutedErrors();
1908 delazificationMode_ = options.eagerDelazificationStrategy();
1910 startLine_ = options.lineno;
1911 startColumn_ = JS::LimitedColumnNumberOneOrigin::fromUnlimited(
1912 JS::ColumnNumberOneOrigin(options.column));
1913 introductionType_ = options.introductionType;
1914 setIntroductionOffset(options.introductionOffset);
1915 // The parameterListEnd_ is initialized later by setParameterListEnd, before
1916 // we expose any scripts that use this ScriptSource to the debugger.
1918 if (options.hasIntroductionInfo) {
1919 MOZ_ASSERT(options.introductionType != nullptr);
1920 const char* filename =
1921 options.filename() ? options.filename().c_str() : "<unknown>";
1922 UniqueChars formatted = FormatIntroducedFilename(
1923 filename, options.introductionLineno, options.introductionType);
1924 if (!formatted) {
1925 ReportOutOfMemory(fc);
1926 return false;
1928 if (!setFilename(fc, std::move(formatted))) {
1929 return false;
1931 } else if (options.filename()) {
1932 if (!setFilename(fc, options.filename().c_str())) {
1933 return false;
1937 if (options.introducerFilename()) {
1938 if (!setIntroducerFilename(fc, options.introducerFilename().c_str())) {
1939 return false;
1943 return true;
1946 // Use the SharedImmutableString map to deduplicate input string. The input
1947 // string must be null-terminated.
1948 template <typename SharedT, typename CharT>
1949 static SharedT GetOrCreateStringZ(FrontendContext* fc,
1950 UniquePtr<CharT[], JS::FreePolicy>&& str) {
1951 size_t lengthWithNull = std::char_traits<CharT>::length(str.get()) + 1;
1952 auto res = SharedImmutableStringsCache::getSingleton().getOrCreate(
1953 std::move(str), lengthWithNull);
1954 if (!res) {
1955 ReportOutOfMemory(fc);
1957 return res;
1960 SharedImmutableString ScriptSource::getOrCreateStringZ(FrontendContext* fc,
1961 UniqueChars&& str) {
1962 return GetOrCreateStringZ<SharedImmutableString>(fc, std::move(str));
1965 SharedImmutableTwoByteString ScriptSource::getOrCreateStringZ(
1966 FrontendContext* fc, UniqueTwoByteChars&& str) {
1967 return GetOrCreateStringZ<SharedImmutableTwoByteString>(fc, std::move(str));
1970 bool ScriptSource::setFilename(FrontendContext* fc, const char* filename) {
1971 UniqueChars owned = DuplicateString(fc, filename);
1972 if (!owned) {
1973 return false;
1975 return setFilename(fc, std::move(owned));
1978 bool ScriptSource::setFilename(FrontendContext* fc, UniqueChars&& filename) {
1979 MOZ_ASSERT(!filename_);
1980 filename_ = getOrCreateStringZ(fc, std::move(filename));
1981 if (filename_) {
1982 filenameHash_ =
1983 mozilla::HashStringKnownLength(filename_.chars(), filename_.length());
1984 return true;
1986 return false;
1989 bool ScriptSource::setIntroducerFilename(FrontendContext* fc,
1990 const char* filename) {
1991 UniqueChars owned = DuplicateString(fc, filename);
1992 if (!owned) {
1993 return false;
1995 return setIntroducerFilename(fc, std::move(owned));
1998 bool ScriptSource::setIntroducerFilename(FrontendContext* fc,
1999 UniqueChars&& filename) {
2000 MOZ_ASSERT(!introducerFilename_);
2001 introducerFilename_ = getOrCreateStringZ(fc, std::move(filename));
2002 return bool(introducerFilename_);
2005 bool ScriptSource::setDisplayURL(FrontendContext* fc, const char16_t* url) {
2006 UniqueTwoByteChars owned = DuplicateString(fc, url);
2007 if (!owned) {
2008 return false;
2010 return setDisplayURL(fc, std::move(owned));
2013 bool ScriptSource::setDisplayURL(FrontendContext* fc,
2014 UniqueTwoByteChars&& url) {
2015 MOZ_ASSERT(!hasDisplayURL());
2016 MOZ_ASSERT(url);
2017 if (url[0] == '\0') {
2018 return true;
2021 displayURL_ = getOrCreateStringZ(fc, std::move(url));
2022 return bool(displayURL_);
2025 bool ScriptSource::setSourceMapURL(FrontendContext* fc, const char16_t* url) {
2026 UniqueTwoByteChars owned = DuplicateString(fc, url);
2027 if (!owned) {
2028 return false;
2030 return setSourceMapURL(fc, std::move(owned));
2033 bool ScriptSource::setSourceMapURL(FrontendContext* fc,
2034 UniqueTwoByteChars&& url) {
2035 MOZ_ASSERT(url);
2036 if (url[0] == '\0') {
2037 return true;
2040 sourceMapURL_ = getOrCreateStringZ(fc, std::move(url));
2041 return bool(sourceMapURL_);
2044 /* static */ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent>
2045 ScriptSource::idCount_;
2048 * [SMDOC] JSScript data layout (immutable)
2050 * Script data that shareable across processes. There are no pointers (GC or
2051 * otherwise) and the data is relocatable.
2053 * Array elements Pointed to by Length
2054 * -------------- ------------- ------
2055 * jsbytecode code() codeLength()
2056 * jsscrnote notes() noteLength()
2057 * uint32_t resumeOffsets()
2058 * ScopeNote scopeNotes()
2059 * TryNote tryNotes()
2062 /* static */ CheckedInt<uint32_t> ImmutableScriptData::sizeFor(
2063 uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets,
2064 uint32_t numScopeNotes, uint32_t numTryNotes) {
2065 // Take a count of which optional arrays will be used and need offset info.
2066 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
2067 unsigned(numScopeNotes > 0) +
2068 unsigned(numTryNotes > 0);
2070 // Compute size including trailing arrays.
2071 CheckedInt<uint32_t> size = sizeof(ImmutableScriptData);
2072 size += sizeof(Flags);
2073 size += CheckedInt<uint32_t>(codeLength) * sizeof(jsbytecode);
2074 size += CheckedInt<uint32_t>(noteLength) * sizeof(SrcNote);
2075 size += CheckedInt<uint32_t>(numOptionalArrays) * sizeof(Offset);
2076 size += CheckedInt<uint32_t>(numResumeOffsets) * sizeof(uint32_t);
2077 size += CheckedInt<uint32_t>(numScopeNotes) * sizeof(ScopeNote);
2078 size += CheckedInt<uint32_t>(numTryNotes) * sizeof(TryNote);
2080 return size;
2083 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_(
2084 FrontendContext* fc, uint32_t codeLength, uint32_t noteLength,
2085 uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) {
2086 auto size = sizeFor(codeLength, noteLength, numResumeOffsets, numScopeNotes,
2087 numTryNotes);
2088 if (!size.isValid()) {
2089 ReportAllocationOverflow(fc);
2090 return nullptr;
2093 // Allocate contiguous raw buffer.
2094 void* raw = fc->getAllocator()->pod_malloc<uint8_t>(size.value());
2095 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0);
2096 if (!raw) {
2097 return nullptr;
2100 // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but
2101 // GCPtrs are put into a safe state.
2102 UniquePtr<ImmutableScriptData> result(new (raw) ImmutableScriptData(
2103 codeLength, noteLength, numResumeOffsets, numScopeNotes, numTryNotes));
2104 if (!result) {
2105 return nullptr;
2108 // Sanity check
2109 MOZ_ASSERT(result->endOffset() == size.value());
2111 return result;
2114 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_(
2115 FrontendContext* fc, uint32_t totalSize) {
2116 void* raw = fc->getAllocator()->pod_malloc<uint8_t>(totalSize);
2117 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0);
2118 UniquePtr<ImmutableScriptData> result(
2119 reinterpret_cast<ImmutableScriptData*>(raw));
2120 return result;
2123 bool js::ImmutableScriptData::validateLayout(uint32_t expectedSize) {
2124 constexpr size_t HeaderSize = sizeof(js::ImmutableScriptData);
2125 constexpr size_t OptionalOffsetsMaxSize = 3 * sizeof(Offset);
2127 // Check that the optional-offsets array lies within the allocation before we
2128 // try to read from it while computing sizes. Remember that the array *ends*
2129 // at the `optArrayOffset_`.
2130 static_assert(OptionalOffsetsMaxSize <= HeaderSize);
2131 if (HeaderSize > optArrayOffset_) {
2132 return false;
2134 if (optArrayOffset_ > expectedSize) {
2135 return false;
2138 // Round-trip the size computation using `CheckedInt` to detect overflow. This
2139 // should indirectly validate most alignment, size, and ordering requirments.
2140 auto size = sizeFor(codeLength(), noteLength(), resumeOffsets().size(),
2141 scopeNotes().size(), tryNotes().size());
2142 return size.isValid() && (size.value() == expectedSize);
2145 /* static */
2146 SharedImmutableScriptData* SharedImmutableScriptData::create(
2147 FrontendContext* fc) {
2148 return fc->getAllocator()->new_<SharedImmutableScriptData>();
2151 /* static */
2152 SharedImmutableScriptData* SharedImmutableScriptData::createWith(
2153 FrontendContext* fc, js::UniquePtr<ImmutableScriptData>&& isd) {
2154 MOZ_ASSERT(isd.get());
2155 SharedImmutableScriptData* sisd = create(fc);
2156 if (!sisd) {
2157 return nullptr;
2160 sisd->setOwn(std::move(isd));
2161 return sisd;
2164 void JSScript::relazify(JSRuntime* rt) {
2165 js::Scope* scope = enclosingScope();
2166 UniquePtr<PrivateScriptData> scriptData;
2168 // Any JIT compiles should have been released, so we already point to the
2169 // interpreter trampoline which supports lazy scripts.
2170 MOZ_ASSERT_IF(jit::HasJitBackend(), isUsingInterpreterTrampoline(rt));
2172 // Without bytecode, the script counts are invalid so destroy them if they
2173 // still exist.
2174 destroyScriptCounts();
2176 // Release the bytecode and gcthings list.
2177 // NOTE: We clear the PrivateScriptData to nullptr. This is fine because we
2178 // only allowed relazification (via AllowRelazify) if the original lazy
2179 // script we compiled from had a nullptr PrivateScriptData.
2180 swapData(scriptData);
2181 freeSharedData();
2183 // We should not still be in any side-tables for the debugger or
2184 // code-coverage. The finalizer will not be able to clean them up once
2185 // bytecode is released. We check in JSFunction::maybeRelazify() for these
2186 // conditions before requesting relazification.
2187 MOZ_ASSERT(!coverage::IsLCovEnabled());
2188 MOZ_ASSERT(!hasScriptCounts());
2189 MOZ_ASSERT(!hasDebugScript());
2191 // Rollback warmUpData_ to have enclosingScope.
2192 MOZ_ASSERT(warmUpData_.isWarmUpCount(),
2193 "JitScript should already be released");
2194 warmUpData_.resetWarmUpCount(0);
2195 warmUpData_.initEnclosingScope(scope);
2197 MOZ_ASSERT(isReadyForDelazification());
2200 // Takes ownership of the passed SharedImmutableScriptData and either adds it
2201 // into the runtime's SharedImmutableScriptDataTable, or frees it if a matching
2202 // entry already exists and replaces the passed RefPtr with the existing entry.
2203 /* static */
2204 bool SharedImmutableScriptData::shareScriptData(
2205 FrontendContext* fc, RefPtr<SharedImmutableScriptData>& sisd) {
2206 MOZ_ASSERT(sisd);
2207 MOZ_ASSERT(sisd->refCount() == 1);
2209 SharedImmutableScriptData* data = sisd.get();
2211 SharedImmutableScriptData::Hasher::Lookup lookup(data);
2213 Maybe<AutoLockGlobalScriptData> lock;
2214 js::SharedImmutableScriptDataTable& table =
2215 fc->scriptDataTableHolder()->getMaybeLocked(lock);
2217 SharedImmutableScriptDataTable::AddPtr p = table.lookupForAdd(lookup);
2218 if (p) {
2219 MOZ_ASSERT(data != *p);
2220 sisd = *p;
2221 } else {
2222 if (!table.add(p, data)) {
2223 ReportOutOfMemory(fc);
2224 return false;
2227 // Being in the table counts as a reference on the script data.
2228 data->AddRef();
2231 // Refs: sisd argument, SharedImmutableScriptDataTable
2232 MOZ_ASSERT(sisd->refCount() >= 2);
2234 return true;
2237 static void SweepScriptDataTable(SharedImmutableScriptDataTable& table) {
2238 // Entries are removed from the table when their reference count is one,
2239 // i.e. when the only reference to them is from the table entry.
2241 for (SharedImmutableScriptDataTable::Enum e(table); !e.empty();
2242 e.popFront()) {
2243 SharedImmutableScriptData* sharedData = e.front();
2244 if (sharedData->refCount() == 1) {
2245 sharedData->Release();
2246 e.removeFront();
2251 void js::SweepScriptData(JSRuntime* rt) {
2252 SweepScriptDataTable(rt->scriptDataTableHolder().getWithoutLock());
2254 AutoLockGlobalScriptData lock;
2255 SweepScriptDataTable(js::globalSharedScriptDataTableHolder.get(lock));
2258 inline size_t PrivateScriptData::allocationSize() const { return endOffset(); }
2260 // Initialize and placement-new the trailing arrays.
2261 PrivateScriptData::PrivateScriptData(uint32_t ngcthings)
2262 : ngcthings(ngcthings) {
2263 // Variable-length data begins immediately after PrivateScriptData itself.
2264 // NOTE: Alignment is computed using cursor/offset so the alignment of
2265 // PrivateScriptData must be stricter than any trailing array type.
2266 Offset cursor = sizeof(PrivateScriptData);
2268 // Layout and initialize the gcthings array.
2270 initElements<JS::GCCellPtr>(cursor, ngcthings);
2271 cursor += ngcthings * sizeof(JS::GCCellPtr);
2274 // Sanity check.
2275 MOZ_ASSERT(endOffset() == cursor);
2278 /* static */
2279 PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings) {
2280 // Compute size including trailing arrays.
2281 CheckedInt<Offset> size = sizeof(PrivateScriptData);
2282 size += CheckedInt<Offset>(ngcthings) * sizeof(JS::GCCellPtr);
2283 if (!size.isValid()) {
2284 ReportAllocationOverflow(cx);
2285 return nullptr;
2288 // Allocate contiguous raw buffer for the trailing arrays.
2289 void* raw = cx->pod_malloc<uint8_t>(size.value());
2290 MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0);
2291 if (!raw) {
2292 return nullptr;
2295 // Constuct the PrivateScriptData. Trailing arrays are uninitialized but
2296 // GCPtrs are put into a safe state.
2297 PrivateScriptData* result = new (raw) PrivateScriptData(ngcthings);
2298 if (!result) {
2299 return nullptr;
2302 // Sanity check.
2303 MOZ_ASSERT(result->endOffset() == size.value());
2305 return result;
2308 /* static */
2309 bool PrivateScriptData::InitFromStencil(
2310 JSContext* cx, js::HandleScript script,
2311 const js::frontend::CompilationAtomCache& atomCache,
2312 const js::frontend::CompilationStencil& stencil,
2313 js::frontend::CompilationGCOutput& gcOutput,
2314 const js::frontend::ScriptIndex scriptIndex) {
2315 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex];
2316 uint32_t ngcthings = scriptStencil.gcThingsLength;
2318 MOZ_ASSERT(ngcthings <= INDEX_LIMIT);
2320 // Create and initialize PrivateScriptData
2321 if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) {
2322 return false;
2325 js::PrivateScriptData* data = script->data_;
2326 if (ngcthings) {
2327 if (!EmitScriptThingsVector(cx, atomCache, stencil, gcOutput,
2328 scriptStencil.gcthings(stencil),
2329 data->gcthings())) {
2330 return false;
2334 return true;
2337 void PrivateScriptData::trace(JSTracer* trc) {
2338 for (JS::GCCellPtr& elem : gcthings()) {
2339 TraceManuallyBarrieredGCCellPtr(trc, &elem, "script-gcthing");
2343 /*static*/
2344 JSScript* JSScript::Create(JSContext* cx, JS::Handle<JSFunction*> function,
2345 js::Handle<ScriptSourceObject*> sourceObject,
2346 const SourceExtent& extent,
2347 js::ImmutableScriptFlags flags) {
2348 return static_cast<JSScript*>(
2349 BaseScript::New(cx, function, sourceObject, extent, flags));
2352 #ifdef MOZ_VTUNE
2353 uint32_t JSScript::vtuneMethodID() {
2354 if (!zone()->scriptVTuneIdMap) {
2355 auto map = MakeUnique<ScriptVTuneIdMap>();
2356 if (!map) {
2357 MOZ_CRASH("Failed to allocate ScriptVTuneIdMap");
2360 zone()->scriptVTuneIdMap = std::move(map);
2363 ScriptVTuneIdMap::AddPtr p = zone()->scriptVTuneIdMap->lookupForAdd(this);
2364 if (p) {
2365 return p->value();
2368 MOZ_ASSERT(this->hasBytecode());
2370 uint32_t id = vtune::GenerateUniqueMethodID();
2371 if (!zone()->scriptVTuneIdMap->add(p, this, id)) {
2372 MOZ_CRASH("Failed to add vtune method id");
2375 return id;
2377 #endif
2379 /* static */
2380 bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script,
2381 uint32_t ngcthings) {
2382 cx->check(script);
2384 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings));
2385 if (!data) {
2386 return false;
2389 script->swapData(data);
2390 MOZ_ASSERT(!data);
2392 return true;
2395 /* static */
2396 bool JSScript::fullyInitFromStencil(
2397 JSContext* cx, const js::frontend::CompilationAtomCache& atomCache,
2398 const js::frontend::CompilationStencil& stencil,
2399 frontend::CompilationGCOutput& gcOutput, HandleScript script,
2400 const js::frontend::ScriptIndex scriptIndex) {
2401 MutableScriptFlags lazyMutableFlags;
2402 Rooted<Scope*> lazyEnclosingScope(cx);
2404 // A holder for the lazy PrivateScriptData that we must keep around in case
2405 // this process fails and we must return the script to its original state.
2407 // This is initialized by BaseScript::swapData() which will run pre-barriers
2408 // for us. On successful conversion to non-lazy script, the old script data
2409 // here will be released by the UniquePtr.
2410 Rooted<UniquePtr<PrivateScriptData>> lazyData(cx);
2412 // Whether we are a newborn script or an existing lazy script, we should
2413 // already be pointing to the interpreter trampoline.
2414 MOZ_ASSERT_IF(jit::HasJitBackend(),
2415 script->isUsingInterpreterTrampoline(cx->runtime()));
2417 // If we are using an existing lazy script, record enough info to be able to
2418 // rollback on failure.
2419 if (script->isReadyForDelazification()) {
2420 lazyMutableFlags = script->mutableFlags_;
2421 lazyEnclosingScope = script->releaseEnclosingScope();
2422 script->swapData(lazyData.get());
2423 MOZ_ASSERT(script->sharedData_ == nullptr);
2426 // Restore the script to lazy state on failure. If this was a fresh script, we
2427 // just need to clear bytecode to mark script as incomplete.
2428 auto rollbackGuard = mozilla::MakeScopeExit([&] {
2429 if (lazyEnclosingScope) {
2430 script->mutableFlags_ = lazyMutableFlags;
2431 script->warmUpData_.initEnclosingScope(lazyEnclosingScope);
2432 script->swapData(lazyData.get());
2433 script->sharedData_ = nullptr;
2435 MOZ_ASSERT(script->isReadyForDelazification());
2436 } else {
2437 script->sharedData_ = nullptr;
2441 // The counts of indexed things must be checked during code generation.
2442 MOZ_ASSERT(stencil.scriptData[scriptIndex].gcThingsLength <= INDEX_LIMIT);
2444 // Note: These flags should already be correct when the BaseScript was
2445 // allocated.
2446 MOZ_ASSERT_IF(stencil.isInitialStencil(),
2447 script->immutableFlags() ==
2448 stencil.scriptExtra[scriptIndex].immutableFlags);
2450 // Create and initialize PrivateScriptData
2451 if (!PrivateScriptData::InitFromStencil(cx, script, atomCache, stencil,
2452 gcOutput, scriptIndex)) {
2453 return false;
2456 // Member-initializer data is computed in initial parse only. If we are
2457 // delazifying, make sure to copy it off the `lazyData` before we throw it
2458 // away.
2459 if (script->useMemberInitializers()) {
2460 if (stencil.isInitialStencil()) {
2461 MemberInitializers initializers(
2462 stencil.scriptExtra[scriptIndex].memberInitializers());
2463 script->setMemberInitializers(initializers);
2464 } else {
2465 script->setMemberInitializers(lazyData.get()->getMemberInitializers());
2468 auto* scriptData = stencil.sharedData.get(scriptIndex);
2469 script->initSharedData(scriptData);
2471 // NOTE: JSScript is now constructed and should be linked in.
2472 rollbackGuard.release();
2474 // Link Scope -> JSFunction -> BaseScript.
2475 if (script->isFunction()) {
2476 JSFunction* fun = gcOutput.getFunction(scriptIndex);
2477 script->bodyScope()->as<FunctionScope>().initCanonicalFunction(fun);
2478 if (fun->isIncomplete()) {
2479 fun->initScript(script);
2480 } else if (fun->hasSelfHostedLazyScript()) {
2481 fun->clearSelfHostedLazyScript();
2482 fun->initScript(script);
2483 } else {
2484 // We are delazifying in-place.
2485 MOZ_ASSERT(fun->baseScript() == script);
2489 // NOTE: The caller is responsible for linking ModuleObjects if this is a
2490 // module script.
2492 #ifdef JS_STRUCTURED_SPEW
2493 // We want this to happen after line number initialization to allow filtering
2494 // to work.
2495 script->setSpewEnabled(cx->spewer().enabled(script));
2496 #endif
2498 #ifdef DEBUG
2499 script->assertValidJumpTargets();
2500 #endif
2502 if (coverage::IsLCovEnabled()) {
2503 if (!coverage::InitScriptCoverage(cx, script)) {
2504 return false;
2508 return true;
2511 JSScript* JSScript::fromStencil(JSContext* cx,
2512 frontend::CompilationAtomCache& atomCache,
2513 const frontend::CompilationStencil& stencil,
2514 frontend::CompilationGCOutput& gcOutput,
2515 frontend::ScriptIndex scriptIndex) {
2516 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex];
2517 js::frontend::ScriptStencilExtra& scriptExtra =
2518 stencil.scriptExtra[scriptIndex];
2519 MOZ_ASSERT(scriptStencil.hasSharedData(),
2520 "Need generated bytecode to use JSScript::fromStencil");
2522 Rooted<JSFunction*> function(cx);
2523 if (scriptStencil.isFunction()) {
2524 function = gcOutput.getFunction(scriptIndex);
2527 Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);
2528 RootedScript script(cx, Create(cx, function, sourceObject, scriptExtra.extent,
2529 scriptExtra.immutableFlags));
2530 if (!script) {
2531 return nullptr;
2534 if (!fullyInitFromStencil(cx, atomCache, stencil, gcOutput, script,
2535 scriptIndex)) {
2536 return nullptr;
2539 return script;
2542 #ifdef DEBUG
2543 void JSScript::assertValidJumpTargets() const {
2544 BytecodeLocation mainLoc = mainLocation();
2545 BytecodeLocation endLoc = endLocation();
2546 AllBytecodesIterable iter(this);
2547 for (BytecodeLocation loc : iter) {
2548 // Check jump instructions' target.
2549 if (loc.isJump()) {
2550 BytecodeLocation target = loc.getJumpTarget();
2551 MOZ_ASSERT(mainLoc <= target && target < endLoc);
2552 MOZ_ASSERT(target.isJumpTarget());
2554 // All backward jumps must be to a JSOp::LoopHead op. This is an invariant
2555 // we want to maintain to simplify JIT compilation and bytecode analysis.
2556 MOZ_ASSERT_IF(target < loc, target.is(JSOp::LoopHead));
2557 MOZ_ASSERT_IF(target < loc, IsBackedgePC(loc.toRawBytecode()));
2559 // All forward jumps must be to a JSOp::JumpTarget op.
2560 MOZ_ASSERT_IF(target > loc, target.is(JSOp::JumpTarget));
2562 // Jumps must not cross scope boundaries.
2563 MOZ_ASSERT(loc.innermostScope(this) == target.innermostScope(this));
2565 // Check fallthrough of conditional jump instructions.
2566 if (loc.fallsThrough()) {
2567 BytecodeLocation fallthrough = loc.next();
2568 MOZ_ASSERT(mainLoc <= fallthrough && fallthrough < endLoc);
2569 MOZ_ASSERT(fallthrough.isJumpTarget());
2573 // Check table switch case labels.
2574 if (loc.is(JSOp::TableSwitch)) {
2575 BytecodeLocation target = loc.getTableSwitchDefaultTarget();
2577 // Default target.
2578 MOZ_ASSERT(mainLoc <= target && target < endLoc);
2579 MOZ_ASSERT(target.is(JSOp::JumpTarget));
2581 int32_t low = loc.getTableSwitchLow();
2582 int32_t high = loc.getTableSwitchHigh();
2584 for (int i = 0; i < high - low + 1; i++) {
2585 BytecodeLocation switchCase = loc.getTableSwitchCaseTarget(this, i);
2586 MOZ_ASSERT(mainLoc <= switchCase && switchCase < endLoc);
2587 MOZ_ASSERT(switchCase.is(JSOp::JumpTarget));
2592 // Check catch/finally blocks as jump targets.
2593 for (const TryNote& tn : trynotes()) {
2594 if (tn.kind() != TryNoteKind::Catch && tn.kind() != TryNoteKind::Finally) {
2595 continue;
2598 jsbytecode* tryStart = offsetToPC(tn.start);
2599 jsbytecode* tryPc = tryStart - JSOpLength_Try;
2600 MOZ_ASSERT(JSOp(*tryPc) == JSOp::Try);
2602 jsbytecode* tryTarget = tryStart + tn.length;
2603 MOZ_ASSERT(main() <= tryTarget && tryTarget < codeEnd());
2604 MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget)));
2607 #endif
2609 void JSScript::addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf,
2610 size_t* sizeOfJitScript,
2611 size_t* sizeOfAllocSites) const {
2612 if (!hasJitScript()) {
2613 return;
2616 jitScript()->addSizeOfIncludingThis(mallocSizeOf, sizeOfJitScript,
2617 sizeOfAllocSites);
2620 js::GlobalObject& JSScript::uninlinedGlobal() const { return global(); }
2622 unsigned js::PCToLineNumber(unsigned startLine,
2623 JS::LimitedColumnNumberOneOrigin startCol,
2624 SrcNote* notes, SrcNote* notesEnd, jsbytecode* code,
2625 jsbytecode* pc,
2626 JS::LimitedColumnNumberOneOrigin* columnp) {
2627 unsigned lineno = startLine;
2628 JS::LimitedColumnNumberOneOrigin column = startCol;
2631 * Walk through source notes accumulating their deltas, keeping track of
2632 * line-number notes, until we pass the note for pc's offset within
2633 * script->code.
2635 ptrdiff_t offset = 0;
2636 ptrdiff_t target = pc - code;
2637 for (SrcNoteIterator iter(notes, notesEnd); !iter.atEnd(); ++iter) {
2638 const auto* sn = *iter;
2639 offset += sn->delta();
2640 if (offset > target) {
2641 break;
2644 SrcNoteType type = sn->type();
2645 if (type == SrcNoteType::SetLine) {
2646 lineno = SrcNote::SetLine::getLine(sn, startLine);
2647 column = JS::LimitedColumnNumberOneOrigin();
2648 } else if (type == SrcNoteType::SetLineColumn) {
2649 lineno = SrcNote::SetLineColumn::getLine(sn, startLine);
2650 column = SrcNote::SetLineColumn::getColumn(sn);
2651 } else if (type == SrcNoteType::NewLine) {
2652 lineno++;
2653 column = JS::LimitedColumnNumberOneOrigin();
2654 } else if (type == SrcNoteType::NewLineColumn) {
2655 lineno++;
2656 column = SrcNote::NewLineColumn::getColumn(sn);
2657 } else if (type == SrcNoteType::ColSpan) {
2658 column += SrcNote::ColSpan::getSpan(sn);
2662 if (columnp) {
2663 *columnp = column;
2666 return lineno;
2669 unsigned js::PCToLineNumber(JSScript* script, jsbytecode* pc,
2670 JS::LimitedColumnNumberOneOrigin* columnp) {
2671 /* Cope with InterpreterFrame.pc value prior to entering Interpret. */
2672 if (!pc) {
2673 return 0;
2676 return PCToLineNumber(
2677 script->lineno(), JS::LimitedColumnNumberOneOrigin(script->column()),
2678 script->notes(), script->notesEnd(), script->code(), pc, columnp);
2681 jsbytecode* js::LineNumberToPC(JSScript* script, unsigned target) {
2682 ptrdiff_t offset = 0;
2683 ptrdiff_t best = -1;
2684 unsigned lineno = script->lineno();
2685 unsigned bestdiff = SrcNote::MaxOperand;
2686 for (SrcNoteIterator iter(script->notes(), script->notesEnd()); !iter.atEnd();
2687 ++iter) {
2688 const auto* sn = *iter;
2690 * Exact-match only if offset is not in the prologue; otherwise use
2691 * nearest greater-or-equal line number match.
2693 if (lineno == target && offset >= ptrdiff_t(script->mainOffset())) {
2694 goto out;
2696 if (lineno >= target) {
2697 unsigned diff = lineno - target;
2698 if (diff < bestdiff) {
2699 bestdiff = diff;
2700 best = offset;
2703 offset += sn->delta();
2704 SrcNoteType type = sn->type();
2705 if (type == SrcNoteType::SetLine) {
2706 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
2707 } else if (type == SrcNoteType::SetLineColumn) {
2708 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
2709 } else if (type == SrcNoteType::NewLine ||
2710 type == SrcNoteType::NewLineColumn) {
2711 lineno++;
2714 if (best >= 0) {
2715 offset = best;
2717 out:
2718 return script->offsetToPC(offset);
2721 JS_PUBLIC_API unsigned js::GetScriptLineExtent(JSScript* script) {
2722 unsigned lineno = script->lineno();
2723 unsigned maxLineNo = lineno;
2724 for (SrcNoteIterator iter(script->notes(), script->notesEnd()); !iter.atEnd();
2725 ++iter) {
2726 const auto* sn = *iter;
2727 SrcNoteType type = sn->type();
2728 if (type == SrcNoteType::SetLine) {
2729 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
2730 } else if (type == SrcNoteType::SetLineColumn) {
2731 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
2732 } else if (type == SrcNoteType::NewLine ||
2733 type == SrcNoteType::NewLineColumn) {
2734 lineno++;
2737 if (maxLineNo < lineno) {
2738 maxLineNo = lineno;
2742 return 1 + maxLineNo - script->lineno();
2745 #ifdef JS_CACHEIR_SPEW
2746 void js::maybeUpdateWarmUpCount(JSScript* script) {
2747 if (script->needsFinalWarmUpCount()) {
2748 ScriptFinalWarmUpCountMap* map =
2749 script->zone()->scriptFinalWarmUpCountMap.get();
2750 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2751 // already been created and thus must be asserted.
2752 MOZ_ASSERT(map);
2753 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script);
2754 MOZ_ASSERT(p);
2756 std::get<0>(p->value()) += script->jitScript()->warmUpCount();
2760 void js::maybeSpewScriptFinalWarmUpCount(JSScript* script) {
2761 if (script->needsFinalWarmUpCount()) {
2762 ScriptFinalWarmUpCountMap* map =
2763 script->zone()->scriptFinalWarmUpCountMap.get();
2764 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2765 // already been created and thus must be asserted.
2766 MOZ_ASSERT(map);
2767 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script);
2768 MOZ_ASSERT(p);
2769 auto& tuple = p->value();
2770 uint32_t warmUpCount = std::get<0>(tuple);
2771 SharedImmutableString& scriptName = std::get<1>(tuple);
2773 JSContext* cx = TlsContext.get();
2774 cx->spewer().enableSpewing();
2776 // In the case that we care about a script's final warmup count but the
2777 // spewer is not enabled, AutoSpewChannel automatically sets and unsets
2778 // the proper channel for the duration of spewing a health report's warm
2779 // up count.
2780 AutoSpewChannel channel(cx, SpewChannel::CacheIRHealthReport, script);
2781 jit::CacheIRHealth cih;
2782 cih.spewScriptFinalWarmUpCount(cx, scriptName.chars(), script, warmUpCount);
2784 script->zone()->scriptFinalWarmUpCountMap->remove(script);
2785 script->setNeedsFinalWarmUpCount(false);
2788 #endif
2790 void js::DescribeScriptedCallerForDirectEval(JSContext* cx, HandleScript script,
2791 jsbytecode* pc, const char** file,
2792 uint32_t* linenop,
2793 uint32_t* pcOffset,
2794 bool* mutedErrors) {
2795 MOZ_ASSERT(script->containsPC(pc));
2797 static_assert(JSOpLength_SpreadEval == JSOpLength_StrictSpreadEval,
2798 "next op after a spread must be at consistent offset");
2799 static_assert(JSOpLength_Eval == JSOpLength_StrictEval,
2800 "next op after a direct eval must be at consistent offset");
2802 MOZ_ASSERT(JSOp(*pc) == JSOp::Eval || JSOp(*pc) == JSOp::StrictEval ||
2803 JSOp(*pc) == JSOp::SpreadEval ||
2804 JSOp(*pc) == JSOp::StrictSpreadEval);
2806 bool isSpread =
2807 (JSOp(*pc) == JSOp::SpreadEval || JSOp(*pc) == JSOp::StrictSpreadEval);
2808 jsbytecode* nextpc =
2809 pc + (isSpread ? JSOpLength_SpreadEval : JSOpLength_Eval);
2810 MOZ_ASSERT(JSOp(*nextpc) == JSOp::Lineno);
2812 *file = script->filename();
2813 *linenop = GET_UINT32(nextpc);
2814 *pcOffset = script->pcToOffset(pc);
2815 *mutedErrors = script->mutedErrors();
2818 void js::DescribeScriptedCallerForCompilation(
2819 JSContext* cx, MutableHandleScript maybeScript, const char** file,
2820 uint32_t* linenop, uint32_t* pcOffset, bool* mutedErrors) {
2821 NonBuiltinFrameIter iter(cx, cx->realm()->principals());
2823 if (iter.done()) {
2824 maybeScript.set(nullptr);
2825 *file = nullptr;
2826 *linenop = 0;
2827 *pcOffset = 0;
2828 *mutedErrors = false;
2829 return;
2832 *file = iter.filename();
2833 *linenop = iter.computeLine();
2834 *mutedErrors = iter.mutedErrors();
2836 // These values are only used for introducer fields which are debugging
2837 // information and can be safely left null for wasm frames.
2838 if (iter.hasScript()) {
2839 maybeScript.set(iter.script());
2840 *pcOffset = iter.pc() - maybeScript->code();
2841 } else {
2842 maybeScript.set(nullptr);
2843 *pcOffset = 0;
2847 template <typename SourceSpan, typename TargetSpan>
2848 void CopySpan(const SourceSpan& source, TargetSpan target) {
2849 MOZ_ASSERT(source.size() == target.size());
2850 std::copy(source.cbegin(), source.cend(), target.begin());
2853 /* static */
2854 js::UniquePtr<ImmutableScriptData> ImmutableScriptData::new_(
2855 FrontendContext* fc, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots,
2856 GCThingIndex bodyScopeIndex, uint32_t numICEntries, bool isFunction,
2857 uint16_t funLength, uint16_t propertyCountEstimate,
2858 mozilla::Span<const jsbytecode> code, mozilla::Span<const SrcNote> notes,
2859 mozilla::Span<const uint32_t> resumeOffsets,
2860 mozilla::Span<const ScopeNote> scopeNotes,
2861 mozilla::Span<const TryNote> tryNotes) {
2862 MOZ_RELEASE_ASSERT(code.Length() <= frontend::MaxBytecodeLength);
2864 // There are 1-4 copies of SrcNoteType::Null appended after the source
2865 // notes. These are a combination of sentinel and padding values.
2866 static_assert(frontend::MaxSrcNotesLength <= UINT32_MAX - CodeNoteAlign,
2867 "Length + CodeNoteAlign shouldn't overflow UINT32_MAX");
2868 size_t noteLength = notes.Length();
2869 MOZ_RELEASE_ASSERT(noteLength <= frontend::MaxSrcNotesLength);
2871 size_t notePaddingLength = ComputeNotePadding(code.Length(), noteLength);
2873 // Allocate ImmutableScriptData
2874 js::UniquePtr<ImmutableScriptData> data(ImmutableScriptData::new_(
2875 fc, code.Length(), noteLength + notePaddingLength, resumeOffsets.Length(),
2876 scopeNotes.Length(), tryNotes.Length()));
2877 if (!data) {
2878 return data;
2881 // Initialize POD fields
2882 data->mainOffset = mainOffset;
2883 data->nfixed = nfixed;
2884 data->nslots = nslots;
2885 data->bodyScopeIndex = bodyScopeIndex;
2886 data->numICEntries = numICEntries;
2887 data->propertyCountEstimate = propertyCountEstimate;
2889 if (isFunction) {
2890 data->funLength = funLength;
2893 // Initialize trailing arrays
2894 CopySpan(code, data->codeSpan());
2895 CopySpan(notes, data->notesSpan().To(noteLength));
2896 std::fill_n(data->notes() + noteLength, notePaddingLength,
2897 SrcNote::padding());
2898 CopySpan(resumeOffsets, data->resumeOffsets());
2899 CopySpan(scopeNotes, data->scopeNotes());
2900 CopySpan(tryNotes, data->tryNotes());
2902 return data;
2905 void ScriptWarmUpData::trace(JSTracer* trc) {
2906 uintptr_t tag = data_ & TagMask;
2907 switch (tag) {
2908 case EnclosingScriptTag: {
2909 BaseScript* enclosingScript = toEnclosingScript();
2910 BaseScript* prior = enclosingScript;
2911 TraceManuallyBarrieredEdge(trc, &enclosingScript, "enclosingScript");
2912 if (enclosingScript != prior) {
2913 setTaggedPtr<EnclosingScriptTag>(enclosingScript);
2915 break;
2918 case EnclosingScopeTag: {
2919 Scope* enclosingScope = toEnclosingScope();
2920 Scope* prior = enclosingScope;
2921 TraceManuallyBarrieredEdge(trc, &enclosingScope, "enclosingScope");
2922 if (enclosingScope != prior) {
2923 setTaggedPtr<EnclosingScopeTag>(enclosingScope);
2925 break;
2928 case JitScriptTag: {
2929 toJitScript()->trace(trc);
2930 break;
2933 default: {
2934 MOZ_ASSERT(isWarmUpCount());
2935 break;
2940 size_t JSScript::calculateLiveFixed(jsbytecode* pc) {
2941 size_t nlivefixed = numAlwaysLiveFixedSlots();
2943 if (nfixed() != nlivefixed) {
2944 Scope* scope = lookupScope(pc);
2945 if (scope) {
2946 scope = MaybeForwarded(scope);
2949 // Find the nearest LexicalScope in the same script.
2950 while (scope && scope->is<WithScope>()) {
2951 scope = scope->enclosing();
2952 if (scope) {
2953 scope = MaybeForwarded(scope);
2957 if (scope) {
2958 if (scope->is<LexicalScope>()) {
2959 nlivefixed = scope->as<LexicalScope>().nextFrameSlot();
2960 } else if (scope->is<VarScope>()) {
2961 nlivefixed = scope->as<VarScope>().nextFrameSlot();
2962 } else if (scope->is<ClassBodyScope>()) {
2963 nlivefixed = scope->as<ClassBodyScope>().nextFrameSlot();
2968 MOZ_ASSERT(nlivefixed <= nfixed());
2969 MOZ_ASSERT(nlivefixed >= numAlwaysLiveFixedSlots());
2971 return nlivefixed;
2974 Scope* JSScript::lookupScope(const jsbytecode* pc) const {
2975 MOZ_ASSERT(containsPC(pc));
2977 size_t offset = pc - code();
2979 auto notes = scopeNotes();
2980 Scope* scope = nullptr;
2982 // Find the innermost block chain using a binary search.
2983 size_t bottom = 0;
2984 size_t top = notes.size();
2986 while (bottom < top) {
2987 size_t mid = bottom + (top - bottom) / 2;
2988 const ScopeNote* note = &notes[mid];
2989 if (note->start <= offset) {
2990 // Block scopes are ordered in the list by their starting offset, and
2991 // since blocks form a tree ones earlier in the list may cover the pc even
2992 // if later blocks end before the pc. This only happens when the earlier
2993 // block is a parent of the later block, so we need to check parents of
2994 // |mid| in the searched range for coverage.
2995 size_t check = mid;
2996 while (check >= bottom) {
2997 const ScopeNote* checkNote = &notes[check];
2998 MOZ_ASSERT(checkNote->start <= offset);
2999 if (offset < checkNote->start + checkNote->length) {
3000 // We found a matching block chain but there may be inner ones
3001 // at a higher block chain index than mid. Continue the binary search.
3002 if (checkNote->index == ScopeNote::NoScopeIndex) {
3003 scope = nullptr;
3004 } else {
3005 scope = getScope(checkNote->index);
3007 break;
3009 if (checkNote->parent == UINT32_MAX) {
3010 break;
3012 check = checkNote->parent;
3014 bottom = mid + 1;
3015 } else {
3016 top = mid;
3020 return scope;
3023 Scope* JSScript::innermostScope(const jsbytecode* pc) const {
3024 if (Scope* scope = lookupScope(pc)) {
3025 return scope;
3027 return bodyScope();
3030 void js::SetFrameArgumentsObject(JSContext* cx, AbstractFramePtr frame,
3031 HandleScript script, JSObject* argsobj) {
3033 * If the arguments object was optimized out by scalar replacement,
3034 * we must recreate it when we bail out. Because 'arguments' may have
3035 * already been overwritten, we must check to see if the slot already
3036 * contains a value.
3039 Rooted<BindingIter> bi(cx, BindingIter(script));
3040 while (bi && bi.name() != cx->names().arguments) {
3041 bi++;
3043 if (!bi) {
3044 return;
3047 if (bi.location().kind() == BindingLocation::Kind::Environment) {
3048 #ifdef DEBUG
3050 * If |arguments| lives in the call object, we should not have
3051 * optimized it. Scan the script to find the slot in the call
3052 * object that |arguments| is assigned to and verify that it
3053 * already exists.
3055 jsbytecode* pc = script->code();
3056 while (JSOp(*pc) != JSOp::Arguments) {
3057 pc += GetBytecodeLength(pc);
3059 pc += JSOpLength_Arguments;
3060 MOZ_ASSERT(JSOp(*pc) == JSOp::SetAliasedVar);
3062 EnvironmentObject& env = frame.callObj().as<EnvironmentObject>();
3063 MOZ_ASSERT(!env.aliasedBinding(bi).isMagic(JS_OPTIMIZED_OUT));
3064 #endif
3065 return;
3068 MOZ_ASSERT(bi.location().kind() == BindingLocation::Kind::Frame);
3069 uint32_t frameSlot = bi.location().slot();
3070 if (frame.unaliasedLocal(frameSlot).isMagic(JS_OPTIMIZED_OUT)) {
3071 frame.unaliasedLocal(frameSlot) = ObjectValue(*argsobj);
3075 bool JSScript::formalIsAliased(unsigned argSlot) {
3076 if (functionHasParameterExprs()) {
3077 return false;
3080 for (PositionalFormalParameterIter fi(this); fi; fi++) {
3081 if (fi.argumentSlot() == argSlot) {
3082 return fi.closedOver();
3085 MOZ_CRASH("Argument slot not found");
3088 // Returns true if any formal argument is mapped by the arguments
3089 // object, but lives in the call object.
3090 bool JSScript::anyFormalIsForwarded() {
3091 if (!argsObjAliasesFormals()) {
3092 return false;
3095 for (PositionalFormalParameterIter fi(this); fi; fi++) {
3096 if (fi.closedOver()) {
3097 return true;
3100 return false;
3103 bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) {
3104 return argsObjAliasesFormals() && !formalIsAliased(argSlot);
3107 BaseScript::BaseScript(uint8_t* stubEntry, JSFunction* function,
3108 ScriptSourceObject* sourceObject,
3109 const SourceExtent& extent, uint32_t immutableFlags)
3110 : TenuredCellWithNonGCPointer(stubEntry),
3111 function_(function),
3112 sourceObject_(sourceObject),
3113 extent_(extent),
3114 immutableFlags_(immutableFlags) {
3115 MOZ_ASSERT(extent_.toStringStart <= extent_.sourceStart);
3116 MOZ_ASSERT(extent_.sourceStart <= extent_.sourceEnd);
3117 MOZ_ASSERT(extent_.sourceEnd <= extent_.toStringEnd);
3120 /* static */
3121 BaseScript* BaseScript::New(JSContext* cx, JS::Handle<JSFunction*> function,
3122 Handle<ScriptSourceObject*> sourceObject,
3123 const SourceExtent& extent,
3124 uint32_t immutableFlags) {
3125 uint8_t* stubEntry = nullptr;
3126 if (jit::HasJitBackend()) {
3127 stubEntry = cx->runtime()->jitRuntime()->interpreterStub().value;
3130 MOZ_ASSERT_IF(function,
3131 function->compartment() == sourceObject->compartment());
3132 MOZ_ASSERT_IF(function, function->realm() == sourceObject->realm());
3134 return cx->newCell<BaseScript>(stubEntry, function, sourceObject, extent,
3135 immutableFlags);
3138 /* static */
3139 BaseScript* BaseScript::CreateRawLazy(JSContext* cx, uint32_t ngcthings,
3140 HandleFunction fun,
3141 Handle<ScriptSourceObject*> sourceObject,
3142 const SourceExtent& extent,
3143 uint32_t immutableFlags) {
3144 cx->check(fun);
3146 BaseScript* lazy = New(cx, fun, sourceObject, extent, immutableFlags);
3147 if (!lazy) {
3148 return nullptr;
3151 // Allocate a PrivateScriptData if it will not be empty. Lazy class
3152 // constructors that use member initializers also need PrivateScriptData for
3153 // field data.
3155 // This condition is implicit in BaseScript::hasPrivateScriptData, and should
3156 // be mirrored on InputScript::hasPrivateScriptData.
3157 if (ngcthings || lazy->useMemberInitializers()) {
3158 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings));
3159 if (!data) {
3160 return nullptr;
3162 lazy->swapData(data);
3163 MOZ_ASSERT(!data);
3166 return lazy;
3169 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
3170 // This is an arbitrary non-null pointer that we use as a placeholder
3171 // for scripts that can be run in PBL: the rest of the engine expects
3172 // a "non-null jitcode pointer" but we'll never actually call it. We
3173 // have to ensure alignment to keep GC happy.
3174 static uint8_t* const PBLJitCodePtr = reinterpret_cast<uint8_t*>(8);
3175 #endif
3177 void JSScript::updateJitCodeRaw(JSRuntime* rt) {
3178 MOZ_ASSERT(rt);
3179 if (hasBaselineScript() && baselineScript()->hasPendingIonCompileTask()) {
3180 MOZ_ASSERT(!isIonCompilingOffThread());
3181 setJitCodeRaw(rt->jitRuntime()->lazyLinkStub().value);
3182 } else if (hasIonScript()) {
3183 jit::IonScript* ion = ionScript();
3184 setJitCodeRaw(ion->method()->raw());
3185 } else if (hasBaselineScript()) {
3186 setJitCodeRaw(baselineScript()->method()->raw());
3187 } else if (hasJitScript() && js::jit::IsBaselineInterpreterEnabled()) {
3188 bool usingEntryTrampoline = false;
3189 if (js::jit::JitOptions.emitInterpreterEntryTrampoline) {
3190 auto p = rt->jitRuntime()->getInterpreterEntryMap()->lookup(this);
3191 if (p) {
3192 setJitCodeRaw(p->value().raw());
3193 usingEntryTrampoline = true;
3196 if (!usingEntryTrampoline) {
3197 setJitCodeRaw(rt->jitRuntime()->baselineInterpreter().codeRaw());
3199 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
3200 } else if (hasJitScript() &&
3201 js::jit::IsPortableBaselineInterpreterEnabled()) {
3202 // The portable baseline interpreter does not dispatch on this
3203 // pointer, but it needs to be non-null to trigger the appropriate
3204 // code-paths, so we set it to a placeholder value here.
3205 setJitCodeRaw(PBLJitCodePtr);
3206 #endif // ENABLE_PORTABLE_BASELINE_INTERP
3207 } else if (!js::jit::IsBaselineInterpreterEnabled()) {
3208 setJitCodeRaw(nullptr);
3209 } else {
3210 setJitCodeRaw(rt->jitRuntime()->interpreterStub().value);
3212 MOZ_ASSERT_IF(!js::jit::IsPortableBaselineInterpreterEnabled(), jitCodeRaw());
3215 bool JSScript::hasLoops() {
3216 for (const TryNote& tn : trynotes()) {
3217 if (tn.isLoop()) {
3218 return true;
3221 return false;
3224 bool JSScript::mayReadFrameArgsDirectly() {
3225 return needsArgsObj() || usesArgumentsIntrinsics() || hasRest();
3228 void JSScript::resetWarmUpCounterToDelayIonCompilation() {
3229 // Reset the warm-up count only if it's greater than the BaselineCompiler
3230 // threshold. We do this to ensure this has no effect on Baseline compilation
3231 // because we don't want scripts to get stuck in the (Baseline) interpreter in
3232 // pathological cases.
3234 if (getWarmUpCount() > jit::JitOptions.baselineJitWarmUpThreshold) {
3235 incWarmUpResetCounter();
3236 uint32_t newCount = jit::JitOptions.baselineJitWarmUpThreshold;
3237 if (warmUpData_.isWarmUpCount()) {
3238 warmUpData_.resetWarmUpCount(newCount);
3239 } else {
3240 warmUpData_.toJitScript()->resetWarmUpCount(newCount);
3245 #if defined(DEBUG) || defined(JS_JITSPEW)
3247 void JSScript::dump(JSContext* cx) {
3248 JS::Rooted<JSScript*> script(cx, this);
3250 js::Sprinter sp(cx);
3251 if (!sp.init()) {
3252 return;
3255 DumpOptions options;
3256 options.runtimeData = true;
3257 if (!dump(cx, script, options, &sp)) {
3258 return;
3261 JS::UniqueChars str = sp.release();
3262 if (!str) {
3263 return;
3265 fprintf(stderr, "%s\n", str.get());
3268 void JSScript::dumpRecursive(JSContext* cx) {
3269 JS::Rooted<JSScript*> script(cx, this);
3271 js::Sprinter sp(cx);
3272 if (!sp.init()) {
3273 return;
3276 DumpOptions options;
3277 options.runtimeData = true;
3278 options.recursive = true;
3279 if (!dump(cx, script, options, &sp)) {
3280 return;
3283 JS::UniqueChars str = sp.release();
3284 if (!str) {
3285 return;
3287 fprintf(stderr, "%s\n", str.get());
3290 static void DumpMutableScriptFlags(js::JSONPrinter& json,
3291 MutableScriptFlags mutableFlags) {
3292 // Skip warmup data.
3293 static_assert(int(MutableScriptFlagsEnum::WarmupResets_MASK) == 0xff);
3295 for (uint32_t i = 0x100; i; i = i << 1) {
3296 if (uint32_t(mutableFlags) & i) {
3297 switch (MutableScriptFlagsEnum(i)) {
3298 case MutableScriptFlagsEnum::HasRunOnce:
3299 json.value("HasRunOnce");
3300 break;
3301 case MutableScriptFlagsEnum::HasBeenCloned:
3302 json.value("HasBeenCloned");
3303 break;
3304 case MutableScriptFlagsEnum::HasScriptCounts:
3305 json.value("HasScriptCounts");
3306 break;
3307 case MutableScriptFlagsEnum::HasDebugScript:
3308 json.value("HasDebugScript");
3309 break;
3310 case MutableScriptFlagsEnum::AllowRelazify:
3311 json.value("AllowRelazify");
3312 break;
3313 case MutableScriptFlagsEnum::SpewEnabled:
3314 json.value("SpewEnabled");
3315 break;
3316 case MutableScriptFlagsEnum::NeedsFinalWarmUpCount:
3317 json.value("NeedsFinalWarmUpCount");
3318 break;
3319 case MutableScriptFlagsEnum::BaselineDisabled:
3320 json.value("BaselineDisabled");
3321 break;
3322 case MutableScriptFlagsEnum::IonDisabled:
3323 json.value("IonDisabled");
3324 break;
3325 case MutableScriptFlagsEnum::Uninlineable:
3326 json.value("Uninlineable");
3327 break;
3328 case MutableScriptFlagsEnum::NoEagerBaselineHint:
3329 json.value("NoEagerBaselineHint");
3330 break;
3331 case MutableScriptFlagsEnum::FailedBoundsCheck:
3332 json.value("FailedBoundsCheck");
3333 break;
3334 case MutableScriptFlagsEnum::HadLICMInvalidation:
3335 json.value("HadLICMInvalidation");
3336 break;
3337 case MutableScriptFlagsEnum::HadReorderingBailout:
3338 json.value("HadReorderingBailout");
3339 break;
3340 case MutableScriptFlagsEnum::HadEagerTruncationBailout:
3341 json.value("HadEagerTruncationBailout");
3342 break;
3343 case MutableScriptFlagsEnum::FailedLexicalCheck:
3344 json.value("FailedLexicalCheck");
3345 break;
3346 case MutableScriptFlagsEnum::HadSpeculativePhiBailout:
3347 json.value("HadSpeculativePhiBailout");
3348 break;
3349 case MutableScriptFlagsEnum::HadUnboxFoldingBailout:
3350 json.value("HadUnboxFoldingBailout");
3351 break;
3352 default:
3353 json.value("Unknown(%x)", i);
3354 break;
3360 /* static */
3361 bool JSScript::dump(JSContext* cx, JS::Handle<JSScript*> script,
3362 DumpOptions& options, js::StringPrinter* sp) {
3364 JSONPrinter json(*sp);
3366 json.beginObject();
3368 if (const char* filename = script->filename()) {
3369 json.property("file", filename);
3370 } else {
3371 json.nullProperty("file");
3374 json.property("lineno", script->lineno());
3375 json.property("column", script->column().oneOriginValue());
3377 json.beginListProperty("immutableFlags");
3378 DumpImmutableScriptFlags(json, script->immutableFlags());
3379 json.endList();
3381 if (options.runtimeData) {
3382 json.beginListProperty("mutableFlags");
3383 DumpMutableScriptFlags(json, script->mutableFlags_);
3384 json.endList();
3387 if (script->isFunction()) {
3388 JS::Rooted<JSFunction*> fun(cx, script->function());
3390 JS::Rooted<JSAtom*> name(cx, fun->fullDisplayAtom());
3391 if (name) {
3392 UniqueChars bytes = JS_EncodeStringToUTF8(cx, name);
3393 if (!bytes) {
3394 return false;
3396 json.property("functionName", bytes.get());
3397 } else {
3398 json.nullProperty("functionName");
3401 json.beginListProperty("functionFlags");
3402 DumpFunctionFlagsItems(json, fun->flags());
3403 json.endList();
3406 json.endObject();
3409 if (sp->hadOutOfMemory()) {
3410 sp->forwardOutOfMemory();
3411 return false;
3414 sp->put("\n");
3416 if (!Disassemble(cx, script, /* lines = */ true, sp)) {
3417 return false;
3419 if (!dumpSrcNotes(cx, script, sp)) {
3420 return false;
3422 if (!dumpTryNotes(cx, script, sp)) {
3423 return false;
3425 if (!dumpScopeNotes(cx, script, sp)) {
3426 return false;
3428 if (!dumpGCThings(cx, script, sp)) {
3429 return false;
3432 if (options.recursive) {
3433 for (JS::GCCellPtr gcThing : script->gcthings()) {
3434 if (!gcThing.is<JSObject>()) {
3435 continue;
3438 JSObject* obj = &gcThing.as<JSObject>();
3439 if (obj->is<JSFunction>()) {
3440 sp->put("\n");
3442 JS::Rooted<JSFunction*> fun(cx, &obj->as<JSFunction>());
3443 if (fun->isInterpreted()) {
3444 JS::Rooted<JSScript*> innerScript(
3445 cx, JSFunction::getOrCreateScript(cx, fun));
3446 if (!innerScript) {
3447 return false;
3449 if (!dump(cx, innerScript, options, sp)) {
3450 return false;
3452 } else {
3453 sp->put("[native code]\n");
3459 return true;
3462 /* static */
3463 bool JSScript::dumpSrcNotes(JSContext* cx, JS::Handle<JSScript*> script,
3464 js::GenericPrinter* sp) {
3465 sp->put("\nSource notes:\n");
3466 sp->printf("%4s %4s %6s %5s %6s %-16s %s\n", "ofs", "line", "column", "pc",
3467 "delta", "desc", "args");
3468 sp->put("---- ---- ------ ----- ------ ---------------- ------\n");
3469 unsigned offset = 0;
3470 unsigned lineno = script->lineno();
3471 JS::LimitedColumnNumberOneOrigin column = script->column();
3472 SrcNote* notes = script->notes();
3473 SrcNote* notesEnd = script->notesEnd();
3474 for (SrcNoteIterator iter(notes, notesEnd); !iter.atEnd(); ++iter) {
3475 const auto* sn = *iter;
3477 unsigned delta = sn->delta();
3478 offset += delta;
3479 SrcNoteType type = sn->type();
3480 const char* name = sn->name();
3481 sp->printf("%3u: %4u %6u %5u [%4u] %-16s", unsigned(sn - notes), lineno,
3482 column.oneOriginValue(), offset, delta, name);
3484 switch (type) {
3485 case SrcNoteType::Breakpoint:
3486 case SrcNoteType::BreakpointStepSep:
3487 case SrcNoteType::XDelta:
3488 break;
3490 case SrcNoteType::ColSpan: {
3491 JS::ColumnNumberOffset colspan = SrcNote::ColSpan::getSpan(sn);
3492 sp->printf(" colspan %u", colspan.value());
3493 column += colspan;
3494 break;
3497 case SrcNoteType::SetLine:
3498 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
3499 sp->printf(" lineno %u", lineno);
3500 column = JS::LimitedColumnNumberOneOrigin();
3501 break;
3503 case SrcNoteType::SetLineColumn:
3504 lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
3505 column = SrcNote::SetLineColumn::getColumn(sn);
3506 sp->printf(" lineno %u column %u", lineno, column.oneOriginValue());
3507 break;
3509 case SrcNoteType::NewLine:
3510 ++lineno;
3511 column = JS::LimitedColumnNumberOneOrigin();
3512 break;
3514 case SrcNoteType::NewLineColumn:
3515 column = SrcNote::NewLineColumn::getColumn(sn);
3516 sp->printf(" column %u", column.oneOriginValue());
3517 ++lineno;
3518 break;
3520 default:
3521 MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
3523 sp->put("\n");
3526 return true;
3529 static const char* TryNoteName(TryNoteKind kind) {
3530 switch (kind) {
3531 case TryNoteKind::Catch:
3532 return "catch";
3533 case TryNoteKind::Finally:
3534 return "finally";
3535 case TryNoteKind::ForIn:
3536 return "for-in";
3537 case TryNoteKind::ForOf:
3538 return "for-of";
3539 case TryNoteKind::Loop:
3540 return "loop";
3541 case TryNoteKind::ForOfIterClose:
3542 return "for-of-iterclose";
3543 case TryNoteKind::Destructuring:
3544 return "destructuring";
3547 MOZ_CRASH("Bad TryNoteKind");
3550 /* static */
3551 bool JSScript::dumpTryNotes(JSContext* cx, JS::Handle<JSScript*> script,
3552 js::GenericPrinter* sp) {
3553 sp->put("\nException table:\nkind stack start end\n");
3555 for (const js::TryNote& tn : script->trynotes()) {
3556 sp->printf(" %-16s %6u %8u %8u\n", TryNoteName(tn.kind()), tn.stackDepth,
3557 tn.start, tn.start + tn.length);
3559 return true;
3562 /* static */
3563 bool JSScript::dumpScopeNotes(JSContext* cx, JS::Handle<JSScript*> script,
3564 js::GenericPrinter* sp) {
3565 sp->put("\nScope notes:\n index parent start end\n");
3567 for (const ScopeNote& note : script->scopeNotes()) {
3568 if (note.index == ScopeNote::NoScopeIndex) {
3569 sp->printf("%8s ", "(none)");
3570 } else {
3571 sp->printf("%8u ", note.index.index);
3573 if (note.parent == ScopeNote::NoScopeIndex) {
3574 sp->printf("%8s ", "(none)");
3575 } else {
3576 sp->printf("%8u ", note.parent);
3578 sp->printf("%8u %8u\n", note.start, note.start + note.length);
3580 return true;
3583 /* static */
3584 bool JSScript::dumpGCThings(JSContext* cx, JS::Handle<JSScript*> script,
3585 js::GenericPrinter* sp) {
3586 sp->put("\nGC things:\n index type value\n");
3588 size_t i = 0;
3589 for (JS::GCCellPtr gcThing : script->gcthings()) {
3590 sp->printf("%8zu ", i);
3591 if (gcThing.is<JS::BigInt>()) {
3592 sp->put("BigInt ");
3593 gcThing.as<JS::BigInt>().dump(*sp);
3594 sp->put("\n");
3595 } else if (gcThing.is<Scope>()) {
3596 sp->put("Scope ");
3597 JS::Rooted<Scope*> scope(cx, &gcThing.as<Scope>());
3598 if (!Scope::dumpForDisassemble(cx, scope, *sp,
3599 " ")) {
3600 return false;
3602 sp->put("\n");
3603 } else if (gcThing.is<JSObject>()) {
3604 JSObject* obj = &gcThing.as<JSObject>();
3605 if (obj->is<JSFunction>()) {
3606 sp->put("Function ");
3607 JS::Rooted<JSFunction*> fun(cx, &obj->as<JSFunction>());
3608 if (fun->fullDisplayAtom()) {
3609 JS::Rooted<JSAtom*> name(cx, fun->fullDisplayAtom());
3610 JS::UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, name);
3611 if (!utf8chars) {
3612 return false;
3614 sp->put(utf8chars.get());
3615 } else {
3616 sp->put("(anonymous)");
3619 if (fun->hasBaseScript()) {
3620 BaseScript* script = fun->baseScript();
3621 sp->printf(" @ %u:%u\n", script->lineno(),
3622 script->column().oneOriginValue());
3623 } else {
3624 sp->put(" (no script)\n");
3626 } else {
3627 if (obj->is<RegExpObject>()) {
3628 sp->put("RegExp ");
3629 } else {
3630 sp->put("Object ");
3633 JS::Rooted<JS::Value> objValue(cx, ObjectValue(*obj));
3634 JS::Rooted<JSString*> str(cx, ValueToSource(cx, objValue));
3635 if (!str) {
3636 return false;
3638 JS::UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
3639 if (!utf8chars) {
3640 return false;
3642 sp->put(utf8chars.get());
3643 sp->put("\n");
3645 } else if (gcThing.is<JSString>()) {
3646 JS::Rooted<JSString*> str(cx, &gcThing.as<JSString>());
3647 if (str->isAtom()) {
3648 sp->put("Atom ");
3649 } else {
3650 sp->put("String ");
3652 JS::UniqueChars chars = QuoteString(cx, str, '"');
3653 if (!chars) {
3654 return false;
3656 sp->put(chars.get());
3657 sp->put("\n");
3658 } else {
3659 sp->put("Unknown\n");
3661 i++;
3664 return true;
3667 #endif // defined(DEBUG) || defined(JS_JITSPEW)
3669 void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun) {
3670 if (fun) {
3671 JSAutoRealm ar(cx_, fun);
3672 script_ = JSFunction::getOrCreateScript(cx_, fun);
3673 if (script_) {
3674 oldAllowRelazify_ = script_->allowRelazify();
3675 script_->clearAllowRelazify();
3680 void JSScript::AutoDelazify::dropScript() {
3681 if (script_) {
3682 script_->setAllowRelazify(oldAllowRelazify_);
3684 script_ = nullptr;
3687 JS::ubi::Base::Size JS::ubi::Concrete<BaseScript>::size(
3688 mozilla::MallocSizeOf mallocSizeOf) const {
3689 BaseScript* base = &get();
3691 Size size = gc::Arena::thingSize(base->getAllocKind());
3692 size += base->sizeOfExcludingThis(mallocSizeOf);
3694 // Include any JIT data if it exists.
3695 if (base->hasJitScript()) {
3696 JSScript* script = base->asJSScript();
3698 size_t jitScriptSize = 0;
3699 size_t allocSitesSize = 0;
3700 script->addSizeOfJitScript(mallocSizeOf, &jitScriptSize, &allocSitesSize);
3701 size += jitScriptSize;
3702 size += allocSitesSize;
3704 size_t baselineSize = 0;
3705 jit::AddSizeOfBaselineData(script, mallocSizeOf, &baselineSize);
3706 size += baselineSize;
3708 size += jit::SizeOfIonData(script, mallocSizeOf);
3711 MOZ_ASSERT(size > 0);
3712 return size;
3715 const char* JS::ubi::Concrete<BaseScript>::scriptFilename() const {
3716 return get().filename();