Source code
Revision control
Copy as Markdown
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
#include "frontend/BytecodeCompiler.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/Variant.h" // mozilla::Variant
#include "debugger/DebugAPI.h"
#include "ds/LifoAlloc.h"
#include "frontend/BytecodeEmitter.h"
#include "frontend/CompilationStencil.h" // ExtensibleCompilationStencil, ExtraBindingInfoVector, CompilationInput, CompilationGCOutput
#include "frontend/EitherParser.h"
#ifdef JS_ENABLE_SMOOSH
# include "frontend/Frontend2.h" // Smoosh
#endif
#include "frontend/FrontendContext.h" // AutoReportFrontendContext
#include "frontend/ModuleSharedContext.h"
#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
#include "frontend/SharedContext.h" // SharedContext, GlobalSharedContext
#include "frontend/Stencil.h" // ParserBindingIter
#include "frontend/UsedNameTracker.h" // UsedNameTracker, UsedNameMap
#include "js/AllocPolicy.h" // js::SystemAllocPolicy, ReportOutOfMemory
#include "js/CharacterEncoding.h" // JS_EncodeStringToUTF8
#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
#include "js/ErrorReport.h" // JS_ReportErrorASCII
#include "js/experimental/JSStencil.h"
#include "js/GCVector.h" // JS::StackGCVector
#include "js/Id.h" // JS::PropertyKey
#include "js/Modules.h" // JS::ImportAssertionVector
#include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle
#include "js/SourceText.h" // JS::SourceText
#include "js/UniquePtr.h"
#include "js/Utility.h" // UniqueChars
#include "js/Value.h" // JS::Value
#include "vm/EnvironmentObject.h" // WithEnvironmentObject
#include "vm/FunctionFlags.h" // FunctionFlags
#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
#include "vm/HelperThreads.h" // StartOffThreadDelazification, WaitForAllDelazifyTasks
#include "vm/JSContext.h" // JSContext
#include "vm/JSObject.h" // SetIntegrityLevel, IntegrityLevel
#include "vm/JSScript.h" // ScriptSource, UncompressedSourceCache
#include "vm/ModuleBuilder.h" // js::ModuleBuilder
#include "vm/NativeObject.h" // NativeDefineDataProperty
#include "vm/PlainObject.h" // NewPlainObjectWithProto
#include "vm/StencilCache.h" // DelazificationCache
#include "vm/Time.h" // AutoIncrementalTimer
#include "wasm/AsmJS.h"
#include "vm/Compartment-inl.h" // JS::Compartment::wrap
#include "vm/GeckoProfiler-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h" // JSObject::maybeHasInterestingSymbolProperty for ObjectOperations-inl.h
#include "vm/ObjectOperations-inl.h" // HasProperty
using namespace js;
using namespace js::frontend;
using mozilla::Maybe;
using mozilla::Utf8Unit;
using JS::CompileOptions;
using JS::ReadOnlyCompileOptions;
using JS::SourceText;
// RAII class to check the frontend reports an exception when it fails to
// compile a script.
class MOZ_RAII AutoAssertReportedException {
#ifdef DEBUG
JSContext* maybeCx_;
FrontendContext* fc_;
bool check_;
public:
explicit AutoAssertReportedException(JSContext* maybeCx, FrontendContext* fc)
: maybeCx_(maybeCx), fc_(fc), check_(true) {}
void reset() { check_ = false; }
~AutoAssertReportedException() {
if (!check_) {
return;
}
// Error while compiling self-hosted code isn't set as an exception.
// TODO: Remove this once all errors are added to frontend context.
if (maybeCx_ && !maybeCx_->runtime()->hasInitializedSelfHosting()) {
return;
}
// TODO: Remove this once JSContext is removed from frontend.
if (maybeCx_) {
MOZ_ASSERT(maybeCx_->isExceptionPending() || fc_->hadErrors());
} else {
MOZ_ASSERT(fc_->hadErrors());
}
}
#else
public:
explicit AutoAssertReportedException(JSContext*, FrontendContext*) {}
void reset() {}
#endif
};
static bool EmplaceEmitter(CompilationState& compilationState,
Maybe<BytecodeEmitter>& emitter, FrontendContext* fc,
const EitherParser& parser, SharedContext* sc);
template <typename Unit>
class MOZ_STACK_CLASS SourceAwareCompiler {
protected:
SourceText<Unit>& sourceBuffer_;
CompilationState compilationState_;
Maybe<Parser<SyntaxParseHandler, Unit>> syntaxParser;
Maybe<Parser<FullParseHandler, Unit>> parser;
FrontendContext* fc_ = nullptr;
using TokenStreamPosition = frontend::TokenStreamPosition<Unit>;
protected:
explicit SourceAwareCompiler(FrontendContext* fc,
LifoAllocScope& parserAllocScope,
CompilationInput& input,
SourceText<Unit>& sourceBuffer)
: sourceBuffer_(sourceBuffer),
compilationState_(fc, parserAllocScope, input) {
MOZ_ASSERT(sourceBuffer_.get() != nullptr);
}
[[nodiscard]] bool init(FrontendContext* fc, ScopeBindingCache* scopeCache,
InheritThis inheritThis = InheritThis::No,
JSObject* enclosingEnv = nullptr) {
if (!compilationState_.init(fc, scopeCache, inheritThis, enclosingEnv)) {
return false;
}
return createSourceAndParser(fc);
}
// Call this before calling compile{Global,Eval}Script.
[[nodiscard]] bool createSourceAndParser(FrontendContext* fc);
void assertSourceAndParserCreated() const {
MOZ_ASSERT(compilationState_.source != nullptr);
MOZ_ASSERT(parser.isSome());
}
void assertSourceParserAndScriptCreated() { assertSourceAndParserCreated(); }
[[nodiscard]] bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter,
SharedContext* sharedContext) {
return EmplaceEmitter(compilationState_, emitter, fc_,
EitherParser(parser.ptr()), sharedContext);
}
bool canHandleParseFailure(const Directives& newDirectives);
void handleParseFailure(
const Directives& newDirectives, TokenStreamPosition& startPosition,
CompilationState::CompilationStatePosition& startStatePosition);
public:
CompilationState& compilationState() { return compilationState_; };
ExtensibleCompilationStencil& stencil() { return compilationState_; }
};
template <typename Unit>
class MOZ_STACK_CLASS ScriptCompiler : public SourceAwareCompiler<Unit> {
using Base = SourceAwareCompiler<Unit>;
protected:
using Base::compilationState_;
using Base::parser;
using Base::sourceBuffer_;
using Base::assertSourceParserAndScriptCreated;
using Base::canHandleParseFailure;
using Base::emplaceEmitter;
using Base::handleParseFailure;
using typename Base::TokenStreamPosition;
public:
explicit ScriptCompiler(FrontendContext* fc, LifoAllocScope& parserAllocScope,
CompilationInput& input,
SourceText<Unit>& sourceBuffer)
: Base(fc, parserAllocScope, input, sourceBuffer) {}
using Base::init;
using Base::stencil;
[[nodiscard]] bool compile(JSContext* cx, SharedContext* sc);
private:
[[nodiscard]] bool popupateExtraBindingsFields(GlobalSharedContext* globalsc);
};
#ifdef JS_ENABLE_SMOOSH
[[nodiscard]] static bool TrySmoosh(
JSContext* cx, FrontendContext* fc, CompilationInput& input,
JS::SourceText<mozilla::Utf8Unit>& srcBuf,
UniquePtr<ExtensibleCompilationStencil>& stencilOut) {
MOZ_ASSERT(!stencilOut);
if (!cx->options().trySmoosh()) {
return true;
}
JSRuntime* rt = cx->runtime();
if (!Smoosh::tryCompileGlobalScriptToExtensibleStencil(cx, fc, input, srcBuf,
stencilOut)) {
return false;
}
if (cx->options().trackNotImplemented()) {
if (stencilOut) {
rt->parserWatcherFile.put("1");
} else {
rt->parserWatcherFile.put("0");
}
}
if (!stencilOut) {
fprintf(stderr, "Falling back!\n");
return true;
}
return stencilOut->source->assignSource(fc, input.options, srcBuf);
}
[[nodiscard]] static bool TrySmoosh(
JSContext* cx, FrontendContext* fc, CompilationInput& input,
JS::SourceText<char16_t>& srcBuf,
UniquePtr<ExtensibleCompilationStencil>& stencilOut) {
MOZ_ASSERT(!stencilOut);
return true;
}
#endif // JS_ENABLE_SMOOSH
using BytecodeCompilerOutput =
mozilla::Variant<UniquePtr<ExtensibleCompilationStencil>,
RefPtr<CompilationStencil>, CompilationGCOutput*>;
static constexpr ExtraBindingInfoVector* NoExtraBindings = nullptr;
// Compile global script, and return it as one of:
// * ExtensibleCompilationStencil (without instantiation)
// * CompilationStencil (without instantiation, has no external dependency)
// * CompilationGCOutput (with instantiation).
template <typename Unit>
[[nodiscard]] static bool CompileGlobalScriptToStencilAndMaybeInstantiate(
JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
JS::SourceText<Unit>& srcBuf, ScopeKind scopeKind,
ExtraBindingInfoVector* maybeExtraBindings,
BytecodeCompilerOutput& output) {
#ifdef JS_ENABLE_SMOOSH
if (maybeCx) {
UniquePtr<ExtensibleCompilationStencil> extensibleStencil;
if (!TrySmoosh(maybeCx, fc, input, srcBuf, extensibleStencil)) {
return false;
}
if (extensibleStencil) {
if (input.options.populateDelazificationCache()) {
BorrowingCompilationStencil borrowingStencil(*extensibleStencil);
StartOffThreadDelazification(maybeCx, input.options, borrowingStencil);
// When we are trying to validate whether on-demand delazification
// generate the same stencil as concurrent delazification, we want to
// parse everything eagerly off-thread ahead of re-parsing everything on
// demand, to compare the outcome.
if (input.options.waitForDelazificationCache()) {
WaitForAllDelazifyTasks(maybeCx->runtime());
}
}
if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
output.as<UniquePtr<ExtensibleCompilationStencil>>() =
std::move(extensibleStencil);
} else if (output.is<RefPtr<CompilationStencil>>()) {
RefPtr<CompilationStencil> stencil =
fc->getAllocator()->new_<frontend::CompilationStencil>(
std::move(extensibleStencil));
if (!stencil) {
return false;
}
output.as<RefPtr<CompilationStencil>>() = std::move(stencil);
} else {
BorrowingCompilationStencil borrowingStencil(*extensibleStencil);
if (!InstantiateStencils(maybeCx, input, borrowingStencil,
*(output.as<CompilationGCOutput*>()))) {
return false;
}
}
return true;
}
}
#endif // JS_ENABLE_SMOOSH
if (input.options.selfHostingMode) {
if (!input.initForSelfHostingGlobal(fc)) {
return false;
}
} else if (maybeExtraBindings) {
if (!input.initForGlobalWithExtraBindings(fc, maybeExtraBindings)) {
return false;
}
} else {
if (!input.initForGlobal(fc)) {
return false;
}
}
AutoAssertReportedException assertException(maybeCx, fc);
LifoAllocScope parserAllocScope(&tempLifoAlloc);
ScriptCompiler<Unit> compiler(fc, parserAllocScope, input, srcBuf);
if (!compiler.init(fc, scopeCache)) {
return false;
}
SourceExtent extent = SourceExtent::makeGlobalExtent(
srcBuf.length(), input.options.lineno,
JS::LimitedColumnNumberOneOrigin::fromUnlimited(
JS::ColumnNumberOneOrigin(input.options.column)));
GlobalSharedContext globalsc(fc, scopeKind, input.options,
compiler.compilationState().directives, extent);
if (!compiler.compile(maybeCx, &globalsc)) {
return false;
}
if (input.options.populateDelazificationCache()) {
// NOTE: Delazification can be triggered from off-thread compilation.
BorrowingCompilationStencil borrowingStencil(compiler.stencil());
StartOffThreadDelazification(maybeCx, input.options, borrowingStencil);
// When we are trying to validate whether on-demand delazification
// generate the same stencil as concurrent delazification, we want to
// parse everything eagerly off-thread ahead of re-parsing everything on
// demand, to compare the outcome.
//
// This option works only from main-thread compilation, to avoid
// dead-lock.
if (input.options.waitForDelazificationCache() && maybeCx) {
WaitForAllDelazifyTasks(maybeCx->runtime());
}
}
if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
auto stencil =
fc->getAllocator()->make_unique<ExtensibleCompilationStencil>(
std::move(compiler.stencil()));
if (!stencil) {
return false;
}
output.as<UniquePtr<ExtensibleCompilationStencil>>() = std::move(stencil);
} else if (output.is<RefPtr<CompilationStencil>>()) {
Maybe<AutoGeckoProfilerEntry> pseudoFrame;
if (maybeCx) {
pseudoFrame.emplace(maybeCx, "script emit",
JS::ProfilingCategoryPair::JS_Parsing);
}
auto extensibleStencil =
fc->getAllocator()->make_unique<frontend::ExtensibleCompilationStencil>(
std::move(compiler.stencil()));
if (!extensibleStencil) {
return false;
}
RefPtr<CompilationStencil> stencil =
fc->getAllocator()->new_<CompilationStencil>(
std::move(extensibleStencil));
if (!stencil) {
return false;
}
output.as<RefPtr<CompilationStencil>>() = std::move(stencil);
} else {
MOZ_ASSERT(maybeCx);
BorrowingCompilationStencil borrowingStencil(compiler.stencil());
if (!InstantiateStencils(maybeCx, input, borrowingStencil,
*(output.as<CompilationGCOutput*>()))) {
return false;
}
}
assertException.reset();
return true;
}
template <typename Unit>
static already_AddRefed<CompilationStencil> CompileGlobalScriptToStencilImpl(
JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
JS::SourceText<Unit>& srcBuf, ScopeKind scopeKind) {
using OutputType = RefPtr<CompilationStencil>;
BytecodeCompilerOutput output((OutputType()));
if (!CompileGlobalScriptToStencilAndMaybeInstantiate(
maybeCx, fc, tempLifoAlloc, input, scopeCache, srcBuf, scopeKind,
NoExtraBindings, output)) {
return nullptr;
}
return output.as<OutputType>().forget();
}
already_AddRefed<CompilationStencil> frontend::CompileGlobalScriptToStencil(
JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
JS::SourceText<char16_t>& srcBuf, ScopeKind scopeKind) {
return CompileGlobalScriptToStencilImpl(cx, fc, tempLifoAlloc, input,
scopeCache, srcBuf, scopeKind);
}
already_AddRefed<CompilationStencil> frontend::CompileGlobalScriptToStencil(
JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
JS::SourceText<Utf8Unit>& srcBuf, ScopeKind scopeKind) {
return CompileGlobalScriptToStencilImpl(cx, fc, tempLifoAlloc, input,
scopeCache, srcBuf, scopeKind);
}
template <typename Unit>
static UniquePtr<ExtensibleCompilationStencil>
CompileGlobalScriptToExtensibleStencilImpl(JSContext* maybeCx,
FrontendContext* fc,
CompilationInput& input,
ScopeBindingCache* scopeCache,
JS::SourceText<Unit>& srcBuf,
ScopeKind scopeKind) {
using OutputType = UniquePtr<ExtensibleCompilationStencil>;
BytecodeCompilerOutput output((OutputType()));
if (!CompileGlobalScriptToStencilAndMaybeInstantiate(
maybeCx, fc, maybeCx->tempLifoAlloc(), input, scopeCache, srcBuf,
scopeKind, NoExtraBindings, output)) {
return nullptr;
}
return std::move(output.as<OutputType>());
}
UniquePtr<ExtensibleCompilationStencil>
frontend::CompileGlobalScriptToExtensibleStencil(
JSContext* maybeCx, FrontendContext* fc, CompilationInput& input,
ScopeBindingCache* scopeCache, JS::SourceText<char16_t>& srcBuf,
ScopeKind scopeKind) {
return CompileGlobalScriptToExtensibleStencilImpl(
maybeCx, fc, input, scopeCache, srcBuf, scopeKind);
}
UniquePtr<ExtensibleCompilationStencil>
frontend::CompileGlobalScriptToExtensibleStencil(
JSContext* cx, FrontendContext* fc, CompilationInput& input,
ScopeBindingCache* scopeCache, JS::SourceText<Utf8Unit>& srcBuf,
ScopeKind scopeKind) {
return CompileGlobalScriptToExtensibleStencilImpl(cx, fc, input, scopeCache,
srcBuf, scopeKind);
}
static void FireOnNewScript(JSContext* cx,
const JS::InstantiateOptions& options,
JS::Handle<JSScript*> script) {
if (!options.hideFromNewScriptInitial()) {
DebugAPI::onNewScript(cx, script);
}
}
bool frontend::InstantiateStencils(JSContext* cx, CompilationInput& input,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
{
AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate",
JS::ProfilingCategoryPair::JS_Parsing);
if (!CompilationStencil::instantiateStencils(cx, input, stencil,
gcOutput)) {
return false;
}
}
// Enqueue an off-thread source compression task after finishing parsing.
if (!stencil.source->tryCompressOffThread(cx)) {
return false;
}
Rooted<JSScript*> script(cx, gcOutput.script);
const JS::InstantiateOptions instantiateOptions(input.options);
FireOnNewScript(cx, instantiateOptions, script);
return true;
}
template <typename Unit>
static JSScript* CompileGlobalScriptImpl(
JSContext* cx, FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options, JS::SourceText<Unit>& srcBuf,
ScopeKind scopeKind, ExtraBindingInfoVector* maybeExtraBindings) {
Rooted<CompilationInput> input(cx, CompilationInput(options));
Rooted<CompilationGCOutput> gcOutput(cx);
BytecodeCompilerOutput output(gcOutput.address());
NoScopeBindingCache scopeCache;
if (!CompileGlobalScriptToStencilAndMaybeInstantiate(
cx, fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf,
scopeKind, maybeExtraBindings, output)) {
return nullptr;
}
return gcOutput.get().script;
}
JSScript* frontend::CompileGlobalScript(
JSContext* cx, FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options, JS::SourceText<char16_t>& srcBuf,
ScopeKind scopeKind) {
return CompileGlobalScriptImpl(cx, fc, options, srcBuf, scopeKind,
NoExtraBindings);
}
static bool CreateExtraBindingInfoVector(
JSContext* cx,
JS::Handle<JS::StackGCVector<JS::PropertyKey>> unwrappedBindingKeys,
JS::Handle<JS::StackGCVector<JS::Value>> unwrappedBindingValues,
ExtraBindingInfoVector& extraBindings) {
MOZ_ASSERT(unwrappedBindingKeys.length() == unwrappedBindingValues.length());
if (!extraBindings.reserve(unwrappedBindingKeys.length())) {
ReportOutOfMemory(cx);
return false;
}
JS::Rooted<JSObject*> globalLexical(cx, &cx->global()->lexicalEnvironment());
JS::Rooted<JS::PropertyKey> id(cx);
for (size_t i = 0; i < unwrappedBindingKeys.length(); i++) {
if (!unwrappedBindingKeys[i].isString()) {
JS_ReportErrorASCII(cx, "The bindings key should be a string.");
return false;
}
JS::Rooted<JSString*> str(cx, unwrappedBindingKeys[i].toString());
UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
if (!utf8chars) {
return false;
}
bool isShadowed = false;
id = unwrappedBindingKeys[i];
cx->markId(id);
bool found;
if (!HasProperty(cx, cx->global(), id, &found)) {
return false;
}
if (found) {
isShadowed = true;
} else {
if (!HasProperty(cx, globalLexical, id, &found)) {
return false;
}
if (found) {
isShadowed = true;
}
}
extraBindings.infallibleEmplaceBack(std::move(utf8chars), isShadowed);
}
return true;
}
static WithEnvironmentObject* CreateExtraBindingsEnvironment(
JSContext* cx,
JS::Handle<JS::StackGCVector<JS::PropertyKey>> unwrappedBindingKeys,
JS::Handle<JS::StackGCVector<JS::Value>> unwrappedBindingValues,
const ExtraBindingInfoVector& extraBindings) {
JS::Rooted<PlainObject*> extraBindingsObj(
cx, NewPlainObjectWithProto(cx, nullptr));
if (!extraBindingsObj) {
return nullptr;
}
MOZ_ASSERT(unwrappedBindingKeys.length() == extraBindings.length());
JS::Rooted<JS::PropertyKey> id(cx);
size_t i = 0;
for (const auto& bindingInfo : extraBindings) {
if (bindingInfo.isShadowed) {
i++;
continue;
}
id = unwrappedBindingKeys[i];
cx->markId(id);
JS::Rooted<JS::Value> val(cx, unwrappedBindingValues[i]);
if (!cx->compartment()->wrap(cx, &val) ||
!NativeDefineDataProperty(cx, extraBindingsObj, id, val, 0)) {
return nullptr;
}
i++;
}
// The list of bindings shouldn't be modified.
if (!SetIntegrityLevel(cx, extraBindingsObj, IntegrityLevel::Sealed)) {
return nullptr;
}
JS::Rooted<JSObject*> globalLexical(cx, &cx->global()->lexicalEnvironment());
return WithEnvironmentObject::createNonSyntactic(cx, extraBindingsObj,
globalLexical);
}
JSScript* frontend::CompileGlobalScriptWithExtraBindings(
JSContext* cx, FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options, JS::SourceText<char16_t>& srcBuf,
JS::Handle<JS::StackGCVector<JS::PropertyKey>> unwrappedBindingKeys,
JS::Handle<JS::StackGCVector<JS::Value>> unwrappedBindingValues,
JS::MutableHandle<JSObject*> env) {
ExtraBindingInfoVector extraBindings;
if (!CreateExtraBindingInfoVector(cx, unwrappedBindingKeys,
unwrappedBindingValues, extraBindings)) {
return nullptr;
}
JS::Rooted<JSScript*> script(
cx, CompileGlobalScriptImpl(cx, fc, options, srcBuf,
ScopeKind::NonSyntactic, &extraBindings));
if (!script) {
if (fc->extraBindingsAreNotUsed()) {
// Compile the script as regular global script in global lexical.
fc->clearNoExtraBindingReferencesFound();
// Warnings can be reported. Clear them to avoid reporting twice.
fc->clearWarnings();
// No other error should be reported.
MOZ_ASSERT(!fc->hadErrors());
MOZ_ASSERT(!cx->isExceptionPending());
env.set(&cx->global()->lexicalEnvironment());
JS::CompileOptions copiedOptions(nullptr, options);
copiedOptions.setNonSyntacticScope(false);
return CompileGlobalScript(cx, fc, copiedOptions, srcBuf,
ScopeKind::Global);
}
return nullptr;
}
WithEnvironmentObject* extraBindingsEnv = CreateExtraBindingsEnvironment(
cx, unwrappedBindingKeys, unwrappedBindingValues, extraBindings);
if (!extraBindingsEnv) {
return nullptr;
}
env.set(extraBindingsEnv);
return script;
}
JSScript* frontend::CompileGlobalScript(
JSContext* cx, FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options, JS::SourceText<Utf8Unit>& srcBuf,
ScopeKind scopeKind) {
return CompileGlobalScriptImpl(cx, fc, options, srcBuf, scopeKind,
NoExtraBindings);
}
template <typename Unit>
static JSScript* CompileEvalScriptImpl(
JSContext* cx, const JS::ReadOnlyCompileOptions& options,
SourceText<Unit>& srcBuf, JS::Handle<js::Scope*> enclosingScope,
JS::Handle<JSObject*> enclosingEnv) {
JS::Rooted<JSScript*> script(cx);
{
AutoReportFrontendContext fc(cx);
AutoAssertReportedException assertException(cx, &fc);
Rooted<CompilationInput> input(cx, CompilationInput(options));
if (!input.get().initForEval(&fc, enclosingScope)) {
return nullptr;
}
LifoAllocScope parserAllocScope(&cx->tempLifoAlloc());
ScopeBindingCache* scopeCache = &cx->caches().scopeCache;
ScriptCompiler<Unit> compiler(&fc, parserAllocScope, input.get(), srcBuf);
if (!compiler.init(&fc, scopeCache, InheritThis::Yes, enclosingEnv)) {
return nullptr;
}
uint32_t len = srcBuf.length();
SourceExtent extent = SourceExtent::makeGlobalExtent(
len, options.lineno,
JS::LimitedColumnNumberOneOrigin::fromUnlimited(
JS::ColumnNumberOneOrigin(options.column)));
EvalSharedContext evalsc(&fc, compiler.compilationState(), extent);
if (!compiler.compile(cx, &evalsc)) {
return nullptr;
}
Rooted<CompilationGCOutput> gcOutput(cx);
{
BorrowingCompilationStencil borrowingStencil(compiler.stencil());
if (!InstantiateStencils(cx, input.get(), borrowingStencil,
gcOutput.get())) {
return nullptr;
}
}
assertException.reset();
script = gcOutput.get().script;
}
return script;
}
JSScript* frontend::CompileEvalScript(JSContext* cx,
const JS::ReadOnlyCompileOptions& options,
JS::SourceText<char16_t>& srcBuf,
JS::Handle<js::Scope*> enclosingScope,
JS::Handle<JSObject*> enclosingEnv) {
return CompileEvalScriptImpl(cx, options, srcBuf, enclosingScope,
enclosingEnv);
}
template <typename Unit>
class MOZ_STACK_CLASS ModuleCompiler final : public SourceAwareCompiler<Unit> {
using Base = SourceAwareCompiler<Unit>;
using Base::assertSourceParserAndScriptCreated;
using Base::compilationState_;
using Base::emplaceEmitter;
using Base::parser;
public:
explicit ModuleCompiler(FrontendContext* fc, LifoAllocScope& parserAllocScope,
CompilationInput& input,
SourceText<Unit>& sourceBuffer)
: Base(fc, parserAllocScope, input, sourceBuffer) {}
using Base::init;
using Base::stencil;
[[nodiscard]] bool compile(JSContext* maybeCx, FrontendContext* fc);
};
template <typename Unit>
class MOZ_STACK_CLASS StandaloneFunctionCompiler final
: public SourceAwareCompiler<Unit> {
using Base = SourceAwareCompiler<Unit>;
using Base::assertSourceAndParserCreated;
using Base::canHandleParseFailure;
using Base::compilationState_;
using Base::emplaceEmitter;
using Base::handleParseFailure;
using Base::parser;
using Base::sourceBuffer_;
using typename Base::TokenStreamPosition;
public:
explicit StandaloneFunctionCompiler(FrontendContext* fc,
LifoAllocScope& parserAllocScope,
CompilationInput& input,
SourceText<Unit>& sourceBuffer)
: Base(fc, parserAllocScope, input, sourceBuffer) {}
using Base::init;
using Base::stencil;
private:
FunctionNode* parse(JSContext* cx, FunctionSyntaxKind syntaxKind,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
const Maybe<uint32_t>& parameterListEnd);
public:
[[nodiscard]] bool compile(JSContext* cx, FunctionSyntaxKind syntaxKind,
GeneratorKind generatorKind,
FunctionAsyncKind asyncKind,
const Maybe<uint32_t>& parameterListEnd);
};
template <typename Unit>
bool SourceAwareCompiler<Unit>::createSourceAndParser(FrontendContext* fc) {
const auto& options = compilationState_.input.options;
fc_ = fc;
if (!compilationState_.source->assignSource(fc, options, sourceBuffer_)) {
return false;
}
MOZ_ASSERT(compilationState_.canLazilyParse ==
CanLazilyParse(compilationState_.input.options));
if (compilationState_.canLazilyParse) {
syntaxParser.emplace(fc_, options, sourceBuffer_.units(),
sourceBuffer_.length(),
/* foldConstants = */ false, compilationState_,
/* syntaxParser = */ nullptr);
if (!syntaxParser->checkOptions()) {
return false;
}
}
parser.emplace(fc_, options, sourceBuffer_.units(), sourceBuffer_.length(),
/* foldConstants = */ true, compilationState_,
syntaxParser.ptrOr(nullptr));
parser->ss = compilationState_.source.get();
return parser->checkOptions();
}
static bool EmplaceEmitter(CompilationState& compilationState,
Maybe<BytecodeEmitter>& emitter, FrontendContext* fc,
const EitherParser& parser, SharedContext* sc) {
BytecodeEmitter::EmitterMode emitterMode =
sc->selfHosted() ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
emitter.emplace(fc, parser, sc, compilationState, emitterMode);
return emitter->init();
}
template <typename Unit>
bool SourceAwareCompiler<Unit>::canHandleParseFailure(
const Directives& newDirectives) {
// Try to reparse if no parse errors were thrown and the directives changed.
//
// NOTE:
// Only the following two directive changes force us to reparse the script:
// - The "use asm" directive was encountered.
// - The "use strict" directive was encountered and duplicate parameter names
// are present. We reparse in this case to display the error at the correct
// source location. See |Parser::hasValidSimpleStrictParameterNames()|.
return !parser->anyChars.hadError() &&
compilationState_.directives != newDirectives;
}
template <typename Unit>
void SourceAwareCompiler<Unit>::handleParseFailure(
const Directives& newDirectives, TokenStreamPosition& startPosition,
CompilationState::CompilationStatePosition& startStatePosition) {
MOZ_ASSERT(canHandleParseFailure(newDirectives));
// Rewind to starting position to retry.
parser->tokenStream.rewind(startPosition);
compilationState_.rewind(startStatePosition);
// Assignment must be monotonic to prevent reparsing iloops
MOZ_ASSERT_IF(compilationState_.directives.strict(), newDirectives.strict());
MOZ_ASSERT_IF(compilationState_.directives.asmJS(), newDirectives.asmJS());
compilationState_.directives = newDirectives;
}
static bool UsesExtraBindings(GlobalSharedContext* globalsc,
const ExtraBindingInfoVector& extraBindings,
const UsedNameTracker::UsedNameMap& usedNameMap) {
for (const auto& bindingInfo : extraBindings) {
if (bindingInfo.isShadowed) {
continue;
}
for (auto r = usedNameMap.all(); !r.empty(); r.popFront()) {
const auto& item = r.front();
const auto& name = item.key();
if (bindingInfo.nameIndex != name) {
continue;
}
const auto& nameInfo = item.value();
if (nameInfo.empty()) {
continue;
}
// This name is free, and uses the extra binding.
return true;
}
}
return false;
}
template <typename Unit>
bool ScriptCompiler<Unit>::popupateExtraBindingsFields(
GlobalSharedContext* globalsc) {
if (!compilationState_.input.internExtraBindings(
this->fc_, compilationState_.parserAtoms)) {
return false;
}
bool hasNonShadowedBinding = false;
for (auto& bindingInfo : compilationState_.input.extraBindings()) {
if (bindingInfo.isShadowed) {
continue;
}
bool isShadowed = false;
if (globalsc->bindings) {
for (ParserBindingIter bi(*globalsc->bindings); bi; bi++) {
if (bindingInfo.nameIndex == bi.name()) {
isShadowed = true;
break;
}
}
}
bindingInfo.isShadowed = isShadowed;
if (!isShadowed) {
hasNonShadowedBinding = true;
}
}
if (!hasNonShadowedBinding) {
// All bindings are shadowed.
this->fc_->reportExtraBindingsAreNotUsed();
return false;
}
if (globalsc->hasDirectEval()) {
// Direct eval can contain reference.
return true;
}
if (!UsesExtraBindings(globalsc, compilationState_.input.extraBindings(),
parser->usedNames().map())) {
this->fc_->reportExtraBindingsAreNotUsed();
return false;
}
return true;
}
template <typename Unit>
bool ScriptCompiler<Unit>::compile(JSContext* maybeCx, SharedContext* sc) {
assertSourceParserAndScriptCreated();
TokenStreamPosition startPosition(parser->tokenStream);
// Emplace the topLevel stencil
MOZ_ASSERT(compilationState_.scriptData.length() ==
CompilationStencil::TopLevelIndex);
if (!compilationState_.appendScriptStencilAndData(sc->fc_)) {
return false;
}
ParseNode* pn;
{
Maybe<AutoGeckoProfilerEntry> pseudoFrame;
if (maybeCx) {
pseudoFrame.emplace(maybeCx, "script parsing",
JS::ProfilingCategoryPair::JS_Parsing);
}
if (sc->isEvalContext()) {
pn = parser->evalBody(sc->asEvalContext()).unwrapOr(nullptr);
} else {
pn = parser->globalBody(sc->asGlobalContext()).unwrapOr(nullptr);
}
}
if (!pn) {
// Global and eval scripts don't get reparsed after a new directive was
// encountered:
// - "use strict" doesn't require any special error reporting for scripts.
// - "use asm" directives don't have an effect in global/eval contexts.
MOZ_ASSERT(!canHandleParseFailure(compilationState_.directives));
return false;
}
if (sc->isGlobalContext() && compilationState_.input.hasExtraBindings()) {
if (!popupateExtraBindingsFields(sc->asGlobalContext())) {
return false;
}
}
{
// Successfully parsed. Emit the script.
Maybe<AutoGeckoProfilerEntry> pseudoFrame;
if (maybeCx) {
pseudoFrame.emplace(maybeCx, "script emit",
JS::ProfilingCategoryPair::JS_Parsing);
}
Maybe<BytecodeEmitter> emitter;
if (!emplaceEmitter(emitter, sc)) {
return false;
}
if (!emitter->emitScript(pn)) {
return false;
}
}
MOZ_ASSERT(!this->fc_->hadErrors());
return true;
}
template <typename Unit>
bool ModuleCompiler<Unit>::compile(JSContext* maybeCx, FrontendContext* fc) {
// Emplace the topLevel stencil
MOZ_ASSERT(compilationState_.scriptData.length() ==
CompilationStencil::TopLevelIndex);
if (!compilationState_.appendScriptStencilAndData(fc)) {
return false;
}
ModuleBuilder builder(fc, parser.ptr());
const auto& options = compilationState_.input.options;
uint32_t len = this->sourceBuffer_.length();
SourceExtent extent = SourceExtent::makeGlobalExtent(
len, options.lineno,
JS::LimitedColumnNumberOneOrigin::fromUnlimited(
JS::ColumnNumberOneOrigin(options.column)));
ModuleSharedContext modulesc(fc, options, builder, extent);
ParseNode* pn = parser->moduleBody(&modulesc).unwrapOr(nullptr);
if (!pn) {
return false;
}
Maybe<BytecodeEmitter> emitter;
if (!emplaceEmitter(emitter, &modulesc)) {
return false;
}
if (!emitter->emitScript(pn->as<ModuleNode>().body())) {
return false;
}
StencilModuleMetadata& moduleMetadata = *compilationState_.moduleMetadata;
builder.finishFunctionDecls(moduleMetadata);
MOZ_ASSERT(!this->fc_->hadErrors());
return true;
}
// Parse a standalone JS function, which might appear as the value of an
// event handler attribute in an HTML <INPUT> tag, or in a Function()
// constructor.
template <typename Unit>
FunctionNode* StandaloneFunctionCompiler<Unit>::parse(
JSContext* cx, FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind,
FunctionAsyncKind asyncKind, const Maybe<uint32_t>& parameterListEnd) {
assertSourceAndParserCreated();
TokenStreamPosition startPosition(parser->tokenStream);
auto startStatePosition = compilationState_.getPosition();
// Speculatively parse using the default directives implied by the context.
// If a directive is encountered (e.g., "use strict") that changes how the
// function should have been parsed, we backup and reparse with the new set
// of directives.
FunctionNode* fn;
for (;;) {
Directives newDirectives = compilationState_.directives;
fn = parser
->standaloneFunction(parameterListEnd, syntaxKind, generatorKind,
asyncKind, compilationState_.directives,
&newDirectives)
.unwrapOr(nullptr);
if (fn) {
break;
}
// Maybe we encountered a new directive. See if we can try again.
if (!canHandleParseFailure(newDirectives)) {
return nullptr;
}
handleParseFailure(newDirectives, startPosition, startStatePosition);
}
return fn;
}
// Compile a standalone JS function.
template <typename Unit>
bool StandaloneFunctionCompiler<Unit>::compile(
JSContext* cx, FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind,
FunctionAsyncKind asyncKind, const Maybe<uint32_t>& parameterListEnd) {
FunctionNode* parsedFunction =
parse(cx, syntaxKind, generatorKind, asyncKind, parameterListEnd);
if (!parsedFunction) {
return false;
}
FunctionBox* funbox = parsedFunction->funbox();
if (funbox->isInterpreted()) {
Maybe<BytecodeEmitter> emitter;
if (!emplaceEmitter(emitter, funbox)) {
return false;
}
if (!emitter->emitFunctionScript(parsedFunction)) {
return false;
}
// The parser extent has stripped off the leading `function...` but
// we want the SourceExtent used in the final standalone script to
// start from the beginning of the buffer, and use the provided
// line and column.
const auto& options = compilationState_.input.options;
compilationState_.scriptExtra[CompilationStencil::TopLevelIndex].extent =
SourceExtent{/* sourceStart = */ 0,
sourceBuffer_.length(),
funbox->extent().toStringStart,
funbox->extent().toStringEnd,
options.lineno,
JS::LimitedColumnNumberOneOrigin::fromUnlimited(
JS::ColumnNumberOneOrigin(options.column))};
} else {
// The asm.js module was created by parser. Instantiation below will
// allocate the JSFunction that wraps it.
MOZ_ASSERT(funbox->isAsmJSModule());
MOZ_ASSERT(compilationState_.asmJS->moduleMap.has(funbox->index()));
MOZ_ASSERT(compilationState_.scriptData[CompilationStencil::TopLevelIndex]
.functionFlags.isAsmJSNative());
}
return true;
}
// Compile module, and return it as one of:
// * ExtensibleCompilationStencil (without instantiation)
// * CompilationStencil (without instantiation, has no external dependency)
// * CompilationGCOutput (with instantiation).
template <typename Unit>
[[nodiscard]] static bool ParseModuleToStencilAndMaybeInstantiate(
JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
SourceText<Unit>& srcBuf, BytecodeCompilerOutput& output) {
MOZ_ASSERT(srcBuf.get());
MOZ_ASSERT(input.options.lineno != 0,
"Module cannot be compiled with lineNumber == 0");
if (!input.initForModule(fc)) {
return false;
}
AutoAssertReportedException assertException(maybeCx, fc);
LifoAllocScope parserAllocScope(&tempLifoAlloc);
ModuleCompiler<Unit> compiler(fc, parserAllocScope, input, srcBuf);
if (!compiler.init(fc, scopeCache)) {
return false;
}
if (!compiler.compile(maybeCx, fc)) {
return false;
}
if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
auto stencil =
fc->getAllocator()->make_unique<ExtensibleCompilationStencil>(
std::move(compiler.stencil()));
if (!stencil) {
return false;
}
output.as<UniquePtr<ExtensibleCompilationStencil>>() = std::move(stencil);
} else if (output.is<RefPtr<CompilationStencil>>()) {
Maybe<AutoGeckoProfilerEntry> pseudoFrame;
if (maybeCx) {
pseudoFrame.emplace(maybeCx, "script emit",
JS::ProfilingCategoryPair::JS_Parsing);
}
auto extensibleStencil =
fc->getAllocator()->make_unique<frontend::ExtensibleCompilationStencil>(
std::move(compiler.stencil()));
if (!extensibleStencil) {
return false;
}
RefPtr<CompilationStencil> stencil =
fc->getAllocator()->new_<CompilationStencil>(
std::move(extensibleStencil));
if (!stencil) {
return false;
}
output.as<RefPtr<CompilationStencil>>() = std::move(stencil);
} else {
MOZ_ASSERT(maybeCx);
BorrowingCompilationStencil borrowingStencil(compiler.stencil());
if (!InstantiateStencils(maybeCx, input, borrowingStencil,
*(output.as<CompilationGCOutput*>()))) {
return false;
}
}
assertException.reset();
return true;
}
template <typename Unit>
already_AddRefed<CompilationStencil> ParseModuleToStencilImpl(
JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
SourceText<Unit>& srcBuf) {
using OutputType = RefPtr<CompilationStencil>;
BytecodeCompilerOutput output((OutputType()));
if (!ParseModuleToStencilAndMaybeInstantiate(
maybeCx, fc, tempLifoAlloc, input, scopeCache, srcBuf, output)) {
return nullptr;
}
return output.as<OutputType>().forget();
}
already_AddRefed<CompilationStencil> frontend::ParseModuleToStencil(
JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
SourceText<char16_t>& srcBuf) {
return ParseModuleToStencilImpl(maybeCx, fc, tempLifoAlloc, input, scopeCache,
srcBuf);
}
already_AddRefed<CompilationStencil> frontend::ParseModuleToStencil(
JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
SourceText<Utf8Unit>& srcBuf) {
return ParseModuleToStencilImpl(maybeCx, fc, tempLifoAlloc, input, scopeCache,
srcBuf);
}
template <typename Unit>
UniquePtr<ExtensibleCompilationStencil> ParseModuleToExtensibleStencilImpl(
JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
CompilationInput& input, ScopeBindingCache* scopeCache,
SourceText<Unit>& srcBuf) {
using OutputType = UniquePtr<ExtensibleCompilationStencil>;
BytecodeCompilerOutput output((OutputType()));
if (!ParseModuleToStencilAndMaybeInstantiate(cx, fc, tempLifoAlloc, input,
scopeCache, srcBuf, output)) {
return nullptr;
}
return std::move(output.as<OutputType>());
}
UniquePtr<ExtensibleCompilationStencil>
frontend::ParseModuleToExtensibleStencil(JSContext* cx, FrontendContext* fc,
js::LifoAlloc& tempLifoAlloc,
CompilationInput& input,
ScopeBindingCache* scopeCache,
SourceText<char16_t>& srcBuf) {
return ParseModuleToExtensibleStencilImpl(cx, fc, tempLifoAlloc, input,
scopeCache, srcBuf);
}
UniquePtr<ExtensibleCompilationStencil>
frontend::ParseModuleToExtensibleStencil(JSContext* cx, FrontendContext* fc,
js::LifoAlloc& tempLifoAlloc,
CompilationInput& input,
ScopeBindingCache* scopeCache,
SourceText<Utf8Unit>& srcBuf) {
return ParseModuleToExtensibleStencilImpl(cx, fc, tempLifoAlloc, input,
scopeCache, srcBuf);
}
template <typename Unit>
static ModuleObject* CompileModuleImpl(
JSContext* cx, FrontendContext* fc,
const JS::ReadOnlyCompileOptions& optionsInput, SourceText<Unit>& srcBuf) {
AutoAssertReportedException assertException(cx, fc);
CompileOptions options(cx, optionsInput);