summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/Eval.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/builtin/Eval.cpp547
1 files changed, 547 insertions, 0 deletions
diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp
new file mode 100644
index 0000000000..28c3317d41
--- /dev/null
+++ b/js/src/builtin/Eval.cpp
@@ -0,0 +1,547 @@
+/* -*- 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/. */
+
+#include "builtin/Eval.h"
+
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Range.h"
+
+#include "frontend/BytecodeCompilation.h"
+#include "gc/HashUtil.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/friend/JSMEnvironment.h" // JS::NewJSMEnvironment, JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::IsJSMEnvironment
+#include "js/friend/WindowProxy.h" // js::IsWindowProxy
+#include "js/SourceText.h"
+#include "js/StableStringChars.h"
+#include "vm/EnvironmentObject.h"
+#include "vm/FrameIter.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/JSContext.h"
+#include "vm/JSONParser.h"
+
+#include "gc/Marking-inl.h"
+#include "vm/EnvironmentObject-inl.h"
+#include "vm/JSContext-inl.h"
+#include "vm/Stack-inl.h"
+
+using namespace js;
+
+using mozilla::AddToHash;
+using mozilla::HashString;
+using mozilla::RangedPtr;
+
+using JS::AutoCheckCannotGC;
+using JS::AutoStableStringChars;
+using JS::CompileOptions;
+using JS::SourceOwnership;
+using JS::SourceText;
+
+// We should be able to assert this for *any* fp->environmentChain().
+static void AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env) {
+#ifdef DEBUG
+ RootedObject obj(cx);
+ for (obj = &env; obj; obj = obj->enclosingEnvironment()) {
+ MOZ_ASSERT(!IsWindowProxy(obj));
+ }
+#endif
+}
+
+static bool IsEvalCacheCandidate(JSScript* script) {
+ if (!script->isDirectEvalInFunction()) {
+ return false;
+ }
+
+ // Make sure there are no inner objects (which may be used directly by script
+ // and clobbered) or inner functions (which may have wrong scope).
+ for (JS::GCCellPtr gcThing : script->gcthings()) {
+ if (gcThing.is<JSObject>()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */
+HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) {
+ HashNumber hash = HashStringChars(l.str);
+ return AddToHash(hash, l.callerScript.get(), l.pc);
+}
+
+/* static */
+bool EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry,
+ const EvalCacheLookup& l) {
+ MOZ_ASSERT(IsEvalCacheCandidate(cacheEntry.script));
+
+ return EqualStrings(cacheEntry.str, l.str) &&
+ cacheEntry.callerScript == l.callerScript && cacheEntry.pc == l.pc;
+}
+
+// Add the script to the eval cache when EvalKernel is finished
+class EvalScriptGuard {
+ JSContext* cx_;
+ Rooted<JSScript*> script_;
+
+ /* These fields are only valid if lookup_.str is non-nullptr. */
+ EvalCacheLookup lookup_;
+ mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
+
+ Rooted<JSLinearString*> lookupStr_;
+
+ public:
+ explicit EvalScriptGuard(JSContext* cx)
+ : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
+
+ ~EvalScriptGuard() {
+ if (script_ && !cx_->isExceptionPending()) {
+ script_->cacheForEval();
+ EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript,
+ lookup_.pc};
+ lookup_.str = lookupStr_;
+ if (lookup_.str && IsEvalCacheCandidate(script_)) {
+ // Ignore failure to add cache entry.
+ if (!p_->add(cx_, cx_->caches().evalCache, lookup_, cacheEntry)) {
+ cx_->recoverFromOutOfMemory();
+ }
+ }
+ }
+ }
+
+ void lookupInEvalCache(JSLinearString* str, JSScript* callerScript,
+ jsbytecode* pc) {
+ lookupStr_ = str;
+ lookup_.str = str;
+ lookup_.callerScript = callerScript;
+ lookup_.pc = pc;
+ p_.emplace(cx_, cx_->caches().evalCache, lookup_);
+ if (*p_) {
+ script_ = (*p_)->script;
+ p_->remove(cx_, cx_->caches().evalCache, lookup_);
+ }
+ }
+
+ void setNewScript(JSScript* script) {
+ // JSScript::fullyInitFromStencil has already called js_CallNewScriptHook.
+ MOZ_ASSERT(!script_ && script);
+ script_ = script;
+ }
+
+ bool foundScript() { return !!script_; }
+
+ HandleScript script() {
+ MOZ_ASSERT(script_);
+ return script_;
+ }
+};
+
+enum class EvalJSONResult { Failure, Success, NotJSON };
+
+template <typename CharT>
+static bool EvalStringMightBeJSON(const mozilla::Range<const CharT> chars) {
+ // If the eval string starts with '(' or '[' and ends with ')' or ']', it
+ // may be JSON. Try the JSON parser first because it's much faster. If
+ // the eval string isn't JSON, JSON parsing will probably fail quickly, so
+ // little time will be lost.
+ size_t length = chars.length();
+ if (length < 2) {
+ return false;
+ }
+
+ // It used to be that strings in JavaScript forbid U+2028 LINE SEPARATOR
+ // and U+2029 PARAGRAPH SEPARATOR, so something like
+ //
+ // eval("['" + "\u2028" + "']");
+ //
+ // i.e. an array containing a string with a line separator in it, *would*
+ // be JSON but *would not* be valid JavaScript. Handing such a string to
+ // the JSON parser would then fail to recognize a syntax error. As of
+ // <https://tc39.github.io/proposal-json-superset/> JavaScript strings may
+ // contain these two code points, so it's safe to JSON-parse eval strings
+ // that contain them.
+
+ CharT first = chars[0], last = chars[length - 1];
+ return (first == '[' && last == ']') || (first == '(' && last == ')');
+}
+
+template <typename CharT>
+static EvalJSONResult ParseEvalStringAsJSON(
+ JSContext* cx, const mozilla::Range<const CharT> chars,
+ MutableHandleValue rval) {
+ size_t len = chars.length();
+ MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
+ (chars[0] == '[' && chars[len - 1] == ']'));
+
+ auto jsonChars = (chars[0] == '[') ? chars
+ : mozilla::Range<const CharT>(
+ chars.begin().get() + 1U, len - 2);
+
+ Rooted<JSONParser<CharT>> parser(
+ cx, JSONParser<CharT>(cx, jsonChars,
+ JSONParser<CharT>::ParseType::AttemptForEval));
+ if (!parser.parse(rval)) {
+ return EvalJSONResult::Failure;
+ }
+
+ return rval.isUndefined() ? EvalJSONResult::NotJSON : EvalJSONResult::Success;
+}
+
+static EvalJSONResult TryEvalJSON(JSContext* cx, JSLinearString* str,
+ MutableHandleValue rval) {
+ if (str->hasLatin1Chars()) {
+ AutoCheckCannotGC nogc;
+ if (!EvalStringMightBeJSON(str->latin1Range(nogc))) {
+ return EvalJSONResult::NotJSON;
+ }
+ } else {
+ AutoCheckCannotGC nogc;
+ if (!EvalStringMightBeJSON(str->twoByteRange(nogc))) {
+ return EvalJSONResult::NotJSON;
+ }
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.init(cx, str)) {
+ return EvalJSONResult::Failure;
+ }
+
+ return linearChars.isLatin1()
+ ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
+ : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
+}
+
+enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
+
+// 18.2.1.1 PerformEval
+//
+// Common code implementing direct and indirect eval.
+//
+// Evaluate v, if it is a string, in the context of the given calling
+// frame, with the provided scope chain, with the semantics of either a direct
+// or indirect eval (see ES5 10.4.2). If this is an indirect eval, env
+// must be the global lexical environment.
+//
+// On success, store the completion value in call.rval and return true.
+static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
+ AbstractFramePtr caller, HandleObject env,
+ jsbytecode* pc, MutableHandleValue vp) {
+ MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
+ MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
+ MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
+ AssertInnerizedEnvironmentChain(cx, *env);
+
+ // Step 2.
+ if (!v.isString()) {
+ vp.set(v);
+ return true;
+ }
+
+ // Steps 3-4.
+ RootedString str(cx, v.toString());
+ if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, str)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CSP_BLOCKED_EVAL);
+ return false;
+ }
+
+ // Step 5 ff.
+
+ // Per ES5, indirect eval runs in the global scope. (eval is specified this
+ // way so that the compiler can make assumptions about what bindings may or
+ // may not exist in the current frame if it doesn't see 'eval'.)
+ MOZ_ASSERT_IF(
+ evalType != DIRECT_EVAL,
+ cx->global() == &env->as<GlobalLexicalEnvironmentObject>().global());
+
+ Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
+ if (!linearStr) {
+ return false;
+ }
+
+ RootedScript callerScript(cx, caller ? caller.script() : nullptr);
+ EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
+ if (ejr != EvalJSONResult::NotJSON) {
+ return ejr == EvalJSONResult::Success;
+ }
+
+ EvalScriptGuard esg(cx);
+
+ if (evalType == DIRECT_EVAL && caller.isFunctionFrame()) {
+ esg.lookupInEvalCache(linearStr, callerScript, pc);
+ }
+
+ if (!esg.foundScript()) {
+ RootedScript maybeScript(cx);
+ unsigned lineno;
+ const char* filename;
+ bool mutedErrors;
+ uint32_t pcOffset;
+ if (evalType == DIRECT_EVAL) {
+ DescribeScriptedCallerForDirectEval(cx, callerScript, pc, &filename,
+ &lineno, &pcOffset, &mutedErrors);
+ maybeScript = callerScript;
+ } else {
+ DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
+ &pcOffset, &mutedErrors);
+ }
+
+ const char* introducerFilename = filename;
+ if (maybeScript && maybeScript->scriptSource()->introducerFilename()) {
+ introducerFilename = maybeScript->scriptSource()->introducerFilename();
+ }
+
+ Rooted<Scope*> enclosing(cx);
+ if (evalType == DIRECT_EVAL) {
+ enclosing = callerScript->innermostScope(pc);
+ } else {
+ enclosing = &cx->global()->emptyGlobalScope();
+ }
+
+ CompileOptions options(cx);
+ options.setIsRunOnce(true)
+ .setNoScriptRval(false)
+ .setMutedErrors(mutedErrors)
+ .setDeferDebugMetadata();
+
+ RootedScript introScript(cx);
+
+ if (evalType == DIRECT_EVAL && IsStrictEvalPC(pc)) {
+ options.setForceStrictMode();
+ }
+
+ if (introducerFilename) {
+ options.setFileAndLine(filename, 1);
+ options.setIntroductionInfo(introducerFilename, "eval", lineno, pcOffset);
+ introScript = maybeScript;
+ } else {
+ options.setFileAndLine("eval", 1);
+ options.setIntroductionType("eval");
+ }
+ options.setNonSyntacticScope(
+ enclosing->hasOnChain(ScopeKind::NonSyntactic));
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, linearStr)) {
+ return false;
+ }
+
+ SourceText<char16_t> srcBuf;
+ if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
+ return false;
+ }
+
+ RootedScript script(
+ cx, frontend::CompileEvalScript(cx, options, srcBuf, enclosing, env));
+ if (!script) {
+ return false;
+ }
+
+ RootedValue undefValue(cx);
+ JS::InstantiateOptions instantiateOptions(options);
+ if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, undefValue,
+ nullptr, introScript, maybeScript)) {
+ return false;
+ }
+
+ esg.setNewScript(script);
+ }
+
+ return ExecuteKernel(cx, esg.script(), env, NullFramePtr() /* evalInFrame */,
+ vp);
+}
+
+bool js::IndirectEval(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
+
+ // Note we'll just pass |undefined| here, then return it directly (or throw
+ // if runtime codegen is disabled), if no argument is provided.
+ return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(),
+ globalLexical, nullptr, args.rval());
+}
+
+bool js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp) {
+ // Direct eval can assume it was called from an interpreted or baseline frame.
+ ScriptFrameIter iter(cx);
+ AbstractFramePtr caller = iter.abstractFramePtr();
+
+ MOZ_ASSERT(JSOp(*iter.pc()) == JSOp::Eval ||
+ JSOp(*iter.pc()) == JSOp::StrictEval ||
+ JSOp(*iter.pc()) == JSOp::SpreadEval ||
+ JSOp(*iter.pc()) == JSOp::StrictSpreadEval);
+ MOZ_ASSERT(caller.realm() == caller.script()->realm());
+
+ RootedObject envChain(cx, caller.environmentChain());
+ return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
+}
+
+bool js::IsAnyBuiltinEval(JSFunction* fun) {
+ return fun->maybeNative() == IndirectEval;
+}
+
+static bool ExecuteInExtensibleLexicalEnvironment(
+ JSContext* cx, HandleScript scriptArg,
+ Handle<ExtensibleLexicalEnvironmentObject*> env) {
+ CHECK_THREAD(cx);
+ cx->check(env);
+ cx->check(scriptArg);
+ MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
+
+ RootedValue rval(cx);
+ return ExecuteKernel(cx, scriptArg, env, NullFramePtr() /* evalInFrame */,
+ &rval);
+}
+
+JS_PUBLIC_API bool js::ExecuteInFrameScriptEnvironment(
+ JSContext* cx, HandleObject objArg, HandleScript scriptArg,
+ MutableHandleObject envArg) {
+ RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
+ if (!varEnv) {
+ return false;
+ }
+
+ RootedObjectVector envChain(cx);
+ if (!envChain.append(objArg)) {
+ return false;
+ }
+
+ RootedObject env(cx);
+ if (!js::CreateObjectsForEnvironmentChain(cx, envChain, varEnv, &env)) {
+ return false;
+ }
+
+ // Create lexical environment with |this| == objArg, which should be a Gecko
+ // MessageManager.
+ // NOTE: This is required behavior for Gecko FrameScriptLoader, where some
+ // callers try to bind methods from the message manager in their scope chain
+ // to |this|, and will fail if it is not bound to a message manager.
+ ObjectRealm& realm = ObjectRealm::get(varEnv);
+ Rooted<NonSyntacticLexicalEnvironmentObject*> lexicalEnv(
+ cx,
+ realm.getOrCreateNonSyntacticLexicalEnvironment(cx, env, varEnv, objArg));
+ if (!lexicalEnv) {
+ return false;
+ }
+
+ if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexicalEnv)) {
+ return false;
+ }
+
+ envArg.set(lexicalEnv);
+ return true;
+}
+
+JS_PUBLIC_API JSObject* JS::NewJSMEnvironment(JSContext* cx) {
+ RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
+ if (!varEnv) {
+ return nullptr;
+ }
+
+ // Force the NonSyntacticLexicalEnvironmentObject to be created.
+ ObjectRealm& realm = ObjectRealm::get(varEnv);
+ MOZ_ASSERT(!realm.getNonSyntacticLexicalEnvironment(varEnv));
+ if (!realm.getOrCreateNonSyntacticLexicalEnvironment(cx, varEnv)) {
+ return nullptr;
+ }
+
+ return varEnv;
+}
+
+JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx,
+ HandleScript scriptArg,
+ HandleObject varEnv) {
+ RootedObjectVector emptyChain(cx);
+ return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
+}
+
+JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx,
+ HandleScript scriptArg,
+ HandleObject varEnv,
+ HandleObjectVector targetObj) {
+ cx->check(varEnv);
+ MOZ_ASSERT(
+ ObjectRealm::get(varEnv).getNonSyntacticLexicalEnvironment(varEnv));
+ MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
+
+ Rooted<ExtensibleLexicalEnvironmentObject*> env(
+ cx, ExtensibleLexicalEnvironmentObject::forVarEnvironment(varEnv));
+
+ // If the Gecko subscript loader specifies target objects, we need to add
+ // them to the environment. These are added after the NSVO environment.
+ if (!targetObj.empty()) {
+ // The environment chain will be as follows:
+ // GlobalObject / BackstagePass
+ // GlobalLexicalEnvironmentObject[this=global]
+ // NonSyntacticVariablesObject (the JSMEnvironment)
+ // NonSyntacticLexicalEnvironmentObject[this=nsvo]
+ // WithEnvironmentObject[target=targetObj]
+ // NonSyntacticLexicalEnvironmentObject[this=targetObj] (*)
+ //
+ // (*) This environment intercepts JSOp::GlobalThis.
+
+ // Wrap the target objects in WithEnvironments.
+ RootedObject envChain(cx);
+ if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &envChain)) {
+ return false;
+ }
+
+ // See CreateNonSyntacticEnvironmentChain
+ if (!JSObject::setQualifiedVarObj(cx, envChain)) {
+ return false;
+ }
+
+ // Create an extensible lexical environment for the target object.
+ env = ObjectRealm::get(envChain).getOrCreateNonSyntacticLexicalEnvironment(
+ cx, envChain);
+ if (!env) {
+ return false;
+ }
+ }
+
+ return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
+}
+
+JS_PUBLIC_API JSObject* JS::GetJSMEnvironmentOfScriptedCaller(JSContext* cx) {
+ FrameIter iter(cx);
+ if (iter.done()) {
+ return nullptr;
+ }
+
+ // WASM frames don't always provide their environment, but we also shouldn't
+ // expect to see any calling into here.
+ MOZ_RELEASE_ASSERT(!iter.isWasm());
+
+ RootedObject env(cx, iter.environmentChain(cx));
+ while (env && !env->is<NonSyntacticVariablesObject>()) {
+ env = env->enclosingEnvironment();
+ }
+
+ return env;
+}
+
+JS_PUBLIC_API bool JS::IsJSMEnvironment(JSObject* obj) {
+ // NOTE: This also returns true if the NonSyntacticVariablesObject was
+ // created for reasons other than the JSM loader.
+ return obj->is<NonSyntacticVariablesObject>();
+}
+
+#ifdef JSGC_HASH_TABLE_CHECKS
+void RuntimeCaches::checkEvalCacheAfterMinorGC() {
+ JSContext* cx = TlsContext.get();
+ for (auto r = evalCache.all(); !r.empty(); r.popFront()) {
+ const EvalCacheEntry& entry = r.front();
+ CheckGCThingAfterMovingGC(entry.str);
+ EvalCacheLookup lookup(cx);
+ lookup.str = entry.str;
+ lookup.callerScript = entry.callerScript;
+ lookup.pc = entry.pc;
+ auto ptr = evalCache.lookup(lookup);
+ MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
+ }
+}
+#endif