#include "js/CompilationAndEvaluation.h"
#include "mozilla/Maybe.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Utf8.h"
#include <utility>
#include "jsfriendapi.h"
#include "jstypes.h"
#include "frontend/BytecodeCompilation.h"
#include "frontend/FullParseHandler.h"
#include "frontend/ParseContext.h"
#include "frontend/Parser.h"
#include "js/CharacterEncoding.h"
#include "js/RootingAPI.h"
#include "js/SourceText.h"
#include "js/TypeDecls.h"
#include "js/Utility.h"
#include "js/Value.h"
#include "util/CompleteFile.h"
#include "util/StringBuffer.h"
#include "vm/Debugger.h"
#include "vm/EnvironmentObject.h"
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/JSContext-inl.h"
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 bool CompileSourceBuffer(JSContext* cx,
const ReadOnlyCompileOptions& options,
SourceText<Unit>& srcBuf,
JS::MutableHandleScript script) {
ScopeKind scopeKind =
options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
frontend::GlobalScriptInfo info(cx, options, scopeKind);
script.set(frontend::CompileGlobalScript(info, srcBuf));
return !!script;
}
static bool CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options,
const char* bytes, size_t length,
JS::MutableHandleScript script) {
auto chars = UniqueTwoByteChars(
UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get());
if (!chars) {
return false;
}
SourceText<char16_t> source;
if (!source.init(cx, std::move(chars), length)) {
return false;
}
return CompileSourceBuffer(cx, options, source, script);
}
static bool CompileUtf8DontInflate(JSContext* cx,
const ReadOnlyCompileOptions& options,
const char* bytes, size_t length,
JS::MutableHandleScript script) {
SourceText<Utf8Unit> source;
if (!source.init(cx, bytes, length, SourceOwnership::Borrowed)) {
return false;
}
return CompileSourceBuffer(cx, options, source, script);
}
bool JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
SourceText<char16_t>& srcBuf, JS::MutableHandleScript script) {
return CompileSourceBuffer(cx, options, srcBuf, script);
}
bool JS::CompileDontInflate(JSContext* cx,
const ReadOnlyCompileOptions& options,
SourceText<Utf8Unit>& srcBuf,
JS::MutableHandleScript script) {
return CompileSourceBuffer(cx, options, srcBuf, script);
}
bool JS::CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options,
const char* bytes, size_t length,
JS::MutableHandleScript script) {
return ::CompileUtf8(cx, options, bytes, length, script);
}
bool JS::CompileUtf8DontInflate(JSContext* cx,
const ReadOnlyCompileOptions& options,
const char* bytes, size_t length,
JS::MutableHandleScript script) {
return ::CompileUtf8DontInflate(cx, options, bytes, length, script);
}
bool JS::CompileUtf8File(JSContext* cx, const ReadOnlyCompileOptions& options,
FILE* file, JS::MutableHandleScript script) {
FileContents buffer(cx);
if (!ReadCompleteFile(cx, file, buffer)) {
return false;
}
return ::CompileUtf8(cx, options,
reinterpret_cast<const char*>(buffer.begin()),
buffer.length(), script);
}
bool JS::CompileUtf8FileDontInflate(JSContext* cx,
const ReadOnlyCompileOptions& options,
FILE* file,
JS::MutableHandleScript script) {
FileContents buffer(cx);
if (!ReadCompleteFile(cx, file, buffer)) {
return false;
}
return ::CompileUtf8DontInflate(cx, options,
reinterpret_cast<const char*>(buffer.begin()),
buffer.length(), script);
}
bool JS::CompileUtf8Path(JSContext* cx,
const ReadOnlyCompileOptions& optionsArg,
const char* filename, JS::MutableHandleScript script) {
AutoFile file;
if (!file.open(cx, filename)) {
return false;
}
CompileOptions options(cx, optionsArg);
options.setFileAndLine(filename, 1);
return CompileUtf8File(cx, options, file.fp(), script);
}
bool JS::CompileForNonSyntacticScope(JSContext* cx,
const ReadOnlyCompileOptions& optionsArg,
SourceText<char16_t>& srcBuf,
JS::MutableHandleScript script) {
CompileOptions options(cx, optionsArg);
options.setNonSyntacticScope(true);
return CompileSourceBuffer(cx, options, srcBuf, script);
}
bool JS::CompileUtf8ForNonSyntacticScope(
JSContext* cx, const ReadOnlyCompileOptions& optionsArg, const char* bytes,
size_t length, JS::MutableHandleScript script) {
CompileOptions options(cx, optionsArg);
options.setNonSyntacticScope(true);
return ::CompileUtf8(cx, options, bytes, length, script);
}
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).get()};
if (!chars) {
return true;
}
bool result = true;
using frontend::CreateScriptSourceObject;
using frontend::FullParseHandler;
using frontend::ParseGoal;
using frontend::Parser;
using frontend::UsedNameTracker;
CompileOptions options(cx);
UsedNameTracker usedNames(cx);
Rooted<ScriptSourceObject*> sourceObject(cx);
sourceObject = CreateScriptSourceObject(cx, options, mozilla::Nothing());
if (!sourceObject) {
return false;
}
JS::AutoSuppressWarningReporter suppressWarnings(cx);
Parser<FullParseHandler, char16_t> parser(
cx, cx->tempLifoAlloc(), options, chars.get(), length,
true, usedNames, nullptr, nullptr, sourceObject,
ParseGoal::Script);
if (!parser.checkOptions() || !parser.parse()) {
if (parser.isUnexpectedEOF()) {
result = false;
}
cx->clearPendingException();
}
return result;
}
static bool CompileFunction(
JSContext* cx, const ReadOnlyCompileOptions& optionsArg, HandleAtom name,
bool isInvalidName, SourceText<char16_t>& srcBuf, uint32_t parameterListEnd,
HandleObject enclosingEnv, HandleScope enclosingScope,
MutableHandleFunction fun) {
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(enclosingEnv);
RootedAtom funAtom(cx);
fun.set(NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL,
isInvalidName ? nullptr : name,
nullptr, gc::AllocKind::FUNCTION,
TenuredObject, enclosingEnv));
if (!fun) {
return false;
}
MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(enclosingEnv),
enclosingScope->hasOnChain(ScopeKind::NonSyntactic));
if (!js::frontend::CompileStandaloneFunction(cx, fun, optionsArg, srcBuf,
mozilla::Some(parameterListEnd),
enclosingScope)) {
return false;
}
if (isInvalidName) {
fun->setAtom(name);
}
return true;
}
static MOZ_MUST_USE bool BuildFunctionString(const char* name, size_t nameLen,
unsigned nargs,
const char* const* argnames,
const SourceText<char16_t>& srcBuf,
StringBuffer* out,
uint32_t* parameterListEnd) {
MOZ_ASSERT(out);
MOZ_ASSERT(parameterListEnd);
if (!out->ensureTwoByteChars()) {
return false;
}
if (!out->append("function ")) {
return false;
}
if (name) {
if (!out->append(name, nameLen)) {
return false;
}
}
if (!out->append("(")) {
return false;
}
for (unsigned i = 0; i < nargs; i++) {
if (i != 0) {
if (!out->append(", ")) {
return false;
}
}
if (!out->append(argnames[i], strlen(argnames[i]))) {
return false;
}
}
*parameterListEnd = out->length();
MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')');
if (!out->append(FunctionConstructorMedialSigils)) {
return false;
}
if (!out->append(srcBuf.get(), srcBuf.length())) {
return false;
}
if (!out->append(FunctionConstructorFinalBrace)) {
return false;
}
return true;
}
JS_PUBLIC_API bool JS::CompileFunction(JSContext* cx,
AutoObjectVector& envChain,
const ReadOnlyCompileOptions& options,
const char* name, unsigned nargs,
const char* const* argnames,
SourceText<char16_t>& srcBuf,
MutableHandleFunction fun) {
RootedObject env(cx);
RootedScope scope(cx);
if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &scope)) {
return false;
}
size_t nameLen = 0;
bool isInvalidName = false;
RootedAtom nameAtom(cx);
if (name) {
nameLen = strlen(name);
nameAtom = Atomize(cx, name, nameLen);
if (!nameAtom) {
return false;
}
if (!js::frontend::IsIdentifier(reinterpret_cast<const Latin1Char*>(name),
nameLen)) {
isInvalidName = true;
}
}
uint32_t parameterListEnd;
StringBuffer funStr(cx);
if (!BuildFunctionString(isInvalidName ? nullptr : name, nameLen, nargs,
argnames, srcBuf, &funStr, ¶meterListEnd)) {
return false;
}
size_t newLen = funStr.length();
UniqueTwoByteChars stolen(funStr.stealChars());
if (!stolen) {
return false;
}
SourceText<char16_t> newSrcBuf;
if (!newSrcBuf.init(cx, std::move(stolen), newLen)) {
return false;
}
return CompileFunction(cx, options, nameAtom, isInvalidName, newSrcBuf,
parameterListEnd, env, scope, fun);
}
JS_PUBLIC_API bool JS::CompileFunctionUtf8(
JSContext* cx, AutoObjectVector& envChain,
const ReadOnlyCompileOptions& options, const char* name, unsigned nargs,
const char* const* argnames, const char* bytes, size_t length,
MutableHandleFunction fun) {
auto chars = UniqueTwoByteChars(
UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get());
if (!chars) {
return false;
}
SourceText<char16_t> source;
if (!source.init(cx, std::move(chars), length)) {
return false;
}
return CompileFunction(cx, envChain, options, name, nargs, argnames, source,
fun);
}
JS_PUBLIC_API bool JS::InitScriptSourceElement(JSContext* cx,
HandleScript script,
HandleObject element,
HandleString elementAttrName) {
MOZ_ASSERT(cx);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
RootedScriptSourceObject sso(
cx, &script->sourceObject()->as<ScriptSourceObject>());
return ScriptSourceObject::initElementProperties(cx, sso, element,
elementAttrName);
}
JS_PUBLIC_API void JS::ExposeScriptToDebugger(JSContext* cx,
HandleScript script) {
MOZ_ASSERT(cx);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
MOZ_ASSERT(script->hideScriptFromDebugger());
script->clearHideScriptFromDebugger();
Debugger::onNewScript(cx, script);
}
MOZ_NEVER_INLINE static bool ExecuteScript(JSContext* cx, HandleObject scope,
HandleScript script, Value* rval) {
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(scope, script);
MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(scope),
script->hasNonSyntacticScope());
return Execute(cx, script, *scope, rval);
}
static bool ExecuteScript(JSContext* cx, AutoObjectVector& envChain,
HandleScript scriptArg, Value* 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;
}
js::Debugger::onNewScript(cx, script);
}
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.address());
}
MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx,
HandleScript scriptArg) {
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
return ExecuteScript(cx, globalLexical, scriptArg, nullptr);
}
MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx,
AutoObjectVector& envChain,
HandleScript scriptArg,
MutableHandleValue rval) {
return ExecuteScript(cx, envChain, scriptArg, rval.address());
}
MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx,
AutoObjectVector& envChain,
HandleScript scriptArg) {
return ExecuteScript(cx, envChain, scriptArg, nullptr);
}
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;
}
js::Debugger::onNewScript(cx, script);
}
return ExecuteScript(cx, globalLexical, script, rval.address());
}
JS_PUBLIC_API bool JS::CloneAndExecuteScript(JSContext* cx,
JS::AutoObjectVector& 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;
}
js::Debugger::onNewScript(cx, script);
}
return ExecuteScript(cx, envChain, script, rval.address());
}
static bool Evaluate(JSContext* cx, ScopeKind scopeKind, HandleObject env,
const ReadOnlyCompileOptions& optionsArg,
SourceText<char16_t>& srcBuf, MutableHandleValue 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.setIsRunOnce(true);
RootedScript script(cx);
{
frontend::GlobalScriptInfo info(cx, options, scopeKind);
script = frontend::CompileGlobalScript(info, srcBuf);
if (!script) {
return false;
}
}
return Execute(cx, script, *env,
options.noScriptRval ? nullptr : rval.address());
}
static bool Evaluate(JSContext* cx, AutoObjectVector& envChain,
const ReadOnlyCompileOptions& optionsArg,
SourceText<char16_t>& srcBuf, MutableHandleValue rval) {
RootedObject env(cx);
RootedScope scope(cx);
if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &scope)) {
return false;
}
return ::Evaluate(cx, scope->kind(), env, optionsArg, srcBuf, rval);
}
extern JS_PUBLIC_API bool JS::EvaluateUtf8(
JSContext* cx, const ReadOnlyCompileOptions& options, const char* bytes,
size_t length, MutableHandle<Value> rval) {
auto chars = UniqueTwoByteChars(
UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get());
if (!chars) {
return false;
}
SourceText<char16_t> srcBuf;
if (!srcBuf.init(cx, std::move(chars), length)) {
return false;
}
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
return ::Evaluate(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 ::Evaluate(cx, ScopeKind::Global, globalLexical, optionsArg, srcBuf,
rval);
}
JS_PUBLIC_API bool JS::Evaluate(JSContext* cx, AutoObjectVector& envChain,
const ReadOnlyCompileOptions& optionsArg,
SourceText<char16_t>& srcBuf,
MutableHandleValue rval) {
return ::Evaluate(cx, envChain, optionsArg, 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);
return EvaluateUtf8(cx, options,
reinterpret_cast<const char*>(buffer.begin()),
buffer.length(), rval);
}