/* -*- 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 // 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 static JSScript* CompileSourceBuffer(JSContext* cx, const ReadOnlyCompileOptions& options, SourceText& 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& srcBuf) { return CompileSourceBuffer(cx, options, srcBuf); } JSScript* JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options, SourceText& srcBuf) { return CompileSourceBuffer(cx, options, srcBuf); } template static JSScript* CompileSourceBufferAndStartIncrementalEncoding( JSContext* cx, const ReadOnlyCompileOptions& options, SourceText& srcBuf) { ScopeKind scopeKind = options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global; MOZ_ASSERT(!cx->zone()->isAtomsZone()); AssertHeapIsIdle(); CHECK_THREAD(cx); Rooted 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 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 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& srcBuf) { return CompileSourceBufferAndStartIncrementalEncoding(cx, options, srcBuf); } JSScript* JS::CompileAndStartIncrementalEncoding( JSContext* cx, const ReadOnlyCompileOptions& options, SourceText& 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 srcBuf; if (!srcBuf.init(cx, reinterpret_cast(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 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 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(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 inline MOZ_MUST_USE bool addFunctionBody(const SourceText& 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 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& 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& 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 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 static bool EvaluateSourceBuffer(JSContext* cx, ScopeKind scopeKind, Handle env, const ReadOnlyCompileOptions& optionsArg, SourceText& srcBuf, MutableHandle 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& srcBuf, MutableHandle 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& 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& 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(buffer.begin()); size_t length = buffer.length(); JS::SourceText srcBuf; if (!srcBuf.init(cx, contents, length, JS::SourceOwnership::Borrowed)) { return false; } return Evaluate(cx, options, srcBuf, rval); }