/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * 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 script_; /* These fields are only valid if lookup_.str is non-nullptr. */ EvalCacheLookup lookup_; mozilla::Maybe> 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 static bool EvalStringMightBeJSON(const mozilla::Range 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 // 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 static EvalJSONResult ParseEvalStringAsJSON(JSContext* cx, const mozilla::Range 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(chars.begin().get() + 1U, len - 2); Rooted> parser(cx, JSONParser(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 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().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 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 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()); MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope()); RootedScript script(cx, scriptArg); Rooted globalRoot(cx, &global->as()); if (script->compartment() != cx->compartment()) { script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script); if (!script) return false; Debugger::onNewScript(cx, script); } Rooted 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; }