Files
tubestation/js/src/vm/CompilationAndEvaluation.cpp
Cosmin Sabou d06859d1a4 Backed out 4 changesets (bug 1831845) for causing spidermonkey bustages. CLOSED TREE
Backed out changeset a9765ccd45b1 (bug 1831845)
Backed out changeset 0d23be10f378 (bug 1831845)
Backed out changeset 5c31df65197e (bug 1831845)
Backed out changeset 11e79bc5149a (bug 1831845)
2023-06-06 07:54:03 +03:00

614 lines
20 KiB
C++

/* -*- 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/. */
/* Same-thread compilation and evaluation APIs. */
#include "js/CompilationAndEvaluation.h"
#include "mozilla/Maybe.h" // mozilla::None, mozilla::Some
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include <utility> // std::move
#include "jsapi.h" // JS_WrapValue
#include "jstypes.h" // JS_PUBLIC_API
#include "debugger/DebugAPI.h"
#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScript
#include "frontend/BytecodeCompiler.h" // frontend::IsIdentifier
#include "frontend/CompilationStencil.h" // for frontened::{CompilationStencil, BorrowingCompilationStencil, CompilationGCOutput}
#include "frontend/FrontendContext.h" // js::AutoReportFrontendContext
#include "frontend/Parser.h" // frontend::Parser, frontend::ParseGoal
#include "js/CharacterEncoding.h" // JS::UTF8Chars, JS::UTF8CharsToNewTwoByteCharsZ
#include "js/experimental/JSStencil.h" // JS::Stencil
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/RootingAPI.h" // JS::Rooted
#include "js/SourceText.h" // JS::SourceText
#include "js/TypeDecls.h" // JS::HandleObject, JS::MutableHandleScript
#include "js/Utility.h" // js::MallocArena, JS::UniqueTwoByteChars
#include "js/Value.h" // JS::Value
#include "util/CompleteFile.h" // js::FileContents, js::ReadCompleteFile
#include "util/StringBuffer.h" // js::StringBuffer
#include "vm/EnvironmentObject.h" // js::CreateNonSyntacticEnvironmentChain
#include "vm/ErrorReporting.h" // js::ErrorMetadata, js::ReportCompileErrorLatin1
#include "vm/Interpreter.h" // js::Execute
#include "vm/JSContext.h" // JSContext
#include "vm/JSContext-inl.h" // JSContext::check
using mozilla::Utf8Unit;
using JS::CompileOptions;
using JS::HandleObject;
using JS::ReadOnlyCompileOptions;
using JS::SourceOwnership;
using JS::SourceText;
using JS::UniqueTwoByteChars;
using JS::UTF8Chars;
using JS::UTF8CharsToNewTwoByteCharsZ;
using namespace js;
JS_PUBLIC_API void JS::detail::ReportSourceTooLong(JSContext* cx) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SOURCE_TOO_LONG);
}
static void ReportSourceTooLongImpl(JS::FrontendContext* fc, ...) {
va_list args;
va_start(args, fc);
js::ErrorMetadata metadata;
metadata.filename = "<unknown>";
metadata.lineNumber = 0;
metadata.columnNumber = 0;
metadata.lineLength = 0;
metadata.tokenOffset = 0;
metadata.isMuted = false;
js::ReportCompileErrorLatin1(fc, std::move(metadata), nullptr,
JSMSG_SOURCE_TOO_LONG, &args);
va_end(args);
}
JS_PUBLIC_API void JS::detail::ReportSourceTooLong(JS::FrontendContext* fc) {
ReportSourceTooLongImpl(fc);
}
template <typename Unit>
static JSScript* CompileSourceBuffer(JSContext* cx,
const ReadOnlyCompileOptions& options,
SourceText<Unit>& srcBuf) {
ScopeKind scopeKind =
options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
JS::Rooted<JSScript*> script(cx);
{
AutoReportFrontendContext fc(cx);
script = frontend::CompileGlobalScript(cx, &fc, options, srcBuf, scopeKind);
}
return script;
}
JSScript* JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
SourceText<char16_t>& srcBuf) {
return CompileSourceBuffer(cx, options, srcBuf);
}
JSScript* JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
SourceText<Utf8Unit>& srcBuf) {
return CompileSourceBuffer(cx, options, srcBuf);
}
JS_PUBLIC_API bool JS::StartIncrementalEncoding(JSContext* cx,
RefPtr<JS::Stencil>&& stencil) {
MOZ_ASSERT(cx);
MOZ_ASSERT(!stencil->hasMultipleReference());
auto* source = stencil->source.get();
UniquePtr<frontend::ExtensibleCompilationStencil> initial;
if (stencil->hasOwnedBorrow()) {
initial.reset(stencil->takeOwnedBorrow());
stencil = nullptr;
} else {
initial = cx->make_unique<frontend::ExtensibleCompilationStencil>(
stencil->source);
if (!initial) {
return false;
}
AutoReportFrontendContext fc(cx);
if (!initial->steal(&fc, std::move(stencil))) {
return false;
}
}
return source->startIncrementalEncoding(cx, std::move(initial));
}
JSScript* JS::CompileUtf8File(JSContext* cx,
const ReadOnlyCompileOptions& options,
FILE* file) {
FileContents buffer(cx);
if (!ReadCompleteFile(cx, file, buffer)) {
return nullptr;
}
SourceText<Utf8Unit> srcBuf;
if (!srcBuf.init(cx, reinterpret_cast<const char*>(buffer.begin()),
buffer.length(), SourceOwnership::Borrowed)) {
return nullptr;
}
return CompileSourceBuffer(cx, options, srcBuf);
}
JSScript* JS::CompileUtf8Path(JSContext* cx,
const ReadOnlyCompileOptions& optionsArg,
const char* filename) {
AutoFile file;
if (!file.open(cx, filename)) {
return nullptr;
}
CompileOptions options(cx, optionsArg);
options.setFileAndLine(filename, 1);
return CompileUtf8File(cx, options, file.fp());
}
JS_PUBLIC_API bool JS_Utf8BufferIsCompilableUnit(JSContext* cx,
HandleObject obj,
const char* utf8,
size_t length) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(obj);
cx->clearPendingException();
JS::UniqueTwoByteChars chars{
UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(utf8, length), &length,
js::MallocArena)
.get()};
if (!chars) {
return true;
}
// Return true on any out-of-memory error or non-EOF-related syntax error, so
// our caller doesn't try to collect more buffered source.
bool result = true;
using frontend::FullParseHandler;
using frontend::ParseGoal;
using frontend::Parser;
AutoReportFrontendContext fc(cx,
AutoReportFrontendContext::Warning::Suppress);
CompileOptions options(cx);
Rooted<frontend::CompilationInput> input(cx,
frontend::CompilationInput(options));
if (!input.get().initForGlobal(&fc)) {
return false;
}
LifoAllocScope allocScope(&cx->tempLifoAlloc());
js::frontend::NoScopeBindingCache scopeCache;
frontend::CompilationState compilationState(&fc, allocScope, input.get());
if (!compilationState.init(&fc, &scopeCache)) {
return false;
}
Parser<FullParseHandler, char16_t> parser(&fc, options, chars.get(), length,
/* foldConstants = */ true,
compilationState,
/* syntaxParser = */ nullptr);
if (!parser.checkOptions() || !parser.parse()) {
// We ran into an error. If it was because we ran out of source, we
// return false so our caller knows to try to collect more buffered
// source.
if (parser.isUnexpectedEOF()) {
result = false;
}
cx->clearPendingException();
}
return result;
}
class FunctionCompiler {
private:
JSContext* const cx_;
Rooted<JSAtom*> nameAtom_;
StringBuffer funStr_;
uint32_t parameterListEnd_ = 0;
bool nameIsIdentifier_ = true;
public:
explicit FunctionCompiler(JSContext* cx, FrontendContext* fc)
: cx_(cx), nameAtom_(cx), funStr_(fc) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
MOZ_ASSERT(!cx->zone()->isAtomsZone());
}
[[nodiscard]] bool init(const char* name, unsigned nargs,
const char* const* argnames) {
if (!funStr_.ensureTwoByteChars()) {
return false;
}
if (!funStr_.append("function ")) {
return false;
}
if (name) {
size_t nameLen = strlen(name);
nameAtom_ = Atomize(cx_, name, nameLen);
if (!nameAtom_) {
return false;
}
// If the name is an identifier, we can just add it to source text.
// Otherwise we'll have to set it manually later.
nameIsIdentifier_ = js::frontend::IsIdentifier(
reinterpret_cast<const Latin1Char*>(name), nameLen);
if (nameIsIdentifier_) {
if (!funStr_.append(nameAtom_)) {
return false;
}
}
}
if (!funStr_.append("(")) {
return false;
}
for (unsigned i = 0; i < nargs; i++) {
if (i != 0) {
if (!funStr_.append(", ")) {
return false;
}
}
if (!funStr_.append(argnames[i], strlen(argnames[i]))) {
return false;
}
}
// Remember the position of ")".
parameterListEnd_ = funStr_.length();
static_assert(FunctionConstructorMedialSigils[0] == ')');
return funStr_.append(FunctionConstructorMedialSigils.data(),
FunctionConstructorMedialSigils.length());
}
template <typename Unit>
[[nodiscard]] inline bool addFunctionBody(const SourceText<Unit>& srcBuf) {
return funStr_.append(srcBuf.get(), srcBuf.length());
}
JSFunction* finish(HandleObjectVector envChain,
const ReadOnlyCompileOptions& optionsArg) {
using js::frontend::FunctionSyntaxKind;
if (!funStr_.append(FunctionConstructorFinalBrace.data(),
FunctionConstructorFinalBrace.length())) {
return nullptr;
}
size_t newLen = funStr_.length();
UniqueTwoByteChars stolen(funStr_.stealChars());
if (!stolen) {
return nullptr;
}
SourceText<char16_t> newSrcBuf;
if (!newSrcBuf.init(cx_, std::move(stolen), newLen)) {
return nullptr;
}
RootedObject enclosingEnv(cx_);
ScopeKind kind;
if (envChain.empty()) {
// A compiled function has a burned-in environment chain, so if no exotic
// environment was requested, we can use the global lexical environment
// directly and not need to worry about any potential non-syntactic scope.
enclosingEnv.set(&cx_->global()->lexicalEnvironment());
kind = ScopeKind::Global;
} else {
if (!CreateNonSyntacticEnvironmentChain(cx_, envChain, &enclosingEnv)) {
return nullptr;
}
kind = ScopeKind::NonSyntactic;
}
cx_->check(enclosingEnv);
// Make sure the static scope chain matches up when we have a
// non-syntactic scope.
MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(enclosingEnv),
kind == ScopeKind::NonSyntactic);
CompileOptions options(cx_, optionsArg);
options.setNonSyntacticScope(kind == ScopeKind::NonSyntactic);
FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement;
RootedFunction fun(cx_);
if (kind == ScopeKind::NonSyntactic) {
Rooted<Scope*> enclosingScope(
cx_, GlobalScope::createEmpty(cx_, ScopeKind::NonSyntactic));
if (!enclosingScope) {
return nullptr;
}
fun = js::frontend::CompileStandaloneFunctionInNonSyntacticScope(
cx_, options, newSrcBuf, mozilla::Some(parameterListEnd_), syntaxKind,
enclosingScope);
} else {
fun = js::frontend::CompileStandaloneFunction(
cx_, options, newSrcBuf, mozilla::Some(parameterListEnd_),
syntaxKind);
}
if (!fun) {
return nullptr;
}
// When the function name isn't a valid identifier, the generated function
// source in srcBuf won't include the name, so name the function manually.
if (!nameIsIdentifier_) {
fun->setAtom(nameAtom_);
}
if (fun->isInterpreted()) {
fun->initEnvironment(enclosingEnv);
}
return fun;
}
};
JS_PUBLIC_API JSFunction* JS::CompileFunction(
JSContext* cx, HandleObjectVector envChain,
const ReadOnlyCompileOptions& options, const char* name, unsigned nargs,
const char* const* argnames, SourceText<char16_t>& srcBuf) {
ManualReportFrontendContext fc(cx);
FunctionCompiler compiler(cx, &fc);
if (!compiler.init(name, nargs, argnames) ||
!compiler.addFunctionBody(srcBuf)) {
fc.failure();
return nullptr;
}
fc.ok();
return compiler.finish(envChain, options);
}
JS_PUBLIC_API JSFunction* JS::CompileFunction(
JSContext* cx, HandleObjectVector envChain,
const ReadOnlyCompileOptions& options, const char* name, unsigned nargs,
const char* const* argnames, SourceText<Utf8Unit>& srcBuf) {
ManualReportFrontendContext fc(cx);
FunctionCompiler compiler(cx, &fc);
if (!compiler.init(name, nargs, argnames) ||
!compiler.addFunctionBody(srcBuf)) {
fc.failure();
return nullptr;
}
fc.ok();
return compiler.finish(envChain, options);
}
JS_PUBLIC_API JSFunction* JS::CompileFunctionUtf8(
JSContext* cx, HandleObjectVector envChain,
const ReadOnlyCompileOptions& options, const char* name, unsigned nargs,
const char* const* argnames, const char* bytes, size_t length) {
SourceText<Utf8Unit> srcBuf;
if (!srcBuf.init(cx, bytes, length, SourceOwnership::Borrowed)) {
return nullptr;
}
return CompileFunction(cx, envChain, options, name, nargs, argnames, srcBuf);
}
JS_PUBLIC_API void JS::ExposeScriptToDebugger(JSContext* cx,
HandleScript script) {
MOZ_ASSERT(cx);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
DebugAPI::onNewScript(cx, script);
}
JS_PUBLIC_API bool JS::UpdateDebugMetadata(
JSContext* cx, Handle<JSScript*> script, const InstantiateOptions& options,
HandleValue privateValue, HandleString elementAttributeName,
HandleScript introScript, HandleScript scriptOrModule) {
Rooted<ScriptSourceObject*> sso(cx, script->sourceObject());
if (!ScriptSourceObject::initElementProperties(cx, sso,
elementAttributeName)) {
return false;
}
// There is no equivalent of cross-compartment wrappers for scripts. If the
// introduction script and ScriptSourceObject are in different compartments,
// we would be creating a cross-compartment script reference, which is
// forbidden. We can still store a CCW to the script source object though.
RootedValue introductionScript(cx);
if (introScript) {
if (introScript->compartment() == cx->compartment()) {
introductionScript.setPrivateGCThing(introScript);
}
}
sso->setIntroductionScript(introductionScript);
RootedValue privateValueStore(cx, UndefinedValue());
if (privateValue.isUndefined()) {
// Set the private value to that of the script or module that this source is
// part of, if any.
if (scriptOrModule) {
privateValueStore = scriptOrModule->sourceObject()->getPrivate();
}
} else {
privateValueStore = privateValue;
}
if (!privateValueStore.isUndefined()) {
if (!JS_WrapValue(cx, &privateValueStore)) {
return false;
}
}
sso->setPrivate(cx->runtime(), privateValueStore);
if (!options.hideScriptFromDebugger) {
JS::ExposeScriptToDebugger(cx, script);
}
return true;
}
MOZ_NEVER_INLINE static bool ExecuteScript(JSContext* cx, HandleObject envChain,
HandleScript script,
MutableHandleValue rval) {
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(envChain, script);
if (!IsGlobalLexicalEnvironment(envChain)) {
MOZ_RELEASE_ASSERT(script->hasNonSyntacticScope());
}
return Execute(cx, script, envChain, rval);
}
static bool ExecuteScript(JSContext* cx, HandleObjectVector envChain,
HandleScript script, MutableHandleValue rval) {
RootedObject env(cx);
if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env)) {
return false;
}
return ExecuteScript(cx, env, script, rval);
}
MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx,
HandleScript scriptArg,
MutableHandleValue rval) {
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
return ExecuteScript(cx, globalLexical, scriptArg, rval);
}
MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx,
HandleScript scriptArg) {
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
RootedValue rval(cx);
return ExecuteScript(cx, globalLexical, scriptArg, &rval);
}
MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(
JSContext* cx, HandleObjectVector envChain, HandleScript scriptArg,
MutableHandleValue rval) {
return ExecuteScript(cx, envChain, scriptArg, rval);
}
MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(
JSContext* cx, HandleObjectVector envChain, HandleScript scriptArg) {
RootedValue rval(cx);
return ExecuteScript(cx, envChain, scriptArg, &rval);
}
template <typename Unit>
static bool EvaluateSourceBuffer(JSContext* cx, ScopeKind scopeKind,
Handle<JSObject*> env,
const ReadOnlyCompileOptions& optionsArg,
SourceText<Unit>& srcBuf,
MutableHandle<Value> rval) {
CompileOptions options(cx, optionsArg);
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(env);
MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(env),
scopeKind == ScopeKind::NonSyntactic);
options.setNonSyntacticScope(scopeKind == ScopeKind::NonSyntactic);
options.setIsRunOnce(true);
AutoReportFrontendContext fc(cx);
RootedScript script(
cx, frontend::CompileGlobalScript(cx, &fc, options, srcBuf, scopeKind));
if (!script) {
return false;
}
return Execute(cx, script, env, rval);
}
JS_PUBLIC_API bool JS::Evaluate(JSContext* cx,
const ReadOnlyCompileOptions& options,
SourceText<Utf8Unit>& srcBuf,
MutableHandle<Value> rval) {
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
return EvaluateSourceBuffer(cx, ScopeKind::Global, globalLexical, options,
srcBuf, rval);
}
JS_PUBLIC_API bool JS::Evaluate(JSContext* cx,
const ReadOnlyCompileOptions& optionsArg,
SourceText<char16_t>& srcBuf,
MutableHandleValue rval) {
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
return EvaluateSourceBuffer(cx, ScopeKind::Global, globalLexical, optionsArg,
srcBuf, rval);
}
JS_PUBLIC_API bool JS::Evaluate(JSContext* cx, HandleObjectVector envChain,
const ReadOnlyCompileOptions& options,
SourceText<char16_t>& srcBuf,
MutableHandleValue rval) {
RootedObject env(cx);
if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env)) {
return false;
}
return EvaluateSourceBuffer(cx, ScopeKind::NonSyntactic, env, options, srcBuf,
rval);
}
JS_PUBLIC_API bool JS::EvaluateUtf8Path(
JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
const char* filename, MutableHandleValue rval) {
FileContents buffer(cx);
{
AutoFile file;
if (!file.open(cx, filename) || !file.readAll(cx, buffer)) {
return false;
}
}
CompileOptions options(cx, optionsArg);
options.setFileAndLine(filename, 1);
auto contents = reinterpret_cast<const char*>(buffer.begin());
size_t length = buffer.length();
JS::SourceText<Utf8Unit> srcBuf;
if (!srcBuf.init(cx, contents, length, JS::SourceOwnership::Borrowed)) {
return false;
}
return Evaluate(cx, options, srcBuf, rval);
}