diff options
Diffstat (limited to 'js/src/vm/CompilationAndEvaluation.cpp')
-rw-r--r-- | js/src/vm/CompilationAndEvaluation.cpp | 594 |
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); +} |