mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-27 02:29:35 +00:00
887 lines
29 KiB
C++
887 lines
29 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* 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/. */
|
|
|
|
#include "frontend/BytecodeCompiler.h"
|
|
|
|
#include "jscntxt.h"
|
|
#include "jsscript.h"
|
|
|
|
#include "asmjs/AsmJSLink.h"
|
|
#include "builtin/ModuleObject.h"
|
|
#include "frontend/BytecodeEmitter.h"
|
|
#include "frontend/FoldConstants.h"
|
|
#include "frontend/NameFunctions.h"
|
|
#include "frontend/Parser.h"
|
|
#include "vm/GlobalObject.h"
|
|
#include "vm/TraceLogging.h"
|
|
|
|
#include "jsobjinlines.h"
|
|
#include "jsscriptinlines.h"
|
|
|
|
#include "frontend/Parser-inl.h"
|
|
#include "vm/ScopeObject-inl.h"
|
|
|
|
using namespace js;
|
|
using namespace js::frontend;
|
|
using mozilla::Maybe;
|
|
|
|
class MOZ_STACK_CLASS AutoCompilationTraceLogger
|
|
{
|
|
public:
|
|
AutoCompilationTraceLogger(ExclusiveContext* cx, const TraceLoggerTextId id,
|
|
const ReadOnlyCompileOptions& options);
|
|
|
|
private:
|
|
TraceLoggerThread* logger;
|
|
TraceLoggerEvent event;
|
|
AutoTraceLog scriptLogger;
|
|
AutoTraceLog typeLogger;
|
|
};
|
|
|
|
// The BytecodeCompiler class contains resources common to compiling scripts and
|
|
// function bodies.
|
|
class MOZ_STACK_CLASS BytecodeCompiler
|
|
{
|
|
public:
|
|
// Construct an object passing mandatory arguments.
|
|
BytecodeCompiler(ExclusiveContext* cx,
|
|
LifoAlloc* alloc,
|
|
const ReadOnlyCompileOptions& options,
|
|
SourceBufferHolder& sourceBuffer,
|
|
Handle<ScopeObject*> enclosingStaticScope,
|
|
TraceLoggerTextId logId);
|
|
|
|
// Call setters for optional arguments.
|
|
void maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor);
|
|
void setSourceArgumentsNotIncluded();
|
|
|
|
JSScript* compileScript(HandleObject scopeChain, HandleScript evalCaller);
|
|
ModuleObject* compileModule();
|
|
bool compileFunctionBody(MutableHandleFunction fun, Handle<PropertyNameVector> formals,
|
|
GeneratorKind generatorKind);
|
|
|
|
ScriptSourceObject* sourceObjectPtr() const;
|
|
|
|
private:
|
|
bool checkLength();
|
|
bool createScriptSource();
|
|
bool maybeCompressSource();
|
|
bool canLazilyParse();
|
|
bool createParser();
|
|
bool createSourceAndParser();
|
|
bool createScript(HandleObject staticScope, bool savedCallerFun = false);
|
|
bool createEmitter(SharedContext* sharedContext, HandleScript evalCaller = nullptr,
|
|
bool insideNonGlobalEval = false);
|
|
bool isEvalCompilationUnit();
|
|
bool isNonGlobalEvalCompilationUnit();
|
|
bool isNonSyntacticCompilationUnit();
|
|
bool saveCallerFun(HandleScript evalCaller);
|
|
bool handleParseFailure(const Directives& newDirectives);
|
|
bool prepareAndEmitTree(ParseNode** pn);
|
|
bool checkArgumentsWithinEval(JSContext* cx, HandleFunction fun);
|
|
bool maybeCheckEvalFreeVariables(HandleScript evalCaller, HandleObject scopeChain,
|
|
ParseContext<FullParseHandler>& pc);
|
|
bool maybeSetDisplayURL(TokenStream& tokenStream);
|
|
bool maybeSetSourceMap(TokenStream& tokenStream);
|
|
bool maybeSetSourceMapFromOptions();
|
|
bool emitFinalReturn();
|
|
bool initGlobalOrEvalBindings(ParseContext<FullParseHandler>& pc);
|
|
bool maybeCompleteCompressSource();
|
|
|
|
AutoCompilationTraceLogger traceLogger;
|
|
AutoKeepAtoms keepAtoms;
|
|
|
|
ExclusiveContext* cx;
|
|
LifoAlloc* alloc;
|
|
const ReadOnlyCompileOptions& options;
|
|
SourceBufferHolder& sourceBuffer;
|
|
|
|
Rooted<ScopeObject*> enclosingStaticScope;
|
|
bool sourceArgumentsNotIncluded;
|
|
|
|
RootedScriptSource sourceObject;
|
|
ScriptSource* scriptSource;
|
|
|
|
Maybe<SourceCompressionTask> maybeSourceCompressor;
|
|
SourceCompressionTask* sourceCompressor;
|
|
|
|
Maybe<Parser<SyntaxParseHandler>> syntaxParser;
|
|
Maybe<Parser<FullParseHandler>> parser;
|
|
|
|
Directives directives;
|
|
TokenStream::Position startPosition;
|
|
|
|
RootedScript script;
|
|
Maybe<BytecodeEmitter> emitter;
|
|
};
|
|
|
|
AutoCompilationTraceLogger::AutoCompilationTraceLogger(ExclusiveContext* cx,
|
|
const TraceLoggerTextId id, const ReadOnlyCompileOptions& options)
|
|
: logger(cx->isJSContext() ? TraceLoggerForMainThread(cx->asJSContext()->runtime())
|
|
: TraceLoggerForCurrentThread()),
|
|
event(logger, TraceLogger_AnnotateScripts, options),
|
|
scriptLogger(logger, event),
|
|
typeLogger(logger, id)
|
|
{}
|
|
|
|
BytecodeCompiler::BytecodeCompiler(ExclusiveContext* cx,
|
|
LifoAlloc* alloc,
|
|
const ReadOnlyCompileOptions& options,
|
|
SourceBufferHolder& sourceBuffer,
|
|
Handle<ScopeObject*> enclosingStaticScope,
|
|
TraceLoggerTextId logId)
|
|
: traceLogger(cx, logId, options),
|
|
keepAtoms(cx->perThreadData),
|
|
cx(cx),
|
|
alloc(alloc),
|
|
options(options),
|
|
sourceBuffer(sourceBuffer),
|
|
enclosingStaticScope(cx, enclosingStaticScope),
|
|
sourceArgumentsNotIncluded(false),
|
|
sourceObject(cx),
|
|
scriptSource(nullptr),
|
|
sourceCompressor(nullptr),
|
|
directives(options.strictOption),
|
|
startPosition(keepAtoms),
|
|
script(cx)
|
|
{
|
|
}
|
|
|
|
void
|
|
BytecodeCompiler::maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor)
|
|
{
|
|
this->sourceCompressor = sourceCompressor;
|
|
}
|
|
|
|
void
|
|
BytecodeCompiler::setSourceArgumentsNotIncluded()
|
|
{
|
|
sourceArgumentsNotIncluded = true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::checkLength()
|
|
{
|
|
// Note this limit is simply so we can store sourceStart and sourceEnd in
|
|
// JSScript as 32-bits. It could be lifted fairly easily, since the compiler
|
|
// is using size_t internally already.
|
|
if (sourceBuffer.length() > UINT32_MAX) {
|
|
if (cx->isJSContext())
|
|
JS_ReportErrorNumber(cx->asJSContext(), GetErrorMessage, nullptr,
|
|
JSMSG_SOURCE_TOO_LONG);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::createScriptSource()
|
|
{
|
|
if (!checkLength())
|
|
return false;
|
|
|
|
sourceObject = CreateScriptSourceObject(cx, options);
|
|
if (!sourceObject)
|
|
return false;
|
|
|
|
scriptSource = sourceObject->source();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::maybeCompressSource()
|
|
{
|
|
if (!sourceCompressor) {
|
|
maybeSourceCompressor.emplace(cx);
|
|
sourceCompressor = maybeSourceCompressor.ptr();
|
|
}
|
|
|
|
if (!cx->compartment()->options().discardSource()) {
|
|
if (options.sourceIsLazy) {
|
|
scriptSource->setSourceRetrievable();
|
|
} else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceArgumentsNotIncluded,
|
|
sourceCompressor))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::canLazilyParse()
|
|
{
|
|
return options.canLazilyParse &&
|
|
!HasNonSyntacticStaticScopeChain(enclosingStaticScope) &&
|
|
!cx->compartment()->options().disableLazyParsing() &&
|
|
!cx->compartment()->options().discardSource() &&
|
|
!options.sourceIsLazy &&
|
|
!cx->lcovEnabled();
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::createParser()
|
|
{
|
|
if (canLazilyParse()) {
|
|
syntaxParser.emplace(cx, alloc, options, sourceBuffer.get(), sourceBuffer.length(),
|
|
/* foldConstants = */ false, (Parser<SyntaxParseHandler>*) nullptr,
|
|
(LazyScript*) nullptr);
|
|
|
|
if (!syntaxParser->checkOptions())
|
|
return false;
|
|
}
|
|
|
|
parser.emplace(cx, alloc, options, sourceBuffer.get(), sourceBuffer.length(),
|
|
/* foldConstants = */ true, syntaxParser.ptrOr(nullptr), nullptr);
|
|
parser->sct = sourceCompressor;
|
|
parser->ss = scriptSource;
|
|
if (!parser->checkOptions())
|
|
return false;
|
|
|
|
parser->tokenStream.tell(&startPosition);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::createSourceAndParser()
|
|
{
|
|
return createScriptSource() &&
|
|
maybeCompressSource() &&
|
|
createParser();
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::createScript(HandleObject staticScope, bool savedCallerFun)
|
|
{
|
|
script = JSScript::Create(cx, staticScope, savedCallerFun, options,
|
|
sourceObject, /* sourceStart = */ 0, sourceBuffer.length());
|
|
|
|
return script != nullptr;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::createEmitter(SharedContext* sharedContext, HandleScript evalCaller,
|
|
bool insideNonGlobalEval)
|
|
{
|
|
BytecodeEmitter::EmitterMode emitterMode =
|
|
options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
|
|
emitter.emplace(/* parent = */ nullptr, parser.ptr(), sharedContext, script,
|
|
/* lazyScript = */ nullptr, options.forEval, evalCaller,
|
|
insideNonGlobalEval, options.lineno, emitterMode);
|
|
return emitter->init();
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::isEvalCompilationUnit()
|
|
{
|
|
return enclosingStaticScope->is<StaticEvalObject>();
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::isNonGlobalEvalCompilationUnit()
|
|
{
|
|
if (!isEvalCompilationUnit())
|
|
return false;
|
|
StaticEvalObject& eval = enclosingStaticScope->as<StaticEvalObject>();
|
|
JSObject* enclosing = eval.enclosingScopeForStaticScopeIter();
|
|
return !IsStaticGlobalLexicalScope(enclosing);
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::isNonSyntacticCompilationUnit()
|
|
{
|
|
return enclosingStaticScope->is<StaticNonSyntacticScopeObjects>();
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::saveCallerFun(HandleScript evalCaller)
|
|
{
|
|
/*
|
|
* An eval script in a caller frame needs to have its enclosing
|
|
* function captured in case it refers to an upvar, and someone
|
|
* wishes to decompile it while it's running.
|
|
*
|
|
* This ends up as script->objects()->vector[0] in the compiled script.
|
|
*/
|
|
RootedFunction fun(cx, evalCaller->functionOrCallerFunction());
|
|
MOZ_ASSERT_IF(fun->strict(), options.strictOption);
|
|
Directives directives(/* strict = */ options.strictOption);
|
|
ObjectBox* funbox = parser->newFunctionBox(/* fn = */ nullptr, fun,
|
|
directives, fun->generatorKind(),
|
|
fun->asyncKind(),
|
|
enclosingStaticScope);
|
|
if (!funbox)
|
|
return false;
|
|
|
|
emitter->objectList.add(funbox);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::handleParseFailure(const Directives& newDirectives)
|
|
{
|
|
if (parser->hadAbortedSyntaxParse()) {
|
|
// Hit some unrecoverable ambiguity during an inner syntax parse.
|
|
// Syntax parsing has now been disabled in the parser, so retry
|
|
// the parse.
|
|
parser->clearAbortedSyntaxParse();
|
|
} else if (parser->tokenStream.hadError() || directives == newDirectives) {
|
|
return false;
|
|
}
|
|
|
|
parser->tokenStream.seek(startPosition);
|
|
|
|
// Assignment must be monotonic to prevent reparsing iloops
|
|
MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
|
|
MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
|
|
directives = newDirectives;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::prepareAndEmitTree(ParseNode** ppn)
|
|
{
|
|
if (!FoldConstants(cx, ppn, parser.ptr()) ||
|
|
!NameFunctions(cx, *ppn) ||
|
|
!emitter->updateLocalsToFrameSlots() ||
|
|
!emitter->emitTree(*ppn))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::maybeSetDisplayURL(TokenStream& tokenStream)
|
|
{
|
|
if (tokenStream.hasDisplayURL()) {
|
|
if (!scriptSource->setDisplayURL(cx, tokenStream.displayURL()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::maybeSetSourceMap(TokenStream& tokenStream)
|
|
{
|
|
if (tokenStream.hasSourceMapURL()) {
|
|
MOZ_ASSERT(!scriptSource->hasSourceMapURL());
|
|
if (!scriptSource->setSourceMapURL(cx, tokenStream.sourceMapURL()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::maybeSetSourceMapFromOptions()
|
|
{
|
|
/*
|
|
* Source map URLs passed as a compile option (usually via a HTTP source map
|
|
* header) override any source map urls passed as comment pragmas.
|
|
*/
|
|
if (options.sourceMapURL()) {
|
|
// Warn about the replacement, but use the new one.
|
|
if (scriptSource->hasSourceMapURL()) {
|
|
if(!parser->report(ParseWarning, false, nullptr, JSMSG_ALREADY_HAS_PRAGMA,
|
|
scriptSource->filename(), "//# sourceMappingURL"))
|
|
return false;
|
|
}
|
|
|
|
if (!scriptSource->setSourceMapURL(cx, options.sourceMapURL()))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::checkArgumentsWithinEval(JSContext* cx, HandleFunction fun)
|
|
{
|
|
RootedScript script(cx, fun->getOrCreateScript(cx));
|
|
if (!script)
|
|
return false;
|
|
|
|
// It's an error to use |arguments| in a legacy generator expression.
|
|
if (script->isGeneratorExp() && script->isLegacyGenerator()) {
|
|
parser->report(ParseError, false, nullptr, JSMSG_BAD_GENEXP_BODY, js_arguments_str);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::maybeCheckEvalFreeVariables(HandleScript evalCaller, HandleObject scopeChain,
|
|
ParseContext<FullParseHandler>& pc)
|
|
{
|
|
if (!evalCaller || !evalCaller->functionOrCallerFunction())
|
|
return true;
|
|
|
|
// Eval scripts are only compiled on the main thread.
|
|
JSContext* cx = this->cx->asJSContext();
|
|
|
|
// Watch for uses of 'arguments' within the evaluated script, both as
|
|
// free variables and as variables redeclared with 'var'.
|
|
RootedFunction fun(cx, evalCaller->functionOrCallerFunction());
|
|
HandlePropertyName arguments = cx->names().arguments;
|
|
for (AtomDefnRange r = pc.lexdeps->all(); !r.empty(); r.popFront()) {
|
|
if (r.front().key() == arguments) {
|
|
if (!checkArgumentsWithinEval(cx, fun))
|
|
return false;
|
|
}
|
|
}
|
|
for (AtomDefnListMap::Range r = pc.decls().all(); !r.empty(); r.popFront()) {
|
|
if (r.front().key() == arguments) {
|
|
if (!checkArgumentsWithinEval(cx, fun))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If the eval'ed script contains any debugger statement, force construction
|
|
// of arguments objects for the caller script and any other scripts it is
|
|
// transitively nested inside. The debugger can access any variable on the
|
|
// scope chain.
|
|
if (pc.sc->hasDebuggerStatement()) {
|
|
RootedObject scope(cx, scopeChain);
|
|
while (scope->is<ScopeObject>() || scope->is<DebugScopeObject>()) {
|
|
if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
|
|
RootedScript script(cx, scope->as<CallObject>().callee().getOrCreateScript(cx));
|
|
if (!script)
|
|
return false;
|
|
if (script->argumentsHasVarBinding()) {
|
|
if (!JSScript::argumentsOptimizationFailed(cx, script))
|
|
return false;
|
|
}
|
|
}
|
|
scope = scope->enclosingScope();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::emitFinalReturn()
|
|
{
|
|
/*
|
|
* Nowadays the threaded interpreter needs a last return instruction, so we
|
|
* do have to emit that here.
|
|
*/
|
|
return emitter->emit1(JSOP_RETRVAL);
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::initGlobalOrEvalBindings(ParseContext<FullParseHandler>& pc)
|
|
{
|
|
Rooted<Bindings> bindings(cx, script->bindings);
|
|
if (!pc.generateBindings(cx, parser->tokenStream, *alloc, &bindings))
|
|
return false;
|
|
script->bindings = bindings;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::maybeCompleteCompressSource()
|
|
{
|
|
return !maybeSourceCompressor || maybeSourceCompressor->complete();
|
|
}
|
|
|
|
JSScript*
|
|
BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller)
|
|
{
|
|
if (!createSourceAndParser())
|
|
return nullptr;
|
|
|
|
RootedFunction savedCallerFun(cx);
|
|
if (evalCaller)
|
|
savedCallerFun = evalCaller->functionOrCallerFunction();
|
|
|
|
if (!createScript(enclosingStaticScope, savedCallerFun))
|
|
return nullptr;
|
|
|
|
GlobalSharedContext globalsc(cx, enclosingStaticScope, directives, options.extraWarningsOption,
|
|
savedCallerFun);
|
|
if (!createEmitter(&globalsc, evalCaller, isNonGlobalEvalCompilationUnit()))
|
|
return nullptr;
|
|
|
|
if (savedCallerFun && !saveCallerFun(evalCaller))
|
|
return nullptr;
|
|
|
|
for (;;) {
|
|
ParseContext<FullParseHandler> pc(parser.ptr(),
|
|
/* parent = */ nullptr,
|
|
/* maybeFunction = */ nullptr,
|
|
&globalsc,
|
|
/* newDirectives = */ nullptr);
|
|
if (!pc.init(*parser))
|
|
return nullptr;
|
|
|
|
ParseNode* pn;
|
|
if (isEvalCompilationUnit())
|
|
pn = parser->evalBody();
|
|
else
|
|
pn = parser->globalBody();
|
|
|
|
// Successfully parsed. Emit the script.
|
|
if (pn) {
|
|
if (!initGlobalOrEvalBindings(pc))
|
|
return nullptr;
|
|
if (!maybeCheckEvalFreeVariables(evalCaller, scopeChain, pc))
|
|
return nullptr;
|
|
if (!prepareAndEmitTree(&pn))
|
|
return nullptr;
|
|
parser->handler.freeTree(pn);
|
|
|
|
break;
|
|
}
|
|
|
|
// Maybe we aborted a syntax parse. See if we can try again.
|
|
if (!handleParseFailure(directives))
|
|
return nullptr;
|
|
}
|
|
|
|
if (!maybeSetDisplayURL(parser->tokenStream) ||
|
|
!maybeSetSourceMap(parser->tokenStream) ||
|
|
!maybeSetSourceMapFromOptions() ||
|
|
!emitFinalReturn() ||
|
|
!JSScript::fullyInitFromEmitter(cx, script, emitter.ptr()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
emitter->tellDebuggerAboutCompiledScript(cx);
|
|
|
|
if (!maybeCompleteCompressSource())
|
|
return nullptr;
|
|
|
|
MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
|
|
return script;
|
|
}
|
|
|
|
ModuleObject* BytecodeCompiler::compileModule()
|
|
{
|
|
if (!createSourceAndParser())
|
|
return nullptr;
|
|
|
|
Rooted<ModuleObject*> module(cx, ModuleObject::create(cx, enclosingStaticScope));
|
|
if (!module)
|
|
return nullptr;
|
|
|
|
if (!createScript(module))
|
|
return nullptr;
|
|
|
|
module->init(script);
|
|
|
|
ParseNode* pn = parser->standaloneModule(module);
|
|
if (!pn)
|
|
return nullptr;
|
|
|
|
if (!NameFunctions(cx, pn) ||
|
|
!maybeSetDisplayURL(parser->tokenStream) ||
|
|
!maybeSetSourceMap(parser->tokenStream))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
script->bindings = pn->pn_modulebox->bindings;
|
|
|
|
RootedModuleEnvironmentObject dynamicScope(cx, ModuleEnvironmentObject::create(cx, module));
|
|
if (!dynamicScope)
|
|
return nullptr;
|
|
|
|
module->setInitialEnvironment(dynamicScope);
|
|
|
|
if (!createEmitter(pn->pn_modulebox) ||
|
|
!emitter->emitModuleScript(pn->pn_body))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
ModuleBuilder builder(cx->asJSContext(), module);
|
|
if (!builder.buildAndInit(pn))
|
|
return nullptr;
|
|
|
|
parser->handler.freeTree(pn);
|
|
|
|
if (!maybeCompleteCompressSource())
|
|
return nullptr;
|
|
|
|
MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
|
|
return module;
|
|
}
|
|
|
|
bool
|
|
BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun,
|
|
Handle<PropertyNameVector> formals,
|
|
GeneratorKind generatorKind)
|
|
{
|
|
MOZ_ASSERT(fun);
|
|
MOZ_ASSERT(fun->isTenured());
|
|
|
|
fun->setArgCount(formals.length());
|
|
|
|
if (!createSourceAndParser())
|
|
return false;
|
|
|
|
// 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.
|
|
|
|
ParseNode* fn;
|
|
do {
|
|
Directives newDirectives = directives;
|
|
fn = parser->standaloneFunctionBody(fun, formals, generatorKind, SyncFunction, directives,
|
|
&newDirectives, enclosingStaticScope);
|
|
if (!fn && !handleParseFailure(newDirectives))
|
|
return false;
|
|
} while (!fn);
|
|
|
|
if (!NameFunctions(cx, fn) ||
|
|
!maybeSetDisplayURL(parser->tokenStream) ||
|
|
!maybeSetSourceMap(parser->tokenStream))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (fn->pn_funbox->function()->isInterpreted()) {
|
|
MOZ_ASSERT(fun == fn->pn_funbox->function());
|
|
|
|
if (!createScript(enclosingStaticScope))
|
|
return false;
|
|
|
|
script->bindings = fn->pn_funbox->bindings;
|
|
|
|
if (!createEmitter(fn->pn_funbox) ||
|
|
!emitter->emitFunctionScript(fn->pn_body))
|
|
{
|
|
return false;
|
|
}
|
|
} else {
|
|
fun.set(fn->pn_funbox->function());
|
|
MOZ_ASSERT(IsAsmJSModuleNative(fun->native()));
|
|
}
|
|
|
|
if (!maybeCompleteCompressSource())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
ScriptSourceObject*
|
|
BytecodeCompiler::sourceObjectPtr() const
|
|
{
|
|
return sourceObject.get();
|
|
}
|
|
|
|
ScriptSourceObject*
|
|
frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options)
|
|
{
|
|
ScriptSource* ss = cx->new_<ScriptSource>();
|
|
if (!ss)
|
|
return nullptr;
|
|
ScriptSourceHolder ssHolder(ss);
|
|
|
|
if (!ss->initFromOptions(cx, options))
|
|
return nullptr;
|
|
|
|
RootedScriptSource sso(cx, ScriptSourceObject::create(cx, ss));
|
|
if (!sso)
|
|
return nullptr;
|
|
|
|
// Off-thread compilations do all their GC heap allocation, including the
|
|
// SSO, in a temporary compartment. Hence, for the SSO to refer to the
|
|
// gc-heap-allocated values in |options|, it would need cross-compartment
|
|
// wrappers from the temporary compartment to the real compartment --- which
|
|
// would then be inappropriate once we merged the temporary and real
|
|
// compartments.
|
|
//
|
|
// Instead, we put off populating those SSO slots in off-thread compilations
|
|
// until after we've merged compartments.
|
|
if (cx->isJSContext()) {
|
|
if (!ScriptSourceObject::initFromOptions(cx->asJSContext(), sso, options))
|
|
return nullptr;
|
|
}
|
|
|
|
return sso;
|
|
}
|
|
|
|
JSScript*
|
|
frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject scopeChain,
|
|
Handle<ScopeObject*> enclosingStaticScope,
|
|
HandleScript evalCaller,
|
|
const ReadOnlyCompileOptions& options,
|
|
SourceBufferHolder& srcBuf,
|
|
JSString* source_ /* = nullptr */,
|
|
SourceCompressionTask* extraSct /* = nullptr */,
|
|
ScriptSourceObject** sourceObjectOut /* = nullptr */)
|
|
{
|
|
MOZ_ASSERT(srcBuf.get());
|
|
|
|
/*
|
|
* The scripted callerFrame can only be given for compile-and-go scripts
|
|
* and non-zero static level requires callerFrame.
|
|
*/
|
|
MOZ_ASSERT_IF(evalCaller, options.isRunOnce);
|
|
MOZ_ASSERT_IF(evalCaller, options.forEval);
|
|
MOZ_ASSERT_IF(evalCaller && evalCaller->strict(), options.strictOption);
|
|
|
|
MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
|
|
|
|
BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingStaticScope,
|
|
TraceLogger_ParserCompileScript);
|
|
compiler.maybeSetSourceCompressor(extraSct);
|
|
JSScript* script = compiler.compileScript(scopeChain, evalCaller);
|
|
|
|
// frontend::CompileScript independently returns the
|
|
// ScriptSourceObject (SSO) for the compile. This is used by
|
|
// off-main-thread script compilation (OMT-SC).
|
|
//
|
|
// OMT-SC cannot initialize the SSO when it is first constructed
|
|
// because the SSO is allocated initially in a separate compartment.
|
|
//
|
|
// After OMT-SC, the separate compartment is merged with the main
|
|
// compartment, at which point the JSScripts created become observable
|
|
// by the debugger via memory-space scanning.
|
|
//
|
|
// Whatever happens to the top-level script compilation (even if it
|
|
// fails and returns null), we must finish initializing the SSO. This
|
|
// is because there may be valid inner scripts observable by the debugger
|
|
// which reference the partially-initialized SSO.
|
|
if (sourceObjectOut)
|
|
*sourceObjectOut = compiler.sourceObjectPtr();
|
|
|
|
return script;
|
|
}
|
|
|
|
ModuleObject*
|
|
frontend::CompileModule(JSContext* cx, HandleObject obj,
|
|
const ReadOnlyCompileOptions& optionsInput,
|
|
SourceBufferHolder& srcBuf)
|
|
{
|
|
MOZ_ASSERT(srcBuf.get());
|
|
|
|
CompileOptions options(cx, optionsInput);
|
|
options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
|
|
options.setIsRunOnce(true);
|
|
|
|
Rooted<ScopeObject*> staticScope(cx, &cx->global()->lexicalScope().staticBlock());
|
|
BytecodeCompiler compiler(cx, &cx->tempLifoAlloc(), options, srcBuf, staticScope,
|
|
TraceLogger_ParserCompileModule);
|
|
return compiler.compileModule();
|
|
}
|
|
|
|
bool
|
|
frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length)
|
|
{
|
|
MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
|
|
|
|
CompileOptions options(cx, lazy->version());
|
|
options.setMutedErrors(lazy->mutedErrors())
|
|
.setFileAndLine(lazy->filename(), lazy->lineno())
|
|
.setColumn(lazy->column())
|
|
.setNoScriptRval(false)
|
|
.setSelfHostingMode(false);
|
|
|
|
AutoCompilationTraceLogger traceLogger(cx, TraceLogger_ParserCompileLazy, options);
|
|
|
|
Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars, length,
|
|
/* foldConstants = */ true, nullptr, lazy);
|
|
if (!parser.checkOptions())
|
|
return false;
|
|
|
|
Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying());
|
|
MOZ_ASSERT(!lazy->isLegacyGenerator());
|
|
ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->strict(), lazy->generatorKind(), lazy->asyncKind());
|
|
if (!pn)
|
|
return false;
|
|
|
|
if (!NameFunctions(cx, pn))
|
|
return false;
|
|
|
|
RootedObject enclosingScope(cx, lazy->enclosingScope());
|
|
RootedScriptSource sourceObject(cx, lazy->sourceObject());
|
|
MOZ_ASSERT(sourceObject);
|
|
|
|
Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
|
|
sourceObject, lazy->begin(), lazy->end()));
|
|
if (!script)
|
|
return false;
|
|
|
|
script->bindings = pn->pn_funbox->bindings;
|
|
|
|
if (lazy->isLikelyConstructorWrapper())
|
|
script->setLikelyConstructorWrapper();
|
|
if (lazy->hasBeenCloned())
|
|
script->setHasBeenCloned();
|
|
|
|
/*
|
|
* We just pass false for insideNonGlobalEval and insideEval, because we
|
|
* don't actually know whether we are or not. The only consumer of those
|
|
* booleans is TryConvertFreeName, and it has special machinery to avoid
|
|
* doing bad things when a lazy function is inside eval.
|
|
*/
|
|
MOZ_ASSERT(!options.forEval);
|
|
BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
|
|
/* insideEval = */ false, /* evalCaller = */ nullptr,
|
|
/* insideNonGlobalEval = */ false, options.lineno,
|
|
BytecodeEmitter::LazyFunction);
|
|
if (!bce.init())
|
|
return false;
|
|
|
|
return bce.emitFunctionScript(pn->pn_body);
|
|
}
|
|
|
|
// Compile a JS function body, which might appear as the value of an event
|
|
// handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
|
|
static bool
|
|
CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options,
|
|
Handle<PropertyNameVector> formals, SourceBufferHolder& srcBuf,
|
|
Handle<ScopeObject*> enclosingStaticScope, GeneratorKind generatorKind)
|
|
{
|
|
MOZ_ASSERT(!options.isRunOnce);
|
|
|
|
// FIXME: make Function pass in two strings and parse them as arguments and
|
|
// ProgramElements respectively.
|
|
|
|
BytecodeCompiler compiler(cx, &cx->tempLifoAlloc(), options, srcBuf, enclosingStaticScope,
|
|
TraceLogger_ParserCompileFunction);
|
|
compiler.setSourceArgumentsNotIncluded();
|
|
return compiler.compileFunctionBody(fun, formals, generatorKind);
|
|
}
|
|
|
|
bool
|
|
frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
|
|
const ReadOnlyCompileOptions& options,
|
|
Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf,
|
|
Handle<ScopeObject*> enclosingStaticScope)
|
|
{
|
|
return CompileFunctionBody(cx, fun, options, formals, srcBuf,
|
|
enclosingStaticScope, NotGenerator);
|
|
}
|
|
|
|
bool
|
|
frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
|
|
const ReadOnlyCompileOptions& options,
|
|
Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf)
|
|
{
|
|
Rooted<ScopeObject*> staticLexical(cx, &cx->global()->lexicalScope().staticBlock());
|
|
return CompileFunctionBody(cx, fun, options, formals, srcBuf, staticLexical, NotGenerator);
|
|
}
|
|
|
|
|
|
bool
|
|
frontend::CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun,
|
|
const ReadOnlyCompileOptions& options,
|
|
Handle<PropertyNameVector> formals,
|
|
JS::SourceBufferHolder& srcBuf)
|
|
{
|
|
Rooted<ScopeObject*> staticLexical(cx, &cx->global()->lexicalScope().staticBlock());
|
|
return CompileFunctionBody(cx, fun, options, formals, srcBuf, staticLexical, StarGenerator);
|
|
}
|