LCOV - code coverage report
Current view: top level - js/src/builtin - Eval.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 131 236 55.5 %
Date: 2017-07-14 16:53:18 Functions: 14 21 66.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
       2             :  * vim: set ts=8 sts=4 et sw=4 tw=99:
       3             :  * This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "builtin/Eval.h"
       8             : 
       9             : #include "mozilla/HashFunctions.h"
      10             : #include "mozilla/Range.h"
      11             : 
      12             : #include "jscntxt.h"
      13             : #include "jshashutil.h"
      14             : 
      15             : #include "frontend/BytecodeCompiler.h"
      16             : #include "vm/Debugger.h"
      17             : #include "vm/GlobalObject.h"
      18             : #include "vm/JSONParser.h"
      19             : 
      20             : #include "vm/Interpreter-inl.h"
      21             : 
      22             : using namespace js;
      23             : 
      24             : using mozilla::AddToHash;
      25             : using mozilla::HashString;
      26             : using mozilla::RangedPtr;
      27             : 
      28             : using JS::AutoCheckCannotGC;
      29             : 
      30             : // We should be able to assert this for *any* fp->environmentChain().
      31             : static void
      32           2 : AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env)
      33             : {
      34             : #ifdef DEBUG
      35           4 :     RootedObject obj(cx);
      36           8 :     for (obj = &env; obj; obj = obj->enclosingEnvironment())
      37           6 :         MOZ_ASSERT(!IsWindowProxy(obj));
      38             : #endif
      39           2 : }
      40             : 
      41             : static bool
      42           2 : IsEvalCacheCandidate(JSScript* script)
      43             : {
      44             :     // Make sure there are no inner objects which might use the wrong parent
      45             :     // and/or call scope by reusing the previous eval's script.
      46           4 :     return script->isDirectEvalInFunction() &&
      47           2 :            !script->hasSingletons() &&
      48           2 :            !script->hasObjects();
      49             : }
      50             : 
      51             : /* static */ HashNumber
      52           2 : EvalCacheHashPolicy::hash(const EvalCacheLookup& l)
      53             : {
      54           4 :     AutoCheckCannotGC nogc;
      55           2 :     uint32_t hash = l.str->hasLatin1Chars()
      56           4 :                     ? HashString(l.str->latin1Chars(nogc), l.str->length())
      57           4 :                     : HashString(l.str->twoByteChars(nogc), l.str->length());
      58           4 :     return AddToHash(hash, l.callerScript.get(), l.version, l.pc);
      59             : }
      60             : 
      61             : /* static */ bool
      62           0 : EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry, const EvalCacheLookup& l)
      63             : {
      64           0 :     JSScript* script = cacheEntry.script;
      65             : 
      66           0 :     MOZ_ASSERT(IsEvalCacheCandidate(script));
      67             : 
      68           0 :     return EqualStrings(cacheEntry.str, l.str) &&
      69           0 :            cacheEntry.callerScript == l.callerScript &&
      70           0 :            script->getVersion() == l.version &&
      71           0 :            cacheEntry.pc == l.pc;
      72             : }
      73             : 
      74             : // Add the script to the eval cache when EvalKernel is finished
      75             : class EvalScriptGuard
      76             : {
      77             :     JSContext* cx_;
      78             :     Rooted<JSScript*> script_;
      79             : 
      80             :     /* These fields are only valid if lookup_.str is non-nullptr. */
      81             :     EvalCacheLookup lookup_;
      82             :     mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
      83             : 
      84             :     RootedLinearString lookupStr_;
      85             : 
      86             :   public:
      87           2 :     explicit EvalScriptGuard(JSContext* cx)
      88           2 :         : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
      89             : 
      90           4 :     ~EvalScriptGuard() {
      91           2 :         if (script_ && !cx_->isExceptionPending()) {
      92           2 :             script_->cacheForEval();
      93           2 :             EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript, lookup_.pc};
      94           2 :             lookup_.str = lookupStr_;
      95           2 :             if (lookup_.str && IsEvalCacheCandidate(script_)) {
      96             :                 // Ignore failure to add cache entry.
      97           0 :                 if (!p_->add(cx_, cx_->caches().evalCache, lookup_, cacheEntry))
      98           0 :                     cx_->recoverFromOutOfMemory();
      99             :             }
     100             :         }
     101           2 :     }
     102             : 
     103           2 :     void lookupInEvalCache(JSLinearString* str, JSScript* callerScript, jsbytecode* pc)
     104             :     {
     105           2 :         lookupStr_ = str;
     106           2 :         lookup_.str = str;
     107           2 :         lookup_.callerScript = callerScript;
     108           2 :         lookup_.version = cx_->findVersion();
     109           2 :         lookup_.pc = pc;
     110           2 :         p_.emplace(cx_, cx_->caches().evalCache, lookup_);
     111           2 :         if (*p_) {
     112           0 :             script_ = (*p_)->script;
     113           0 :             p_->remove(cx_, cx_->caches().evalCache, lookup_);
     114           0 :             script_->uncacheForEval();
     115             :         }
     116           2 :     }
     117             : 
     118           2 :     void setNewScript(JSScript* script) {
     119             :         // JSScript::initFromEmitter has already called js_CallNewScriptHook.
     120           2 :         MOZ_ASSERT(!script_ && script);
     121           2 :         script_ = script;
     122           2 :         script_->setActiveEval();
     123           2 :     }
     124             : 
     125           2 :     bool foundScript() {
     126           2 :         return !!script_;
     127             :     }
     128             : 
     129           2 :     HandleScript script() {
     130           2 :         MOZ_ASSERT(script_);
     131           2 :         return script_;
     132             :     }
     133             : };
     134             : 
     135             : enum EvalJSONResult {
     136             :     EvalJSON_Failure,
     137             :     EvalJSON_Success,
     138             :     EvalJSON_NotJSON
     139             : };
     140             : 
     141             : template <typename CharT>
     142             : static bool
     143           2 : EvalStringMightBeJSON(const mozilla::Range<const CharT> chars)
     144             : {
     145             :     // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON.
     146             :     // Try the JSON parser first because it's much faster.  If the eval string
     147             :     // isn't JSON, JSON parsing will probably fail quickly, so little time
     148             :     // will be lost.
     149           2 :     size_t length = chars.length();
     150           4 :     if (length > 2 &&
     151           4 :         ((chars[0] == '[' && chars[length - 1] == ']') ||
     152           2 :          (chars[0] == '(' && chars[length - 1] == ')')))
     153             :     {
     154             :         // Remarkably, JavaScript syntax is not a superset of JSON syntax:
     155             :         // strings in JavaScript cannot contain the Unicode line and paragraph
     156             :         // terminator characters U+2028 and U+2029, but strings in JSON can.
     157             :         // Rather than force the JSON parser to handle this quirk when used by
     158             :         // eval, we simply don't use the JSON parser when either character
     159             :         // appears in the provided string.  See bug 657367.
     160             :         if (sizeof(CharT) > 1) {
     161           0 :             for (RangedPtr<const CharT> cp = chars.begin() + 1, end = chars.end() - 1;
     162             :                  cp < end;
     163             :                  cp++)
     164             :             {
     165           0 :                 char16_t c = *cp;
     166           0 :                 if (c == 0x2028 || c == 0x2029)
     167           0 :                     return false;
     168             :             }
     169             :         }
     170             : 
     171           0 :         return true;
     172             :     }
     173           2 :     return false;
     174             : }
     175             : 
     176             : template <typename CharT>
     177             : static EvalJSONResult
     178           0 : ParseEvalStringAsJSON(JSContext* cx, const mozilla::Range<const CharT> chars, MutableHandleValue rval)
     179             : {
     180           0 :     size_t len = chars.length();
     181           0 :     MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
     182             :                (chars[0] == '[' && chars[len - 1] == ']'));
     183             : 
     184           0 :     auto jsonChars = (chars[0] == '[')
     185             :                      ? chars
     186           0 :                      : mozilla::Range<const CharT>(chars.begin().get() + 1U, len - 2);
     187             : 
     188           0 :     Rooted<JSONParser<CharT>> parser(cx, JSONParser<CharT>(cx, jsonChars, JSONParserBase::NoError));
     189           0 :     if (!parser.parse(rval))
     190           0 :         return EvalJSON_Failure;
     191             : 
     192           0 :     return rval.isUndefined() ? EvalJSON_NotJSON : EvalJSON_Success;
     193             : }
     194             : 
     195             : static EvalJSONResult
     196           2 : TryEvalJSON(JSContext* cx, JSLinearString* str, MutableHandleValue rval)
     197             : {
     198           2 :     if (str->hasLatin1Chars()) {
     199           0 :         AutoCheckCannotGC nogc;
     200           0 :         if (!EvalStringMightBeJSON(str->latin1Range(nogc)))
     201           0 :             return EvalJSON_NotJSON;
     202             :     } else {
     203           2 :         AutoCheckCannotGC nogc;
     204           2 :         if (!EvalStringMightBeJSON(str->twoByteRange(nogc)))
     205           2 :             return EvalJSON_NotJSON;
     206             :     }
     207             : 
     208           0 :     AutoStableStringChars linearChars(cx);
     209           0 :     if (!linearChars.init(cx, str))
     210           0 :         return EvalJSON_Failure;
     211             : 
     212           0 :     return linearChars.isLatin1()
     213           0 :            ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
     214           0 :            : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
     215             : }
     216             : 
     217             : enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
     218             : 
     219             : // Common code implementing direct and indirect eval.
     220             : //
     221             : // Evaluate call.argv[2], if it is a string, in the context of the given calling
     222             : // frame, with the provided scope chain, with the semantics of either a direct
     223             : // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, env
     224             : // must be a global object.
     225             : //
     226             : // On success, store the completion value in call.rval and return true.
     227             : static bool
     228           2 : EvalKernel(JSContext* cx, HandleValue v, EvalType evalType, AbstractFramePtr caller,
     229             :            HandleObject env, jsbytecode* pc, MutableHandleValue vp)
     230             : {
     231           2 :     MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
     232           2 :     MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
     233           2 :     MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
     234           2 :     AssertInnerizedEnvironmentChain(cx, *env);
     235             : 
     236           4 :     Rooted<GlobalObject*> envGlobal(cx, &env->global());
     237           2 :     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
     238           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
     239           0 :         return false;
     240             :     }
     241             : 
     242             :     // ES5 15.1.2.1 step 1.
     243           2 :     if (!v.isString()) {
     244           0 :         vp.set(v);
     245           0 :         return true;
     246             :     }
     247           4 :     RootedString str(cx, v.toString());
     248             : 
     249             :     // ES5 15.1.2.1 steps 2-8.
     250             : 
     251             :     // Per ES5, indirect eval runs in the global scope. (eval is specified this
     252             :     // way so that the compiler can make assumptions about what bindings may or
     253             :     // may not exist in the current frame if it doesn't see 'eval'.)
     254           2 :     MOZ_ASSERT_IF(evalType != DIRECT_EVAL,
     255             :                   cx->global() == &env->as<LexicalEnvironmentObject>().global());
     256             : 
     257           4 :     RootedLinearString linearStr(cx, str->ensureLinear(cx));
     258           2 :     if (!linearStr)
     259           0 :         return false;
     260             : 
     261           4 :     RootedScript callerScript(cx, caller ? caller.script() : nullptr);
     262           2 :     EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
     263           2 :     if (ejr != EvalJSON_NotJSON)
     264           0 :         return ejr == EvalJSON_Success;
     265             : 
     266           4 :     EvalScriptGuard esg(cx);
     267             : 
     268           2 :     if (evalType == DIRECT_EVAL && caller.isFunctionFrame())
     269           2 :         esg.lookupInEvalCache(linearStr, callerScript, pc);
     270             : 
     271           2 :     if (!esg.foundScript()) {
     272           4 :         RootedScript maybeScript(cx);
     273             :         unsigned lineno;
     274             :         const char* filename;
     275             :         bool mutedErrors;
     276             :         uint32_t pcOffset;
     277           4 :         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
     278             :                                              &mutedErrors,
     279             :                                              evalType == DIRECT_EVAL
     280             :                                              ? CALLED_FROM_JSOP_EVAL
     281           2 :                                              : NOT_CALLED_FROM_JSOP_EVAL);
     282             : 
     283           2 :         const char* introducerFilename = filename;
     284           2 :         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
     285           2 :             introducerFilename = maybeScript->scriptSource()->introducerFilename();
     286             : 
     287           4 :         RootedScope enclosing(cx);
     288           2 :         if (evalType == DIRECT_EVAL)
     289           2 :             enclosing = callerScript->innermostScope(pc);
     290             :         else
     291           0 :             enclosing = &cx->global()->emptyGlobalScope();
     292             : 
     293           4 :         CompileOptions options(cx);
     294           2 :         options.setIsRunOnce(true)
     295           2 :                .setNoScriptRval(false)
     296           4 :                .setMutedErrors(mutedErrors)
     297           4 :                .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
     298             : 
     299           2 :         if (introducerFilename) {
     300           2 :             options.setFileAndLine(filename, 1);
     301           2 :             options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
     302             :         } else {
     303           0 :             options.setFileAndLine("eval", 1);
     304           0 :             options.setIntroductionType("eval");
     305             :         }
     306             : 
     307           4 :         AutoStableStringChars linearChars(cx);
     308           2 :         if (!linearChars.initTwoByte(cx, linearStr))
     309           0 :             return false;
     310             : 
     311           2 :         const char16_t* chars = linearChars.twoByteRange().begin().get();
     312           2 :         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
     313           2 :                                                   ? SourceBufferHolder::GiveOwnership
     314           2 :                                                   : SourceBufferHolder::NoOwnership;
     315           4 :         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
     316           4 :         JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
     317             :                                                          env, enclosing,
     318           2 :                                                          options, srcBuf);
     319           2 :         if (!compiled)
     320           0 :             return false;
     321             : 
     322           2 :         esg.setNewScript(compiled);
     323             :     }
     324             : 
     325             :     // Look up the newTarget from the frame iterator.
     326           2 :     Value newTargetVal = NullValue();
     327           4 :     return ExecuteKernel(cx, esg.script(), *env, newTargetVal,
     328           4 :                          NullFramePtr() /* evalInFrame */, vp.address());
     329             : }
     330             : 
     331             : bool
     332           0 : js::DirectEvalStringFromIon(JSContext* cx,
     333             :                             HandleObject env, HandleScript callerScript,
     334             :                             HandleValue newTargetValue, HandleString str,
     335             :                             jsbytecode* pc, MutableHandleValue vp)
     336             : {
     337           0 :     AssertInnerizedEnvironmentChain(cx, *env);
     338             : 
     339           0 :     Rooted<GlobalObject*> envGlobal(cx, &env->global());
     340           0 :     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
     341           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
     342           0 :         return false;
     343             :     }
     344             : 
     345             :     // ES5 15.1.2.1 steps 2-8.
     346             : 
     347           0 :     RootedLinearString linearStr(cx, str->ensureLinear(cx));
     348           0 :     if (!linearStr)
     349           0 :         return false;
     350             : 
     351           0 :     EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
     352           0 :     if (ejr != EvalJSON_NotJSON)
     353           0 :         return ejr == EvalJSON_Success;
     354             : 
     355           0 :     EvalScriptGuard esg(cx);
     356             : 
     357           0 :     esg.lookupInEvalCache(linearStr, callerScript, pc);
     358             : 
     359           0 :     if (!esg.foundScript()) {
     360           0 :         RootedScript maybeScript(cx);
     361             :         const char* filename;
     362             :         unsigned lineno;
     363             :         bool mutedErrors;
     364             :         uint32_t pcOffset;
     365           0 :         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
     366           0 :                                              &mutedErrors, CALLED_FROM_JSOP_EVAL);
     367             : 
     368           0 :         const char* introducerFilename = filename;
     369           0 :         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
     370           0 :             introducerFilename = maybeScript->scriptSource()->introducerFilename();
     371             : 
     372           0 :         RootedScope enclosing(cx, callerScript->innermostScope(pc));
     373             : 
     374           0 :         CompileOptions options(cx);
     375           0 :         options.setIsRunOnce(true)
     376           0 :                .setNoScriptRval(false)
     377           0 :                .setMutedErrors(mutedErrors)
     378           0 :                .maybeMakeStrictMode(IsStrictEvalPC(pc));
     379             : 
     380           0 :         if (introducerFilename) {
     381           0 :             options.setFileAndLine(filename, 1);
     382           0 :             options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
     383             :         } else {
     384           0 :             options.setFileAndLine("eval", 1);
     385           0 :             options.setIntroductionType("eval");
     386             :         }
     387             : 
     388           0 :         AutoStableStringChars linearChars(cx);
     389           0 :         if (!linearChars.initTwoByte(cx, linearStr))
     390           0 :             return false;
     391             : 
     392           0 :         const char16_t* chars = linearChars.twoByteRange().begin().get();
     393           0 :         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
     394           0 :                                                   ? SourceBufferHolder::GiveOwnership
     395           0 :                                                   : SourceBufferHolder::NoOwnership;
     396           0 :         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
     397           0 :         JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
     398             :                                                          env, enclosing,
     399           0 :                                                          options, srcBuf);
     400           0 :         if (!compiled)
     401           0 :             return false;
     402             : 
     403           0 :         esg.setNewScript(compiled);
     404             :     }
     405             : 
     406           0 :     return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
     407           0 :                          NullFramePtr() /* evalInFrame */, vp.address());
     408             : }
     409             : 
     410             : bool
     411           0 : js::IndirectEval(JSContext* cx, unsigned argc, Value* vp)
     412             : {
     413           0 :     CallArgs args = CallArgsFromVp(argc, vp);
     414             : 
     415           0 :     Rooted<GlobalObject*> global(cx, &args.callee().global());
     416           0 :     RootedObject globalLexical(cx, &global->lexicalEnvironment());
     417             : 
     418             :     // Note we'll just pass |undefined| here, then return it directly (or throw
     419             :     // if runtime codegen is disabled), if no argument is provided.
     420           0 :     return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(), globalLexical, nullptr,
     421           0 :                       args.rval());
     422             : }
     423             : 
     424             : bool
     425           2 : js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp)
     426             : {
     427             :     // Direct eval can assume it was called from an interpreted or baseline frame.
     428           4 :     ScriptFrameIter iter(cx);
     429           2 :     AbstractFramePtr caller = iter.abstractFramePtr();
     430             : 
     431           2 :     MOZ_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
     432             :                JSOp(*iter.pc()) == JSOP_STRICTEVAL ||
     433             :                JSOp(*iter.pc()) == JSOP_SPREADEVAL ||
     434             :                JSOp(*iter.pc()) == JSOP_STRICTSPREADEVAL);
     435           2 :     MOZ_ASSERT(caller.compartment() == caller.script()->compartment());
     436             : 
     437           4 :     RootedObject envChain(cx, caller.environmentChain());
     438           4 :     return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
     439             : }
     440             : 
     441             : bool
     442           0 : js::IsAnyBuiltinEval(JSFunction* fun)
     443             : {
     444           0 :     return fun->maybeNative() == IndirectEval;
     445             : }
     446             : 
     447             : JS_FRIEND_API(bool)
     448          44 : js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
     449             :                                   MutableHandleObject envArg)
     450             : {
     451          87 :     CHECK_REQUEST(cx);
     452          44 :     assertSameCompartment(cx, global);
     453          44 :     MOZ_ASSERT(global->is<GlobalObject>());
     454          44 :     MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
     455             : 
     456          87 :     RootedScript script(cx, scriptArg);
     457          87 :     Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
     458          44 :     if (script->compartment() != cx->compartment()) {
     459          44 :         script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
     460          44 :         if (!script)
     461           0 :             return false;
     462             : 
     463          44 :         Debugger::onNewScript(cx, script);
     464             :     }
     465             : 
     466          87 :     Rooted<EnvironmentObject*> env(cx, NonSyntacticVariablesObject::create(cx));
     467          44 :     if (!env)
     468           0 :         return false;
     469             : 
     470             :     // Unlike the non-syntactic scope chain API used by the subscript loader,
     471             :     // this API creates a fresh block scope each time.
     472          44 :     env = LexicalEnvironmentObject::createNonSyntactic(cx, env);
     473          44 :     if (!env)
     474           0 :         return false;
     475             : 
     476          87 :     RootedValue rval(cx);
     477         131 :     if (!ExecuteKernel(cx, script, *env, UndefinedValue(),
     478          87 :                        NullFramePtr() /* evalInFrame */, rval.address()))
     479             :     {
     480           0 :         return false;
     481             :     }
     482             : 
     483          43 :     envArg.set(env);
     484          43 :     return true;
     485             : }

Generated by: LCOV version 1.13