summaryrefslogtreecommitdiffstats
path: root/js/src/vm/CompilationAndEvaluation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/CompilationAndEvaluation.cpp')
-rw-r--r--js/src/vm/CompilationAndEvaluation.cpp594
1 files changed, 594 insertions, 0 deletions
diff --git a/js/src/vm/CompilationAndEvaluation.cpp b/js/src/vm/CompilationAndEvaluation.cpp
new file mode 100644
index 0000000000..cef18a075f
--- /dev/null
+++ b/js/src/vm/CompilationAndEvaluation.cpp
@@ -0,0 +1,594 @@
+/* -*- 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/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);
+}
+
+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,
+ cx->stackLimitForCurrentPrincipal(),
+ 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(cx, &fc)) {
+ return false;
+ }
+
+ LifoAllocScope allocScope(&cx->tempLifoAlloc());
+ js::frontend::NoScopeBindingCache scopeCache;
+ frontend::CompilationState compilationState(cx, &fc, allocScope, input.get());
+ if (!compilationState.init(cx, &fc, &scopeCache)) {
+ return false;
+ }
+
+ Parser<FullParseHandler, char16_t> parser(
+ cx, &fc, cx->stackLimitForCurrentPrincipal(), 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, cx->stackLimitForCurrentPrincipal(),
+ 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);
+}