Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* JS script operations.
*/
#include "vm/JSScript-inl.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Span.h" // mozilla::{Span,Span}
#include "mozilla/Sprintf.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/Vector.h"
#include <algorithm>
#include <new>
#include <string.h>
#include <type_traits>
#include <utility>
#include "jsapi.h"
#include "jstypes.h"
#include "frontend/BytecodeCompiler.h"
#include "frontend/BytecodeEmitter.h"
#include "frontend/CompilationStencil.h" // frontend::CompilationStencil
#include "frontend/SharedContext.h"
#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator
#include "frontend/StencilXdr.h" // frontend::StencilXdr::SharedData, CanCopyDataToDisk
#include "gc/FreeOp.h"
#include "jit/BaselineJIT.h"
#include "jit/CacheIRHealth.h"
#include "jit/Invalidation.h"
#include "jit/Ion.h"
#include "jit/IonScript.h"
#include "jit/JitCode.h"
#include "jit/JitOptions.h"
#include "jit/JitRuntime.h"
#include "js/CompileOptions.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/MemoryMetrics.h"
#include "js/Printf.h"
#include "js/SourceText.h"
#include "js/Transcoding.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
#include "js/Wrapper.h"
#include "util/Memory.h"
#include "util/Poison.h"
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "vm/ArgumentsObject.h"
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/BytecodeUtil.h"
#include "vm/Compression.h"
#include "vm/FunctionFlags.h" // js::FunctionFlags
#include "vm/HelperThreadState.h" // js::RunPendingSourceCompressions
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/Opcodes.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/SelfHosting.h"
#include "vm/Shape.h"
#include "vm/SharedImmutableStringsCache.h"
#include "vm/Warnings.h" // js::WarnNumberLatin1
#include "vm/Xdr.h"
#ifdef MOZ_VTUNE
# include "vtune/VTuneWrapper.h"
#endif
#include "debugger/DebugAPI-inl.h"
#include "gc/Marking-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/BytecodeLocation-inl.h"
#include "vm/Compartment-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSFunction-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/SharedImmutableStringsCache-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
using mozilla::CheckedInt;
using mozilla::Maybe;
using mozilla::PodCopy;
using mozilla::PointerRangeSize;
using mozilla::Utf8AsUnsignedChars;
using mozilla::Utf8Unit;
using JS::CompileOptions;
using JS::ReadOnlyCompileOptions;
using JS::SourceText;
template <XDRMode mode>
XDRResult js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) {
JSContext* cx = xdr->cx();
enum ConstTag {
SCRIPT_INT,
SCRIPT_DOUBLE,
SCRIPT_ATOM,
SCRIPT_TRUE,
SCRIPT_FALSE,
SCRIPT_NULL,
SCRIPT_OBJECT,
SCRIPT_VOID,
SCRIPT_HOLE,
SCRIPT_BIGINT
};
ConstTag tag;
if (mode == XDR_ENCODE) {
if (vp.isInt32()) {
tag = SCRIPT_INT;
} else if (vp.isDouble()) {
tag = SCRIPT_DOUBLE;
} else if (vp.isString()) {
tag = SCRIPT_ATOM;
} else if (vp.isTrue()) {
tag = SCRIPT_TRUE;
} else if (vp.isFalse()) {
tag = SCRIPT_FALSE;
} else if (vp.isNull()) {
tag = SCRIPT_NULL;
} else if (vp.isObject()) {
tag = SCRIPT_OBJECT;
} else if (vp.isMagic(JS_ELEMENTS_HOLE)) {
tag = SCRIPT_HOLE;
} else if (vp.isBigInt()) {
tag = SCRIPT_BIGINT;
} else {
MOZ_ASSERT(vp.isUndefined());
tag = SCRIPT_VOID;
}
}
MOZ_TRY(xdr->codeEnum32(&tag));
switch (tag) {
case SCRIPT_INT: {
uint32_t i;
if (mode == XDR_ENCODE) {
i = uint32_t(vp.toInt32());
}
MOZ_TRY(xdr->codeUint32(&i));
if (mode == XDR_DECODE) {
vp.set(Int32Value(int32_t(i)));
}
break;
}
case SCRIPT_DOUBLE: {
double d;
if (mode == XDR_ENCODE) {
d = vp.toDouble();
}
MOZ_TRY(xdr->codeDouble(&d));
if (mode == XDR_DECODE) {
vp.set(DoubleValue(d));
}
break;
}
case SCRIPT_ATOM: {
RootedAtom atom(cx);
if (mode == XDR_ENCODE) {
atom = &vp.toString()->asAtom();
}
MOZ_TRY(XDRAtom(xdr, &atom));
if (mode == XDR_DECODE) {
vp.set(StringValue(atom));
}
break;
}
case SCRIPT_TRUE:
if (mode == XDR_DECODE) {
vp.set(BooleanValue(true));
}
break;
case SCRIPT_FALSE:
if (mode == XDR_DECODE) {
vp.set(BooleanValue(false));
}
break;
case SCRIPT_NULL:
if (mode == XDR_DECODE) {
vp.set(NullValue());
}
break;
case SCRIPT_OBJECT: {
RootedObject obj(cx);
if (mode == XDR_ENCODE) {
obj = &vp.toObject();
}
MOZ_TRY(XDRObjectLiteral(xdr, &obj));
if (mode == XDR_DECODE) {
vp.setObject(*obj);
}
break;
}
case SCRIPT_VOID:
if (mode == XDR_DECODE) {
vp.set(UndefinedValue());
}
break;
case SCRIPT_HOLE:
if (mode == XDR_DECODE) {
vp.setMagic(JS_ELEMENTS_HOLE);
}
break;
case SCRIPT_BIGINT: {
RootedBigInt bi(cx);
if (mode == XDR_ENCODE) {
bi = vp.toBigInt();
}
MOZ_TRY(XDRBigInt(xdr, &bi));
if (mode == XDR_DECODE) {
vp.setBigInt(bi);
}
break;
}
default:
// Fail in debug, but only soft-fail in release
MOZ_ASSERT(false, "Bad XDR value kind");
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
return Ok();
}
template XDRResult js::XDRScriptConst(XDRState<XDR_ENCODE>*,
MutableHandleValue);
template XDRResult js::XDRScriptConst(XDRState<XDR_DECODE>*,
MutableHandleValue);
// Code lazy scripts's closed over bindings.
template <XDRMode mode>
/* static */
XDRResult BaseScript::XDRLazyScriptData(XDRState<mode>* xdr,
HandleScriptSourceObject sourceObject,
Handle<BaseScript*> lazy) {
JSContext* cx = xdr->cx();
RootedAtom atom(cx);
RootedFunction func(cx);
if (lazy->useMemberInitializers()) {
uint32_t numMemberInitializers;
if (mode == XDR_ENCODE) {
MOZ_ASSERT(lazy->getMemberInitializers().valid);
numMemberInitializers =
lazy->getMemberInitializers().numMemberInitializers;
}
MOZ_TRY(xdr->codeUint32(&numMemberInitializers));
if (mode == XDR_DECODE) {
lazy->setMemberInitializers(MemberInitializers(numMemberInitializers));
}
}
mozilla::Span<JS::GCCellPtr> gcThings =
lazy->data_ ? lazy->data_->gcthings() : mozilla::Span<JS::GCCellPtr>();
for (JS::GCCellPtr& elem : gcThings) {
JS::TraceKind kind = elem.kind();
MOZ_TRY(xdr->codeEnum32(&kind));
switch (kind) {
case JS::TraceKind::Object: {
if (mode == XDR_ENCODE) {
func = &elem.as<JSObject>().as<JSFunction>();
}
MOZ_TRY(XDRInterpretedFunction(xdr, nullptr, sourceObject, &func));
if (mode == XDR_DECODE) {
func->setEnclosingLazyScript(lazy);
elem = JS::GCCellPtr(func);
}
break;
}
case JS::TraceKind::String: {
if (mode == XDR_ENCODE) {
gc::Cell* cell = elem.asCell();
MOZ_ASSERT_IF(cell, cell->as<JSString>()->isAtom());
atom = static_cast<JSAtom*>(cell);
}
MOZ_TRY(XDRAtom(xdr, &atom));
if (mode == XDR_DECODE) {
elem = JS::GCCellPtr(static_cast<JSString*>(atom));
}
break;
}
case JS::TraceKind::Null: {
// This is default so nothing to do.
MOZ_ASSERT(!elem);
break;
}
default: {
// Fail in debug, but only soft-fail in release
MOZ_ASSERT(false, "Bad XDR class kind");
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
}
}
return Ok();
}
static inline uint32_t FindScopeIndex(mozilla::Span<const JS::GCCellPtr> scopes,
Scope& scope) {
unsigned length = scopes.size();
for (uint32_t i = 0; i < length; ++i) {
if (scopes[i].asCell() == &scope) {
return i;
}
}
MOZ_CRASH("Scope not found");
}
template <XDRMode mode>
static XDRResult XDRInnerObject(XDRState<mode>* xdr,
js::PrivateScriptData* data,
HandleScriptSourceObject sourceObject,
MutableHandleObject inner) {
enum class ClassKind { RegexpObject, JSFunction, JSObject, ArrayObject };
JSContext* cx = xdr->cx();
ClassKind classk;
if (mode == XDR_ENCODE) {
if (inner->is<RegExpObject>()) {
classk = ClassKind::RegexpObject;
} else if (inner->is<JSFunction>()) {
classk = ClassKind::JSFunction;
} else if (inner->is<PlainObject>()) {
classk = ClassKind::JSObject;
} else if (inner->is<ArrayObject>()) {
classk = ClassKind::ArrayObject;
} else {
MOZ_CRASH("Cannot encode this class of object.");
}
}
MOZ_TRY(xdr->codeEnum32(&classk));
switch (classk) {
case ClassKind::RegexpObject: {
Rooted<RegExpObject*> regexp(cx);
if (mode == XDR_ENCODE) {
regexp = &inner->as<RegExpObject>();
}
MOZ_TRY(XDRScriptRegExpObject(xdr, &regexp));
if (mode == XDR_DECODE) {
inner.set(regexp);
}
break;
}
case ClassKind::JSFunction: {
/* Code the nested function's enclosing scope. */
uint32_t funEnclosingScopeIndex = 0;
RootedScope funEnclosingScope(cx);
if (mode == XDR_ENCODE) {
RootedFunction function(cx, &inner->as<JSFunction>());
if (function->isAsmJSNative()) {
return xdr->fail(JS::TranscodeResult::Failure_AsmJSNotSupported);
}
MOZ_ASSERT(function->enclosingScope());
funEnclosingScope = function->enclosingScope();
funEnclosingScopeIndex =
FindScopeIndex(data->gcthings(), *funEnclosingScope);
}
MOZ_TRY(xdr->codeUint32(&funEnclosingScopeIndex));
if (mode == XDR_DECODE) {
funEnclosingScope =
&data->gcthings()[funEnclosingScopeIndex].as<Scope>();
}
// Code nested function and script.
RootedFunction tmp(cx);
if (mode == XDR_ENCODE) {
tmp = &inner->as<JSFunction>();
}
MOZ_TRY(
XDRInterpretedFunction(xdr, funEnclosingScope, sourceObject, &tmp));
if (mode == XDR_DECODE) {
inner.set(tmp);
}
break;
}
case ClassKind::JSObject:
case ClassKind::ArrayObject: {
/* Code object literal. */
RootedObject tmp(cx);
if (mode == XDR_ENCODE) {
tmp = inner.get();
}
MOZ_TRY(XDRObjectLiteral(xdr, &tmp));
if (mode == XDR_DECODE) {
inner.set(tmp);
}
break;
}
default: {
// Fail in debug, but only soft-fail in release
MOZ_ASSERT(false, "Bad XDR class kind");
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
}
return Ok();
}
template <XDRMode mode>
static XDRResult XDRScope(XDRState<mode>* xdr, js::PrivateScriptData* data,
HandleScope scriptEnclosingScope,
HandleObject funOrMod, bool isFirstScope,
MutableHandleScope scope) {
JSContext* cx = xdr->cx();
ScopeKind scopeKind;
RootedScope enclosing(cx);
RootedFunction fun(cx);
RootedModuleObject module(cx);
uint32_t enclosingIndex = 0;
// The enclosingScope is encoded using an integer index into the scope array.
// This means that scopes must be topologically sorted.
if (mode == XDR_ENCODE) {
scopeKind = scope->kind();
if (isFirstScope) {
enclosingIndex = UINT32_MAX;
} else {
MOZ_ASSERT(scope->enclosing());
enclosingIndex = FindScopeIndex(data->gcthings(), *scope->enclosing());
}
}
MOZ_TRY(xdr->codeEnum32(&scopeKind));
MOZ_TRY(xdr->codeUint32(&enclosingIndex));
if (mode == XDR_DECODE) {
if (isFirstScope) {
MOZ_ASSERT(enclosingIndex == UINT32_MAX);
enclosing = scriptEnclosingScope;
} else {
enclosing = &data->gcthings()[enclosingIndex].as<Scope>();
}
if (funOrMod && funOrMod->is<ModuleObject>()) {
module.set(funOrMod.as<ModuleObject>());
} else if (funOrMod && funOrMod->is<JSFunction>()) {
fun.set(funOrMod.as<JSFunction>());
}
}
switch (scopeKind) {
case ScopeKind::Function:
MOZ_TRY(FunctionScope::XDR(xdr, fun, enclosing, scope));
break;
case ScopeKind::FunctionBodyVar:
MOZ_TRY(VarScope::XDR(xdr, scopeKind, enclosing, scope));
break;
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
case ScopeKind::FunctionLexical:
case ScopeKind::ClassBody:
MOZ_TRY(LexicalScope::XDR(xdr, scopeKind, enclosing, scope));
break;
case ScopeKind::With:
MOZ_TRY(WithScope::XDR(xdr, enclosing, scope));
break;
case ScopeKind::Eval:
case ScopeKind::StrictEval:
MOZ_TRY(EvalScope::XDR(xdr, scopeKind, enclosing, scope));
break;
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
MOZ_TRY(GlobalScope::XDR(xdr, scopeKind, scope));
break;
case ScopeKind::Module:
MOZ_TRY(ModuleScope::XDR(xdr, module, enclosing, scope));
break;
case ScopeKind::WasmInstance:
MOZ_CRASH("NYI");
break;
case ScopeKind::WasmFunction:
MOZ_CRASH("wasm functions cannot be nested in JSScripts");
break;
default:
// Fail in debug, but only soft-fail in release
MOZ_ASSERT(false, "Bad XDR scope kind");
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
return Ok();
}
template <XDRMode mode>
static XDRResult XDRScriptGCThing(XDRState<mode>* xdr, PrivateScriptData* data,
HandleScriptSourceObject sourceObject,
HandleScope scriptEnclosingScope,
HandleObject funOrMod, bool* isFirstScope,
JS::GCCellPtr* thingp) {
JSContext* cx = xdr->cx();
JS::TraceKind kind = thingp->kind();
MOZ_TRY(xdr->codeEnum32(&kind));
switch (kind) {
case JS::TraceKind::String: {
RootedAtom atom(cx);
if (mode == XDR_ENCODE) {
atom = &thingp->as<JSString>().asAtom();
}
MOZ_TRY(XDRAtom(xdr, &atom));
if (mode == XDR_DECODE) {
*thingp = JS::GCCellPtr(atom.get());
}
break;
}
case JS::TraceKind::Object: {
RootedObject obj(cx);
if (mode == XDR_ENCODE) {
obj = &thingp->as<JSObject>();
}
MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &obj));
if (mode == XDR_DECODE) {
*thingp = JS::GCCellPtr(obj.get());
}
break;
}
case JS::TraceKind::Scope: {
RootedScope scope(cx);
if (mode == XDR_ENCODE) {
scope = &thingp->as<Scope>();
}
MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, funOrMod, *isFirstScope,
&scope));
if (mode == XDR_DECODE) {
*thingp = JS::GCCellPtr(scope.get());
}
*isFirstScope = false;
break;
}
case JS::TraceKind::BigInt: {
RootedBigInt bi(cx);
if (mode == XDR_ENCODE) {
bi = &thingp->as<BigInt>();
}
MOZ_TRY(XDRBigInt(xdr, &bi));
if (mode == XDR_DECODE) {
*thingp = JS::GCCellPtr(bi.get());
}
break;
}
default:
// Fail in debug, but only soft-fail in release.
MOZ_ASSERT(false, "Bad XDR class kind");
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
return Ok();
}
bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime* rt) const {
return jitCodeRaw() == rt->jitRuntime()->interpreterStub().value;
}
js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const {
return MaybeForwarded(sourceObject())->source();
}
void js::BaseScript::setEnclosingScript(BaseScript* enclosingScript) {
MOZ_ASSERT(enclosingScript);
warmUpData_.initEnclosingScript(enclosingScript);
}
void js::BaseScript::setEnclosingScope(Scope* enclosingScope) {
if (warmUpData_.isEnclosingScript()) {
warmUpData_.clearEnclosingScript();
}
MOZ_ASSERT(enclosingScope);
warmUpData_.initEnclosingScope(enclosingScope);
}
void js::BaseScript::finalize(JSFreeOp* fop) {
// Scripts with bytecode may have optional data stored in per-runtime or
// per-zone maps. Note that a failed compilation must not have entries since
// the script itself will not be marked as having bytecode.
if (hasBytecode()) {
JSScript* script = this->asJSScript();
if (coverage::IsLCovEnabled()) {
coverage::CollectScriptCoverage(script, true);
}
script->destroyScriptCounts();
}
fop->runtime()->geckoProfiler().onScriptFinalized(this);
#ifdef MOZ_VTUNE
if (zone()->scriptVTuneIdMap) {
// Note: we should only get here if the VTune JIT profiler is running.
zone()->scriptVTuneIdMap->remove(this);
}
#endif
if (warmUpData_.isJitScript()) {
JSScript* script = this->asJSScript();
#ifdef JS_CACHEIR_SPEW
maybeUpdateWarmUpCount(script);
#endif
script->releaseJitScriptOnFinalize(fop);
}
#ifdef JS_CACHEIR_SPEW
if (hasBytecode()) {
maybeSpewScriptFinalWarmUpCount(this->asJSScript());
}
#endif
if (data_) {
// We don't need to triger any barriers here, just free the memory.
size_t size = data_->allocationSize();
AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size,
MemCheckKind::MakeNoAccess);
fop->free_(this, data_, size, MemoryUse::ScriptPrivateData);
}
freeSharedData();
}
js::Scope* js::BaseScript::releaseEnclosingScope() {
Scope* enclosing = warmUpData_.toEnclosingScope();
warmUpData_.clearEnclosingScope();
return enclosing;
}
void js::BaseScript::swapData(UniquePtr<PrivateScriptData>& other) {
PrivateScriptData* tmp = other.release();
if (data_) {
// When disconnecting script data from the BaseScript, we must pre-barrier
// all edges contained in it. Those edges are no longer reachable from
// current location in the graph.
PreWriteBarrier(zone(), data_);
RemoveCellMemory(this, data_->allocationSize(),
MemoryUse::ScriptPrivateData);
}
std::swap(tmp, data_);
if (data_) {
AddCellMemory(this, data_->allocationSize(), MemoryUse::ScriptPrivateData);
}
other.reset(tmp);
}
js::Scope* js::BaseScript::enclosingScope() const {
MOZ_ASSERT(!warmUpData_.isEnclosingScript(),
"Enclosing scope is not computed yet");
if (warmUpData_.isEnclosingScope()) {
return warmUpData_.toEnclosingScope();
}
MOZ_ASSERT(data_, "Script doesn't seem to be compiled");
return gcthings()[js::GCThingIndex::outermostScopeIndex()]
.as<Scope>()
.enclosing();
}
size_t JSScript::numAlwaysLiveFixedSlots() const {
if (bodyScope()->is<js::FunctionScope>()) {
return bodyScope()->as<js::FunctionScope>().nextFrameSlot();
}
if (bodyScope()->is<js::ModuleScope>()) {
return bodyScope()->as<js::ModuleScope>().nextFrameSlot();
}
return 0;
}
unsigned JSScript::numArgs() const {
if (bodyScope()->is<js::FunctionScope>()) {
return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters();
}
return 0;
}
bool JSScript::functionHasParameterExprs() const {
// Only functions have parameters.
js::Scope* scope = bodyScope();
if (!scope->is<js::FunctionScope>()) {
return false;
}
return scope->as<js::FunctionScope>().hasParameterExprs();
}
js::ModuleObject* JSScript::module() const {
if (bodyScope()->is<js::ModuleScope>()) {
return bodyScope()->as<js::ModuleScope>().module();
}
return nullptr;
}
bool JSScript::isGlobalCode() const {
return bodyScope()->is<js::GlobalScope>();
}
js::VarScope* JSScript::functionExtraBodyVarScope() const {
MOZ_ASSERT(functionHasExtraBodyVarScope());
for (JS::GCCellPtr gcThing : gcthings()) {
if (!gcThing.is<js::Scope>()) {
continue;
}
js::Scope* scope = &gcThing.as<js::Scope>();
if (scope->kind() == js::ScopeKind::FunctionBodyVar) {
return &scope->as<js::VarScope>();
}
}
MOZ_CRASH("Function extra body var scope not found");
}
bool JSScript::needsBodyEnvironment() const {
for (JS::GCCellPtr gcThing : gcthings()) {
if (!gcThing.is<js::Scope>()) {
continue;
}
js::Scope* scope = &gcThing.as<js::Scope>();
if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) {
return true;
}
}
return false;
}
bool JSScript::isDirectEvalInFunction() const {
if (!isForEval()) {
return false;
}
return bodyScope()->hasOnChain(js::ScopeKind::Function);
}
template <XDRMode mode>
/* static */
XDRResult js::PrivateScriptData::XDR(XDRState<mode>* xdr, HandleScript script,
HandleScriptSourceObject sourceObject,
HandleScope scriptEnclosingScope,
HandleObject funOrMod) {
uint32_t ngcthings = 0;
JSContext* cx = xdr->cx();
PrivateScriptData* data = nullptr;
if (mode == XDR_ENCODE) {
data = script->data_;
ngcthings = data->gcthings().size();
}
MOZ_TRY(xdr->codeUint32(&ngcthings));
if (mode == XDR_DECODE) {
if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) {
return xdr->fail(JS::TranscodeResult::Throw);
}
data = script->data_;
}
// Code the field initializer data.
if (script->useMemberInitializers()) {
uint32_t numMemberInitializers;
if (mode == XDR_ENCODE) {
MOZ_ASSERT(data->getMemberInitializers().valid);
numMemberInitializers =
data->getMemberInitializers().numMemberInitializers;
}
MOZ_TRY(xdr->codeUint32(&numMemberInitializers));
if (mode == XDR_DECODE) {
data->setMemberInitializers(MemberInitializers(numMemberInitializers));
}
}
bool isFirstScope = true;
for (JS::GCCellPtr& gcThing : data->gcthings()) {
MOZ_TRY(XDRScriptGCThing(xdr, data, sourceObject, scriptEnclosingScope,
funOrMod, &isFirstScope, &gcThing));
}
// Verify marker to detect data corruption after decoding GC things. A
// mismatch here indicates we will almost certainly crash in release.
MOZ_TRY(xdr->codeMarker(0xF83B989A));
return Ok();
}
// Initialize the optional arrays in the trailing allocation. This is a set of
// offsets that delimit each optional array followed by the arrays themselves.
// See comment before 'ImmutableScriptData' for more details.
void ImmutableScriptData::initOptionalArrays(Offset* pcursor,
uint32_t numResumeOffsets,
uint32_t numScopeNotes,
uint32_t numTryNotes) {
Offset cursor = (*pcursor);
// The byte arrays must have already been padded.
MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor),
"Bytecode and source notes should be padded to keep alignment");
// Each non-empty optional array needs will need an offset to its end.
unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
unsigned(numScopeNotes > 0) +
unsigned(numTryNotes > 0);
// Default-initialize the optional-offsets.
initElements<Offset>(cursor, numOptionalArrays);
cursor += numOptionalArrays * sizeof(Offset);
// Offset between optional-offsets table and the optional arrays. This is
// later used to access the optional-offsets table as well as first optional
// array.
optArrayOffset_ = cursor;
// Each optional array that follows must store an end-offset in the offset
// table. Assign table entries by using this 'offsetIndex'. The index 0 is
// reserved for implicit value 'optArrayOffset'.
int offsetIndex = 0;
// Default-initialize optional 'resumeOffsets'.
MOZ_ASSERT(resumeOffsetsOffset() == cursor);
if (numResumeOffsets > 0) {
initElements<uint32_t>(cursor, numResumeOffsets);
cursor += numResumeOffsets * sizeof(uint32_t);
setOptionalOffset(++offsetIndex, cursor);
}
flagsRef().resumeOffsetsEndIndex = offsetIndex;
// Default-initialize optional 'scopeNotes'.
MOZ_ASSERT(scopeNotesOffset() == cursor);
if (numScopeNotes > 0) {
initElements<ScopeNote>(cursor, numScopeNotes);
cursor += numScopeNotes * sizeof(ScopeNote);
setOptionalOffset(++offsetIndex, cursor);
}
flagsRef().scopeNotesEndIndex = offsetIndex;
// Default-initialize optional 'tryNotes'
MOZ_ASSERT(tryNotesOffset() == cursor);
if (numTryNotes > 0) {
initElements<TryNote>(cursor, numTryNotes);
cursor += numTryNotes * sizeof(TryNote);
setOptionalOffset(++offsetIndex, cursor);
}
flagsRef().tryNotesEndIndex = offsetIndex;
MOZ_ASSERT(endOffset() == cursor);
(*pcursor) = cursor;
}
ImmutableScriptData::ImmutableScriptData(uint32_t codeLength,
uint32_t noteLength,
uint32_t numResumeOffsets,
uint32_t numScopeNotes,
uint32_t numTryNotes)
: codeLength_(codeLength) {
// Variable-length data begins immediately after ImmutableScriptData itself.
Offset cursor = sizeof(ImmutableScriptData);
// The following arrays are byte-aligned with additional padding to ensure
// that together they maintain uint32_t-alignment.
{
MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
// Zero-initialize 'flags'
MOZ_ASSERT(isAlignedOffset<Flags>(cursor));
new (offsetToPointer<void>(cursor)) Flags{};
cursor += sizeof(Flags);
initElements<jsbytecode>(cursor, codeLength);
cursor += codeLength * sizeof(jsbytecode);
initElements<SrcNote>(cursor, noteLength);
cursor += noteLength * sizeof(SrcNote);
MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
}
// Initialization for remaining arrays.
initOptionalArrays(&cursor, numResumeOffsets, numScopeNotes, numTryNotes);
// Check that we correctly recompute the expected values.
MOZ_ASSERT(this->codeLength() == codeLength);
MOZ_ASSERT(this->noteLength() == noteLength);
// Sanity check
MOZ_ASSERT(endOffset() == cursor);
}
template <XDRMode mode>
XDRResult js::XDRImmutableScriptData(XDRState<mode>* xdr,
UniquePtr<ImmutableScriptData>& isd) {
static_assert(frontend::CanCopyDataToDisk<ImmutableScriptData>::value,
"ImmutableScriptData cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<jsbytecode>::value,
"jsbytecode cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<SrcNote>::value,
"SrcNote cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<ScopeNote>::value,
"ScopeNote cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<TryNote>::value,
"TryNote cannot be bulk-copied to disk");
uint32_t size;
if (mode == XDR_ENCODE) {
size = isd->immutableData().size();
}
MOZ_TRY(xdr->codeUint32(&size));
uint8_t* data;
if (mode == XDR_ENCODE) {
data = const_cast<uint8_t*>(isd->immutableData().data());
MOZ_ASSERT(data == reinterpret_cast<const uint8_t*>(isd.get()),
"Decode below relies on the data placement");
} else {
isd = ImmutableScriptData::new_(xdr->cx(), size);
if (!isd) {
return xdr->fail(JS::TranscodeResult::Throw);
}
data = reinterpret_cast<uint8_t*>(isd.get());
}
MOZ_TRY(xdr->codeBytes(data, size));
if (mode == XDR_DECODE) {
#ifdef DEBUG
isd->validate(size);
#endif
}
return Ok();
}
template XDRResult js::XDRImmutableScriptData(
XDRState<XDR_ENCODE>* xdr, UniquePtr<ImmutableScriptData>& isd);
template XDRResult js::XDRImmutableScriptData(
XDRState<XDR_DECODE>* xdr, UniquePtr<ImmutableScriptData>& isd);
template <XDRMode mode>
XDRResult js::XDRSourceExtent(XDRState<mode>* xdr, SourceExtent* extent) {
MOZ_TRY(xdr->codeUint32(&extent->sourceStart));
MOZ_TRY(xdr->codeUint32(&extent->sourceEnd));
MOZ_TRY(xdr->codeUint32(&extent->toStringStart));
MOZ_TRY(xdr->codeUint32(&extent->toStringEnd));
MOZ_TRY(xdr->codeUint32(&extent->lineno));
MOZ_TRY(xdr->codeUint32(&extent->column));
return Ok();
}
template /* static */
XDRResult
js::XDRSourceExtent(XDRState<XDR_ENCODE>* xdr, SourceExtent* extent);
template /* static */
XDRResult
js::XDRSourceExtent(XDRState<XDR_DECODE>* xdr, SourceExtent* extent);
void js::FillImmutableFlagsFromCompileOptionsForTopLevel(
const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
using ImmutableFlags = ImmutableScriptFlagsEnum;
js::FillImmutableFlagsFromCompileOptionsForFunction(options, flags);
flags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
flags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
}
void js::FillImmutableFlagsFromCompileOptionsForFunction(
const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
using ImmutableFlags = ImmutableScriptFlagsEnum;
flags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
flags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode());
flags.setFlag(ImmutableFlags::HasNonSyntacticScope,
options.nonSyntacticScope);
}
// Check if flags matches to compile options for flags set by
// FillImmutableFlagsFromCompileOptionsForTopLevel above.
//
// If isMultiDecode is true, this check minimal set of CompileOptions that is
// shared across multiple scripts in JS::DecodeMultiOffThreadScripts.
// Other options should be checked when getting the decoded script from the
// cache.
bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions& options,
ImmutableScriptFlags flags,
bool isMultiDecode) {
using ImmutableFlags = ImmutableScriptFlagsEnum;
bool selfHosted = !!(flags & uint32_t(ImmutableFlags::SelfHosted));
bool forceStrict = !!(flags & uint32_t(ImmutableFlags::ForceStrict));
bool hasNonSyntacticScope =
!!(flags & uint32_t(ImmutableFlags::HasNonSyntacticScope));
bool noScriptRval = !!(flags & uint32_t(ImmutableFlags::NoScriptRval));
bool treatAsRunOnce = !!(flags & uint32_t(ImmutableFlags::TreatAsRunOnce));
return options.selfHostingMode == selfHosted &&
options.noScriptRval == noScriptRval &&
options.isRunOnce == treatAsRunOnce &&
(isMultiDecode || (options.forceStrictMode() == forceStrict &&
options.nonSyntacticScope == hasNonSyntacticScope));
}
JS_PUBLIC_API bool JS::CheckCompileOptionsMatch(
const ReadOnlyCompileOptions& options, JSScript* script) {
return js::CheckCompileOptionsMatch(options, script->immutableFlags(), false);
}
template <XDRMode mode>
XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
HandleScriptSourceObject sourceObjectArg,
HandleObject funOrMod, MutableHandleScript scriptp) {
/* NB: Keep this in sync with CopyScriptImpl. */
enum XDRScriptFlags {
OwnSource = 1 << 0,
HasLazyScript = 1 << 1,
};
uint8_t xdrFlags = 0;
SourceExtent extent;
uint32_t immutableFlags = 0;
// NOTE: |mutableFlags| are not preserved by XDR.
JSContext* cx = xdr->cx();
RootedScript script(cx);
bool isFunctionScript = funOrMod && funOrMod->is<JSFunction>();
// Instrumented scripts cannot be encoded, as they have extra instructions
// which are not normally present. Globals with instrumentation enabled must
// compile scripts via the bytecode emitter, which will insert these
// instructions.
if (xdr->hasOptions() ? !!xdr->options().instrumentationKinds
: !!cx->global()->getInstrumentationHolder()) {
return xdr->fail(JS::TranscodeResult::Failure);
}
if (mode == XDR_ENCODE) {
script = scriptp.get();
MOZ_ASSERT_IF(isFunctionScript, script->function() == funOrMod);
if (!sourceObjectArg) {
xdrFlags |= OwnSource;
}
// Preserve the MutableFlags::AllowRelazify flag.
if (script->allowRelazify()) {
xdrFlags |= HasLazyScript;
}
}
MOZ_TRY(xdr->codeUint8(&xdrFlags));
if (mode == XDR_ENCODE) {
extent = script->extent();
immutableFlags = script->immutableFlags();
}
MOZ_TRY(XDRSourceExtent(xdr, &extent));
MOZ_TRY(xdr->codeUint32(&immutableFlags));
RootedScriptSourceObject sourceObject(cx, sourceObjectArg);
Maybe<CompileOptions> options;
if (mode == XDR_DECODE) {
MOZ_ASSERT(xdr->hasOptions());
// When loading from the bytecode cache, and if we get the CompileOptions
// from the document, if the ImmutableFlags and options don't agree, we
// should fail. This only applies to the top-level and not its inner
// functions.
//
// Also, JS::DecodeMultiOffThreadScripts uses single CompileOptions for
// multiple scripts with different CompileOptions.
// We should check minimal set of common flags here, and let the consumer
// check the full flags when getting from the cache.
if (xdrFlags & OwnSource) {
options.emplace(xdr->cx(), xdr->options());
if (!js::CheckCompileOptionsMatch(*options,
ImmutableScriptFlags(immutableFlags),
xdr->isMultiDecode())) {
return xdr->fail(JS::TranscodeResult::Failure_WrongCompileOption);
}
}
}
if (xdrFlags & OwnSource) {
RefPtr<ScriptSource> source;
// We are relying on the script's ScriptSource so the caller should not
// have passed in an explicit one.
MOZ_ASSERT(sourceObjectArg == nullptr);
if (mode == XDR_ENCODE) {
sourceObject = script->sourceObject();
source = do_AddRef(sourceObject->source());
}
MOZ_TRY(ScriptSource::XDR(xdr, options.ptrOr(nullptr), source));
if (mode == XDR_DECODE) {
sourceObject = ScriptSourceObject::create(cx, source);
if (!sourceObject) {
return xdr->fail(JS::TranscodeResult::Throw);
}
if (xdr->hasScriptSourceObjectOut()) {
// When the ScriptSourceObjectOut is provided by ParseTask, it
// is stored in a location which is traced by the GC.
*xdr->scriptSourceObjectOut() = sourceObject;
} else if (!ScriptSourceObject::initFromOptions(cx, sourceObject,
*options)) {
return xdr->fail(JS::TranscodeResult::Throw);
}
}
} else {
// While encoding, the ScriptSource passed in must match the ScriptSource
// of the script.
MOZ_ASSERT_IF(mode == XDR_ENCODE,
sourceObjectArg->source() == script->scriptSource());
}
if (mode == XDR_DECODE) {
RootedObject functionOrGlobal(
cx, isFunctionScript ? static_cast<JSObject*>(funOrMod)
: static_cast<JSObject*>(cx->global()));
script = JSScript::Create(cx, functionOrGlobal, sourceObject, extent,
ImmutableScriptFlags(immutableFlags));
if (!script) {
return xdr->fail(JS::TranscodeResult::Throw);
}
scriptp.set(script);
// Set the script in its function now so that inner scripts to be
// decoded may iterate the static scope chain.
if (isFunctionScript) {
funOrMod->as<JSFunction>().initScript(script);
}
}
// If XDR operation fails, we must call BaseScript::freeSharedData in order to
// neuter the script. Various things that iterate raw scripts in a GC arena
// use the presense of this data to detect if initialization is complete.
auto scriptDataGuard = mozilla::MakeScopeExit([&] {
if (mode == XDR_DECODE) {
script->freeSharedData();
}
});
// NOTE: The script data is rooted by the script.
MOZ_TRY(PrivateScriptData::XDR<mode>(xdr, script, sourceObject,
scriptEnclosingScope, funOrMod));
MOZ_TRY(frontend::StencilXDR::SharedData<mode>(xdr, script->sharedData_));
if (mode == XDR_DECODE) {
if (!SharedImmutableScriptData::shareScriptData(cx, script->sharedData_)) {
return xdr->fail(JS::TranscodeResult::Throw);
}
}
if (xdrFlags & HasLazyScript) {
if (mode == XDR_DECODE) {
script->setAllowRelazify();
}
}
if (mode == XDR_DECODE) {
if (coverage::IsLCovEnabled()) {
if (!coverage::InitScriptCoverage(cx, script)) {
return xdr->fail(JS::TranscodeResult::Throw);
}
}
/* see BytecodeEmitter::tellDebuggerAboutCompiledScript */
if (!isFunctionScript && !cx->isHelperThreadContext()) {
DebugAPI::onNewScript(cx, script);
}
}
MOZ_ASSERT(script->code(), "Where's our bytecode?");
scriptDataGuard.release();
return Ok();
}
template XDRResult js::XDRScript(XDRState<XDR_ENCODE>*, HandleScope,
HandleScriptSourceObject, HandleObject,
MutableHandleScript);
template XDRResult js::XDRScript(XDRState<XDR_DECODE>*, HandleScope,
HandleScriptSourceObject, HandleObject,
MutableHandleScript);
template <XDRMode mode>
XDRResult js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope,
HandleScriptSourceObject sourceObject,
HandleFunction fun,
MutableHandle<BaseScript*> lazy) {
MOZ_ASSERT_IF(mode == XDR_DECODE, sourceObject);
JSContext* cx = xdr->cx();
{
SourceExtent extent;
uint32_t immutableFlags;
uint32_t ngcthings;
if (mode == XDR_ENCODE) {
MOZ_ASSERT(fun == lazy->function());
extent = lazy->extent();
immutableFlags = lazy->immutableFlags();
ngcthings = lazy->gcthings().size();
}
MOZ_TRY(XDRSourceExtent(xdr, &extent));
MOZ_TRY(xdr->codeUint32(&immutableFlags));
MOZ_TRY(xdr->codeUint32(&ngcthings));
if (mode == XDR_DECODE) {
lazy.set(BaseScript::CreateRawLazy(cx, ngcthings, fun, sourceObject,
extent, immutableFlags));
if (!lazy) {
return xdr->fail(JS::TranscodeResult::Throw);
}
// Set the enclosing scope of the lazy function. This value should only be
// set if we have a non-lazy enclosing script at this point.
// BaseScript::enclosingScriptHasEverBeenCompiled relies on the enclosing
// scope being non-null if we have ever been nested inside non-lazy
// function.
if (enclosingScope) {
lazy->setEnclosingScope(enclosingScope);
}
fun->initScript(lazy);
}
}
MOZ_TRY(BaseScript::XDRLazyScriptData(xdr, sourceObject, lazy));
return Ok();
}
template XDRResult js::XDRLazyScript(XDRState<XDR_ENCODE>*, HandleScope,
HandleScriptSourceObject, HandleFunction,
MutableHandle<BaseScript*>);
template XDRResult js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleScope,
HandleScriptSourceObject, HandleFunction,
MutableHandle<BaseScript*>);
bool JSScript::initScriptCounts(JSContext* cx) {
MOZ_ASSERT(!hasScriptCounts());
// Record all pc which are the first instruction of a basic block.
mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets;
js::BytecodeLocation main = mainLocation();
AllBytecodesIterable iterable(this);
for (auto& loc : iterable) {
if (loc.isJumpTarget() || loc == main) {
if (!jumpTargets.append(loc.toRawBytecode())) {
ReportOutOfMemory(cx);
return false;
}
}
}
// Initialize all PCCounts counters to 0.
ScriptCounts::PCCountsVector base;
if (!base.reserve(jumpTargets.length())) {
ReportOutOfMemory(cx);
return false;
}
for (size_t i = 0; i < jumpTargets.length(); i++) {
base.infallibleEmplaceBack(pcToOffset(jumpTargets[i]));
}
// Create zone's scriptCountsMap if necessary.
if (!zone()->scriptCountsMap) {
auto map = cx->make_unique<ScriptCountsMap>();
if (!map) {
return false;
}
zone()->scriptCountsMap = std::move(map);
}
// Allocate the ScriptCounts.
UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base));
if (!sc) {
ReportOutOfMemory(cx);
return false;
}
MOZ_ASSERT(this->hasBytecode());
// Register the current ScriptCounts in the zone's map.
if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) {
ReportOutOfMemory(cx);
return false;
}
// safe to set this; we can't fail after this point.
setHasScriptCounts();
// Enable interrupts in any interpreter frames running on this script. This
// is used to let the interpreter increment the PCCounts, if present.
for (ActivationIterator iter(cx); !iter.done(); ++iter) {
if (iter->isInterpreter()) {
iter->asInterpreter()->enableInterruptsIfRunning(this);
}
}
return true;
}
static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) {
MOZ_ASSERT(script->hasScriptCounts());
ScriptCountsMap::Ptr p = script->zone()->scriptCountsMap->lookup(script);
MOZ_ASSERT(p);
return p;
}
ScriptCounts& JSScript::getScriptCounts() {
ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
return *p->value();
}
js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) {
PCCounts searched = PCCounts(offset);
PCCounts* elem =
std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
return nullptr;
}
return elem;
}
const js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) const {
PCCounts searched = PCCounts(offset);
const PCCounts* elem =
std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
return nullptr;
}
return elem;
}
js::PCCounts* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset) {
PCCounts searched = PCCounts(offset);
PCCounts* elem =
std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
if (elem == pcCounts_.end()) {
return &pcCounts_.back();
}
if (elem->pcOffset() == offset) {
return elem;
}
if (elem != pcCounts_.begin()) {
return elem - 1;
}
return nullptr;
}
const js::PCCounts* ScriptCounts::maybeGetThrowCounts(size_t offset) const {
PCCounts searched = PCCounts(offset);
const PCCounts* elem =
std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
return nullptr;
}
return elem;
}
const js::PCCounts* ScriptCounts::getImmediatePrecedingThrowCounts(
size_t offset) const {
PCCounts searched = PCCounts(offset);
const PCCounts* elem =
std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
if (elem == throwCounts_.end()) {
if (throwCounts_.begin() == throwCounts_.end()) {
return nullptr;
}
return &throwCounts_.back();
}
if (elem->pcOffset() == offset) {
return elem;
}
if (elem != throwCounts_.begin()) {
return elem - 1;
}
return nullptr;
}
js::PCCounts* ScriptCounts::getThrowCounts(size_t offset) {
PCCounts searched = PCCounts(offset);
PCCounts* elem =
std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
elem = throwCounts_.insert(elem, searched);
}
return elem;
}
size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
size_t size = mallocSizeOf(this);
size += pcCounts_.sizeOfExcludingThis(mallocSizeOf);
size += throwCounts_.sizeOfExcludingThis(mallocSizeOf);
if (ionCounts_) {
size += ionCounts_->sizeOfIncludingThis(mallocSizeOf);
}
return size;
}
js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
MOZ_ASSERT(containsPC(pc));
return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
}
const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
MOZ_ASSERT(containsPC(pc));
return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc));
}
js::PCCounts* JSScript::getThrowCounts(jsbytecode* pc) {
MOZ_ASSERT(containsPC(pc));
return getScriptCounts().getThrowCounts(pcToOffset(pc));
}
uint64_t JSScript::getHitCount(jsbytecode* pc) {
MOZ_ASSERT(containsPC(pc));
if (pc < main()) {
pc = main();
}
ScriptCounts& sc = getScriptCounts();
size_t targetOffset = pcToOffset(pc);
const js::PCCounts* baseCount =
sc.getImmediatePrecedingPCCounts(targetOffset);
if (!baseCount) {
return 0;
}
if (baseCount->pcOffset() == targetOffset) {
return baseCount->numExec();
}
MOZ_ASSERT(baseCount->pcOffset() < targetOffset);
uint64_t count = baseCount->numExec();
do {
const js::PCCounts* throwCount =
sc.getImmediatePrecedingThrowCounts(targetOffset);
if (!throwCount) {
return count;
}
if (throwCount->pcOffset() <= baseCount->pcOffset()) {
return count;
}
count -= throwCount->numExec();
targetOffset = throwCount->pcOffset() - 1;
} while (true);
}
void JSScript::incHitCount(jsbytecode* pc) {
MOZ_ASSERT(containsPC(pc));
if (pc < main()) {
pc = main();
}
ScriptCounts& sc = getScriptCounts();
js::PCCounts* baseCount = sc.getImmediatePrecedingPCCounts(pcToOffset(pc));
if (!baseCount) {
return;
}
baseCount->numExec()++;
}
void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) {
ScriptCounts& sc = getScriptCounts();
if (sc.ionCounts_) {
ionCounts->setPrevious(sc.ionCounts_);
}
sc.ionCounts_ = ionCounts;
}
jit::IonScriptCounts* JSScript::getIonCounts() {
return getScriptCounts().ionCounts_;
}
void JSScript::releaseScriptCounts(ScriptCounts* counts) {
ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
*counts = std::move(*p->value().get());
zone()->scriptCountsMap->remove(p);
clearHasScriptCounts();
}
void JSScript::destroyScriptCounts() {
if (hasScriptCounts()) {
ScriptCounts scriptCounts;
releaseScriptCounts(&scriptCounts);
}
}
void JSScript::resetScriptCounts() {
if (!hasScriptCounts()) {
return;
}
ScriptCounts& sc = getScriptCounts();
for (PCCounts& elem : sc.pcCounts_) {
elem.numExec() = 0;
}
for (PCCounts& elem : sc.throwCounts_) {
elem.numExec() = 0;
}
}
void ScriptSourceObject::finalize(JSFreeOp* fop, JSObject* obj) {
MOZ_ASSERT(fop->onMainThread());
ScriptSourceObject* sso = &obj->as<ScriptSourceObject>();
if (sso->isCanonical()) {
sso->source()->finalizeGCData();
}
sso->source()->Release();
// Clear the private value, calling the release hook if necessary.
sso->setPrivate(fop->runtime(), UndefinedValue());
}
static const JSClassOps ScriptSourceObjectClassOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
ScriptSourceObject::finalize, // finalize
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
nullptr, // trace
};
const JSClass ScriptSourceObject::class_ = {
"ScriptSource",
JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
&ScriptSourceObjectClassOps};
ScriptSourceObject* ScriptSourceObject::createInternal(JSContext* cx,
ScriptSource* source,
HandleObject canonical) {
ScriptSourceObject* obj =
NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr);
if (!obj) {
return nullptr;
}
// The matching decref is in ScriptSourceObject::finalize.
obj->initReservedSlot(SOURCE_SLOT, PrivateValue(do_AddRef(source).take()));
if (canonical) {
obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*canonical));
} else {
obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*obj));
}
// The slots below should either be populated by a call to initFromOptions or,
// if this is a non-canonical ScriptSourceObject, they are unused. Poison
// them.
obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC));
obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC));
return obj;
}
ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
ScriptSource* source) {
return createInternal(cx, source, nullptr);
}
ScriptSourceObject* ScriptSourceObject::clone(JSContext* cx,
HandleScriptSourceObject sso) {
MOZ_ASSERT(cx->compartment() != sso->compartment());
RootedObject wrapped(cx, sso);
if (!cx->compartment()->wrap(cx, &wrapped)) {
return nullptr;
}
return createInternal(cx, sso->source(), wrapped);
}
ScriptSourceObject* ScriptSourceObject::unwrappedCanonical() const {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtimeFromAnyThread()));
JSObject* obj = &getReservedSlot(CANONICAL_SLOT).toObject();
return &UncheckedUnwrap(obj)->as<ScriptSourceObject>();
}
[[nodiscard]] static bool MaybeValidateFilename(
JSContext* cx, HandleScriptSourceObject sso,
const ReadOnlyCompileOptions& options) {
// When parsing off-thread we want to do filename validation on the main
// thread. This makes off-thread parsing more pure and is simpler because we
// can't easily throw exceptions off-thread.
MOZ_ASSERT(!cx->isHelperThreadContext());
if (!gFilenameValidationCallback) {
return true;
}
const char* filename = sso->source()->filename();
if (!filename || options.skipFilenameValidation()) {
return true;
}
if (gFilenameValidationCallback(filename, cx->realm()->isSystem())) {
return true;
}
const char* utf8Filename;
if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename))) {
utf8Filename = filename;
} else {
utf8Filename = "(invalid UTF-8 filename)";
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME,