summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/Eval.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/Eval.cpp')
-rw-r--r--js/src/builtin/Eval.cpp485
1 files changed, 485 insertions, 0 deletions
diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp
new file mode 100644
index 000000000..87321ba04
--- /dev/null
+++ b/js/src/builtin/Eval.cpp
@@ -0,0 +1,485 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "jscntxt.h"
+#include "jshashutil.h"
+
+#include "frontend/BytecodeCompiler.h"
+#include "vm/Debugger.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSONParser.h"
+
+#include "vm/Interpreter-inl.h"
+
+using namespace js;
+
+using mozilla::AddToHash;
+using mozilla::HashString;
+using mozilla::RangedPtr;
+
+using JS::AutoCheckCannotGC;
+
+// 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)
+{
+ // Make sure there are no inner objects which might use the wrong parent
+ // and/or call scope by reusing the previous eval's script.
+ return script->isDirectEvalInFunction() &&
+ !script->hasSingletons() &&
+ !script->hasObjects();
+}
+
+/* static */ HashNumber
+EvalCacheHashPolicy::hash(const EvalCacheLookup& l)
+{
+ AutoCheckCannotGC nogc;
+ uint32_t hash = l.str->hasLatin1Chars()
+ ? HashString(l.str->latin1Chars(nogc), l.str->length())
+ : HashString(l.str->twoByteChars(nogc), l.str->length());
+ return AddToHash(hash, l.callerScript.get(), l.version, l.pc);
+}
+
+/* static */ bool
+EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry, const EvalCacheLookup& l)
+{
+ JSScript* script = cacheEntry.script;
+
+ MOZ_ASSERT(IsEvalCacheCandidate(script));
+
+ return EqualStrings(cacheEntry.str, l.str) &&
+ cacheEntry.callerScript == l.callerScript &&
+ script->getVersion() == l.version &&
+ 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_;
+
+ RootedLinearString 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_.version = cx_->findVersion();
+ lookup_.pc = pc;
+ p_.emplace(cx_, cx_->caches.evalCache, lookup_);
+ if (*p_) {
+ script_ = (*p_)->script;
+ p_->remove(cx_, cx_->caches.evalCache, lookup_);
+ script_->uncacheForEval();
+ }
+ }
+
+ void setNewScript(JSScript* script) {
+ // JSScript::initFromEmitter has already called js_CallNewScriptHook.
+ MOZ_ASSERT(!script_ && script);
+ script_ = script;
+ script_->setActiveEval();
+ }
+
+ bool foundScript() {
+ return !!script_;
+ }
+
+ HandleScript script() {
+ MOZ_ASSERT(script_);
+ return script_;
+ }
+};
+
+enum EvalJSONResult {
+ EvalJSON_Failure,
+ EvalJSON_Success,
+ EvalJSON_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 &&
+ ((chars[0] == '[' && chars[length - 1] == ']') ||
+ (chars[0] == '(' && chars[length - 1] == ')')))
+ {
+ // Remarkably, JavaScript syntax is not a superset of JSON syntax:
+ // strings in JavaScript cannot contain the Unicode line and paragraph
+ // terminator characters U+2028 and U+2029, but strings in JSON can.
+ // Rather than force the JSON parser to handle this quirk when used by
+ // eval, we simply don't use the JSON parser when either character
+ // appears in the provided string. See bug 657367.
+ if (sizeof(CharT) > 1) {
+ for (RangedPtr<const CharT> cp = chars.begin() + 1, end = chars.end() - 1;
+ cp < end;
+ cp++)
+ {
+ char16_t c = *cp;
+ if (c == 0x2028 || c == 0x2029)
+ return false;
+ }
+ }
+
+ return true;
+ }
+ return false;
+}
+
+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, JSONParserBase::NoError));
+ if (!parser.parse(rval))
+ return EvalJSON_Failure;
+
+ return rval.isUndefined() ? EvalJSON_NotJSON : EvalJSON_Success;
+}
+
+static EvalJSONResult
+TryEvalJSON(JSContext* cx, JSLinearString* str, MutableHandleValue rval)
+{
+ if (str->hasLatin1Chars()) {
+ AutoCheckCannotGC nogc;
+ if (!EvalStringMightBeJSON(str->latin1Range(nogc)))
+ return EvalJSON_NotJSON;
+ } else {
+ AutoCheckCannotGC nogc;
+ if (!EvalStringMightBeJSON(str->twoByteRange(nogc)))
+ return EvalJSON_NotJSON;
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.init(cx, str))
+ return EvalJSON_Failure;
+
+ return linearChars.isLatin1()
+ ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
+ : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
+}
+
+enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
+
+// Common code implementing direct and indirect eval.
+//
+// Evaluate call.argv[2], 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 a global object.
+//
+// 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);
+
+ Rooted<GlobalObject*> envGlobal(cx, &env->global());
+ if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
+ return false;
+ }
+
+ // ES5 15.1.2.1 step 1.
+ if (!v.isString()) {
+ vp.set(v);
+ return true;
+ }
+ RootedString str(cx, v.toString());
+
+ // ES5 15.1.2.1 steps 2-8.
+
+ // 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<LexicalEnvironmentObject>().global());
+
+ RootedLinearString linearStr(cx, str->ensureLinear(cx));
+ if (!linearStr)
+ return false;
+
+ RootedScript callerScript(cx, caller ? caller.script() : nullptr);
+ EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
+ if (ejr != EvalJSON_NotJSON)
+ return ejr == EvalJSON_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;
+ DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
+ &mutedErrors,
+ evalType == DIRECT_EVAL
+ ? CALLED_FROM_JSOP_EVAL
+ : NOT_CALLED_FROM_JSOP_EVAL);
+
+ const char* introducerFilename = filename;
+ if (maybeScript && maybeScript->scriptSource()->introducerFilename())
+ introducerFilename = maybeScript->scriptSource()->introducerFilename();
+
+ RootedScope 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)
+ .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
+
+ if (introducerFilename) {
+ options.setFileAndLine(filename, 1);
+ options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
+ } else {
+ options.setFileAndLine("eval", 1);
+ options.setIntroductionType("eval");
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, linearStr))
+ return false;
+
+ const char16_t* chars = linearChars.twoByteRange().begin().get();
+ SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
+ ? SourceBufferHolder::GiveOwnership
+ : SourceBufferHolder::NoOwnership;
+ SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
+ JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
+ env, enclosing,
+ options, srcBuf);
+ if (!compiled)
+ return false;
+
+ esg.setNewScript(compiled);
+ }
+
+ // Look up the newTarget from the frame iterator.
+ Value newTargetVal = NullValue();
+ return ExecuteKernel(cx, esg.script(), *env, newTargetVal,
+ NullFramePtr() /* evalInFrame */, vp.address());
+}
+
+bool
+js::DirectEvalStringFromIon(JSContext* cx,
+ HandleObject env, HandleScript callerScript,
+ HandleValue newTargetValue, HandleString str,
+ jsbytecode* pc, MutableHandleValue vp)
+{
+ AssertInnerizedEnvironmentChain(cx, *env);
+
+ Rooted<GlobalObject*> envGlobal(cx, &env->global());
+ if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
+ return false;
+ }
+
+ // ES5 15.1.2.1 steps 2-8.
+
+ RootedLinearString linearStr(cx, str->ensureLinear(cx));
+ if (!linearStr)
+ return false;
+
+ EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
+ if (ejr != EvalJSON_NotJSON)
+ return ejr == EvalJSON_Success;
+
+ EvalScriptGuard esg(cx);
+
+ esg.lookupInEvalCache(linearStr, callerScript, pc);
+
+ if (!esg.foundScript()) {
+ RootedScript maybeScript(cx);
+ const char* filename;
+ unsigned lineno;
+ bool mutedErrors;
+ uint32_t pcOffset;
+ DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
+ &mutedErrors, CALLED_FROM_JSOP_EVAL);
+
+ const char* introducerFilename = filename;
+ if (maybeScript && maybeScript->scriptSource()->introducerFilename())
+ introducerFilename = maybeScript->scriptSource()->introducerFilename();
+
+ RootedScope enclosing(cx, callerScript->innermostScope(pc));
+
+ CompileOptions options(cx);
+ options.setIsRunOnce(true)
+ .setNoScriptRval(false)
+ .setMutedErrors(mutedErrors)
+ .maybeMakeStrictMode(IsStrictEvalPC(pc));
+
+ if (introducerFilename) {
+ options.setFileAndLine(filename, 1);
+ options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
+ } else {
+ options.setFileAndLine("eval", 1);
+ options.setIntroductionType("eval");
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, linearStr))
+ return false;
+
+ const char16_t* chars = linearChars.twoByteRange().begin().get();
+ SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
+ ? SourceBufferHolder::GiveOwnership
+ : SourceBufferHolder::NoOwnership;
+ SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
+ JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
+ env, enclosing,
+ options, srcBuf);
+ if (!compiled)
+ return false;
+
+ esg.setNewScript(compiled);
+ }
+
+ return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
+ NullFramePtr() /* evalInFrame */, vp.address());
+}
+
+bool
+js::IndirectEval(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<GlobalObject*> global(cx, &args.callee().global());
+ RootedObject globalLexical(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.compartment() == caller.script()->compartment());
+
+ 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;
+}
+
+JS_FRIEND_API(bool)
+js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
+ MutableHandleObject envArg)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, global);
+ MOZ_ASSERT(global->is<GlobalObject>());
+ MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
+
+ RootedScript script(cx, scriptArg);
+ Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
+ if (script->compartment() != cx->compartment()) {
+ script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
+ if (!script)
+ return false;
+
+ Debugger::onNewScript(cx, script);
+ }
+
+ Rooted<EnvironmentObject*> env(cx, NonSyntacticVariablesObject::create(cx));
+ if (!env)
+ return false;
+
+ // Unlike the non-syntactic scope chain API used by the subscript loader,
+ // this API creates a fresh block scope each time.
+ env = LexicalEnvironmentObject::createNonSyntactic(cx, env);
+ if (!env)
+ return false;
+
+ RootedValue rval(cx);
+ if (!ExecuteKernel(cx, script, *env, UndefinedValue(),
+ NullFramePtr() /* evalInFrame */, rval.address()))
+ {
+ return false;
+ }
+
+ envArg.set(env);
+ return true;
+}