diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/vm/CompilationAndEvaluation.cpp | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/js/src/vm/CompilationAndEvaluation.cpp b/js/src/vm/CompilationAndEvaluation.cpp new file mode 100644 index 0000000000..0490014a26 --- /dev/null +++ b/js/src/vm/CompilationAndEvaluation.cpp @@ -0,0 +1,581 @@ +/* -*- 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/TextUtils.h" // mozilla::IsAscii +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include <utility> // std::move + +#include "jstypes.h" // JS_PUBLIC_API + +#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScript +#include "frontend/CompilationInfo.h" // for frontened::CompilationStencil, frontened::CompilationGCOutput +#include "frontend/FullParseHandler.h" // frontend::FullParseHandler +#include "frontend/ParseContext.h" // frontend::UsedNameTracker +#include "frontend/Parser.h" // frontend::Parser, frontend::ParseGoal +#include "js/CharacterEncoding.h" // JS::UTF8Chars, JS::UTF8CharsToNewTwoByteCharsZ +#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/FunctionFlags.h" // js::FunctionFlags +#include "vm/Interpreter.h" // js::Execute +#include "vm/JSContext.h" // JSContext + +#include "debugger/DebugAPI-inl.h" // js::DebugAPI +#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); + + return frontend::CompileGlobalScript(cx, options, srcBuf, scopeKind); +} + +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); +} + +template <typename Unit> +static JSScript* CompileSourceBufferAndStartIncrementalEncoding( + 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); + + Rooted<frontend::CompilationStencil> stencil( + cx, frontend::CompilationStencil(cx, options)); + if (!stencil.get().input.initForGlobal(cx)) { + return nullptr; + } + if (!frontend::CompileGlobalScriptToStencil(cx, stencil.get(), srcBuf, + scopeKind)) { + return nullptr; + } + + Rooted<frontend::CompilationGCOutput> gcOutput(cx); + if (!frontend::InstantiateStencils(cx, stencil.get(), gcOutput.get())) { + return nullptr; + } + + RootedScript script(cx, gcOutput.get().script); + if (!script) { + return nullptr; + } + + if (options.useStencilXDR) { + UniquePtr<XDRIncrementalEncoderBase> xdrEncoder; + + if (!stencil.get().input.source()->xdrEncodeInitialStencil( + cx, stencil.get(), xdrEncoder)) { + return nullptr; + } + + script->scriptSource()->setIncrementalEncoder(xdrEncoder.release()); + } else { + if (!script->scriptSource()->xdrEncodeTopLevel(cx, script)) { + return nullptr; + } + } + + return script; +} + +JSScript* JS::CompileAndStartIncrementalEncoding( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf) { + return CompileSourceBufferAndStartIncrementalEncoding(cx, options, srcBuf); +} + +JSScript* JS::CompileAndStartIncrementalEncoding( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<Utf8Unit>& srcBuf) { + return CompileSourceBufferAndStartIncrementalEncoding(cx, options, srcBuf); +} + +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; + + CompileOptions options(cx); + Rooted<frontend::CompilationStencil> stencil( + cx, frontend::CompilationStencil(cx, options)); + if (!stencil.get().input.initForGlobal(cx)) { + return false; + } + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + frontend::CompilationState compilationState(cx, allocScope, options, + stencil.get()); + + JS::AutoSuppressWarningReporter suppressWarnings(cx); + Parser<FullParseHandler, char16_t> parser(cx, options, chars.get(), length, + /* foldConstants = */ true, + stencil.get(), compilationState, + nullptr, 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_; + RootedAtom nameAtom_; + StringBuffer funStr_; + + uint32_t parameterListEnd_ = 0; + bool nameIsIdentifier_ = true; + + public: + explicit FunctionCompiler(JSContext* cx) + : cx_(cx), nameAtom_(cx), funStr_(cx) { + AssertHeapIsIdle(); + CHECK_THREAD(cx); + MOZ_ASSERT(!cx->zone()->isAtomsZone()); + } + + MOZ_MUST_USE 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(); + MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')'); + + return funStr_.append(FunctionConstructorMedialSigils); + } + + template <typename Unit> + inline MOZ_MUST_USE 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)) { + 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_); + RootedScope enclosingScope(cx_); + if (!CreateNonSyntacticEnvironmentChain(cx_, envChain, &enclosingEnv, + &enclosingScope)) { + return nullptr; + } + + cx_->check(enclosingEnv); + + // Make sure the static scope chain matches up when we have a + // non-syntactic scope. + MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(enclosingEnv), + enclosingScope->hasOnChain(ScopeKind::NonSyntactic)); + + CompileOptions options(cx_, optionsArg); + options.setNonSyntacticScope( + enclosingScope->hasOnChain(ScopeKind::NonSyntactic)); + + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; + RootedFunction fun( + cx_, js::frontend::CompileStandaloneFunction( + cx_, options, newSrcBuf, mozilla::Some(parameterListEnd_), + syntaxKind, enclosingScope)); + 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) { + FunctionCompiler compiler(cx); + if (!compiler.init(name, nargs, argnames) || + !compiler.addFunctionBody(srcBuf)) { + return nullptr; + } + + 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) { + FunctionCompiler compiler(cx); + if (!compiler.init(name, nargs, argnames) || + !compiler.addFunctionBody(srcBuf)) { + return nullptr; + } + + 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 void JS::SetGetElementCallback(JSContext* cx, + JSGetElementCallback callback) { + MOZ_ASSERT(cx->runtime()); + cx->runtime()->setElementCallback(cx->runtime(), callback); +} + +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); + MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(envChain), + script->hasNonSyntacticScope()); + return Execute(cx, script, envChain, rval); +} + +static bool ExecuteScript(JSContext* cx, HandleObjectVector envChain, + HandleScript scriptArg, MutableHandleValue rval) { + RootedObject env(cx); + RootedScope dummy(cx); + if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &dummy)) { + return false; + } + + RootedScript script(cx, scriptArg); + if (!script->hasNonSyntacticScope() && !IsGlobalLexicalEnvironment(env)) { + script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script); + if (!script) { + 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); +} + +JS_PUBLIC_API bool JS::CloneAndExecuteScript(JSContext* cx, + HandleScript scriptArg, + JS::MutableHandleValue rval) { + CHECK_THREAD(cx); + RootedScript script(cx, scriptArg); + RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment()); + if (script->realm() != cx->realm()) { + script = CloneGlobalScript(cx, ScopeKind::Global, script); + if (!script) { + return false; + } + } + return ExecuteScript(cx, globalLexical, script, rval); +} + +JS_PUBLIC_API bool JS::CloneAndExecuteScript(JSContext* cx, + JS::HandleObjectVector envChain, + HandleScript scriptArg, + JS::MutableHandleValue rval) { + CHECK_THREAD(cx); + RootedScript script(cx, scriptArg); + if (script->realm() != cx->realm()) { + script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script); + if (!script) { + return false; + } + } + return ExecuteScript(cx, envChain, script, 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); + + RootedScript script( + cx, frontend::CompileGlobalScript(cx, 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); + RootedScope scope(cx); + if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &scope)) { + return false; + } + + return EvaluateSourceBuffer(cx, scope->kind(), 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); +} |