js/src/builtin/Eval.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     7 #include "builtin/Eval.h"
     9 #include "mozilla/HashFunctions.h"
    11 #include "jscntxt.h"
    12 #include "jsonparser.h"
    14 #include "frontend/BytecodeCompiler.h"
    15 #include "vm/GlobalObject.h"
    17 #include "vm/Interpreter-inl.h"
    19 using namespace js;
    21 using mozilla::AddToHash;
    22 using mozilla::HashString;
    24 // We should be able to assert this for *any* fp->scopeChain().
    25 static void
    26 AssertInnerizedScopeChain(JSContext *cx, JSObject &scopeobj)
    27 {
    28 #ifdef DEBUG
    29     RootedObject obj(cx);
    30     for (obj = &scopeobj; obj; obj = obj->enclosingScope()) {
    31         if (JSObjectOp op = obj->getClass()->ext.innerObject) {
    32             JS_ASSERT(op(cx, obj) == obj);
    33         }
    34     }
    35 #endif
    36 }
    38 static bool
    39 IsEvalCacheCandidate(JSScript *script)
    40 {
    41     // Make sure there are no inner objects which might use the wrong parent
    42     // and/or call scope by reusing the previous eval's script. Skip the
    43     // script's first object, which entrains the eval's scope.
    44     return script->savedCallerFun() &&
    45            !script->hasSingletons() &&
    46            script->objects()->length == 1 &&
    47            !script->hasRegexps();
    48 }
    50 /* static */ HashNumber
    51 EvalCacheHashPolicy::hash(const EvalCacheLookup &l)
    52 {
    53     return AddToHash(HashString(l.str->chars(), l.str->length()),
    54                      l.callerScript.get(),
    55                      l.version,
    56                      l.pc);
    57 }
    59 /* static */ bool
    60 EvalCacheHashPolicy::match(const EvalCacheEntry &cacheEntry, const EvalCacheLookup &l)
    61 {
    62     JSScript *script = cacheEntry.script;
    64     JS_ASSERT(IsEvalCacheCandidate(script));
    66     // Get the source string passed for safekeeping in the atom map
    67     // by the prior eval to frontend::CompileScript.
    68     JSAtom *keyStr = script->atoms[0];
    70     return EqualStrings(keyStr, l.str) &&
    71            cacheEntry.callerScript == l.callerScript &&
    72            script->getVersion() == l.version &&
    73            cacheEntry.pc == l.pc;
    74 }
    76 // There are two things we want to do with each script executed in EvalKernel:
    77 //  1. notify OldDebugAPI about script creation/destruction
    78 //  2. add the script to the eval cache when EvalKernel is finished
    79 //
    80 // NB: Although the eval cache keeps a script alive wrt to the JS engine, from
    81 // an OldDebugAPI  user's perspective, we want each eval() to create and
    82 // destroy a script. This hides implementation details and means we don't have
    83 // to deal with calls to JS_GetScriptObject for scripts in the eval cache.
    84 class EvalScriptGuard
    85 {
    86     JSContext *cx_;
    87     Rooted<JSScript*> script_;
    89     /* These fields are only valid if lookup_.str is non-nullptr. */
    90     EvalCacheLookup lookup_;
    91     EvalCache::AddPtr p_;
    93     Rooted<JSLinearString*> lookupStr_;
    95   public:
    96     EvalScriptGuard(JSContext *cx)
    97         : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
    99     ~EvalScriptGuard() {
   100         if (script_) {
   101             CallDestroyScriptHook(cx_->runtime()->defaultFreeOp(), script_);
   102             script_->cacheForEval();
   103             EvalCacheEntry cacheEntry = {script_, lookup_.callerScript, lookup_.pc};
   104             lookup_.str = lookupStr_;
   105             if (lookup_.str && IsEvalCacheCandidate(script_))
   106                 cx_->runtime()->evalCache.relookupOrAdd(p_, lookup_, cacheEntry);
   107         }
   108     }
   110     void lookupInEvalCache(JSLinearString *str, JSScript *callerScript, jsbytecode *pc)
   111     {
   112         lookupStr_ = str;
   113         lookup_.str = str;
   114         lookup_.callerScript = callerScript;
   115         lookup_.version = cx_->findVersion();
   116         lookup_.pc = pc;
   117         p_ = cx_->runtime()->evalCache.lookupForAdd(lookup_);
   118         if (p_) {
   119             script_ = p_->script;
   120             cx_->runtime()->evalCache.remove(p_);
   121             CallNewScriptHook(cx_, script_, NullPtr());
   122             script_->uncacheForEval();
   123         }
   124     }
   126     void setNewScript(JSScript *script) {
   127         // JSScript::initFromEmitter has already called js_CallNewScriptHook.
   128         JS_ASSERT(!script_ && script);
   129         script_ = script;
   130         script_->setActiveEval();
   131     }
   133     bool foundScript() {
   134         return !!script_;
   135     }
   137     HandleScript script() {
   138         JS_ASSERT(script_);
   139         return script_;
   140     }
   141 };
   143 enum EvalJSONResult {
   144     EvalJSON_Failure,
   145     EvalJSON_Success,
   146     EvalJSON_NotJSON
   147 };
   149 static EvalJSONResult
   150 TryEvalJSON(JSContext *cx, JSScript *callerScript,
   151             ConstTwoByteChars chars, size_t length, MutableHandleValue rval)
   152 {
   153     // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON.
   154     // Try the JSON parser first because it's much faster.  If the eval string
   155     // isn't JSON, JSON parsing will probably fail quickly, so little time
   156     // will be lost.
   157     //
   158     // Don't use the JSON parser if the caller is strict mode code, because in
   159     // strict mode object literals must not have repeated properties, and the
   160     // JSON parser cheerfully (and correctly) accepts them.  If you're parsing
   161     // JSON with eval and using strict mode, you deserve to be slow.
   162     if (length > 2 &&
   163         ((chars[0] == '[' && chars[length - 1] == ']') ||
   164         (chars[0] == '(' && chars[length - 1] == ')')) &&
   165         (!callerScript || !callerScript->strict()))
   166     {
   167         // Remarkably, JavaScript syntax is not a superset of JSON syntax:
   168         // strings in JavaScript cannot contain the Unicode line and paragraph
   169         // terminator characters U+2028 and U+2029, but strings in JSON can.
   170         // Rather than force the JSON parser to handle this quirk when used by
   171         // eval, we simply don't use the JSON parser when either character
   172         // appears in the provided string.  See bug 657367.
   173         for (const jschar *cp = &chars[1], *end = &chars[length - 2]; ; cp++) {
   174             if (*cp == 0x2028 || *cp == 0x2029)
   175                 break;
   177             if (cp == end) {
   178                 bool isArray = (chars[0] == '[');
   179                 JSONParser parser(cx, isArray ? chars : chars + 1U, isArray ? length : length - 2,
   180                                   JSONParser::NoError);
   181                 RootedValue tmp(cx);
   182                 if (!parser.parse(&tmp))
   183                     return EvalJSON_Failure;
   184                 if (tmp.isUndefined())
   185                     return EvalJSON_NotJSON;
   186                 rval.set(tmp);
   187                 return EvalJSON_Success;
   188             }
   189         }
   190     }
   191     return EvalJSON_NotJSON;
   192 }
   194 // Define subset of ExecuteType so that casting performs the injection.
   195 enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL };
   197 // Common code implementing direct and indirect eval.
   198 //
   199 // Evaluate call.argv[2], if it is a string, in the context of the given calling
   200 // frame, with the provided scope chain, with the semantics of either a direct
   201 // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
   202 // must be a global object.
   203 //
   204 // On success, store the completion value in call.rval and return true.
   205 static bool
   206 EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, AbstractFramePtr caller,
   207            HandleObject scopeobj, jsbytecode *pc)
   208 {
   209     JS_ASSERT((evalType == INDIRECT_EVAL) == !caller);
   210     JS_ASSERT((evalType == INDIRECT_EVAL) == !pc);
   211     JS_ASSERT_IF(evalType == INDIRECT_EVAL, scopeobj->is<GlobalObject>());
   212     AssertInnerizedScopeChain(cx, *scopeobj);
   214     Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
   215     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
   216         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
   217         return false;
   218     }
   220     // ES5 15.1.2.1 step 1.
   221     if (args.length() < 1) {
   222         args.rval().setUndefined();
   223         return true;
   224     }
   225     if (!args[0].isString()) {
   226         args.rval().set(args[0]);
   227         return true;
   228     }
   229     RootedString str(cx, args[0].toString());
   231     // ES5 15.1.2.1 steps 2-8.
   233     // Per ES5, indirect eval runs in the global scope. (eval is specified this
   234     // way so that the compiler can make assumptions about what bindings may or
   235     // may not exist in the current frame if it doesn't see 'eval'.)
   236     unsigned staticLevel;
   237     RootedValue thisv(cx);
   238     if (evalType == DIRECT_EVAL) {
   239         JS_ASSERT_IF(caller.isInterpreterFrame(), !caller.asInterpreterFrame()->runningInJit());
   240         staticLevel = caller.script()->staticLevel() + 1;
   242         // Direct calls to eval are supposed to see the caller's |this|. If we
   243         // haven't wrapped that yet, do so now, before we make a copy of it for
   244         // the eval code to use.
   245         if (!ComputeThis(cx, caller))
   246             return false;
   247         thisv = caller.thisValue();
   248     } else {
   249         JS_ASSERT(args.callee().global() == *scopeobj);
   250         staticLevel = 0;
   252         // Use the global as 'this', modulo outerization.
   253         JSObject *thisobj = JSObject::thisObject(cx, scopeobj);
   254         if (!thisobj)
   255             return false;
   256         thisv = ObjectValue(*thisobj);
   257     }
   259     Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
   260     if (!flatStr)
   261         return false;
   263     size_t length = flatStr->length();
   264     ConstTwoByteChars chars(flatStr->chars(), length);
   266     RootedScript callerScript(cx, caller ? caller.script() : nullptr);
   267     EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, args.rval());
   268     if (ejr != EvalJSON_NotJSON)
   269         return ejr == EvalJSON_Success;
   271     EvalScriptGuard esg(cx);
   273     if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
   274         esg.lookupInEvalCache(flatStr, callerScript, pc);
   276     if (!esg.foundScript()) {
   277         RootedScript maybeScript(cx);
   278         unsigned lineno;
   279         const char *filename;
   280         JSPrincipals *originPrincipals;
   281         uint32_t pcOffset;
   282         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
   283                                              &originPrincipals,
   284                                              evalType == DIRECT_EVAL
   285                                              ? CALLED_FROM_JSOP_EVAL
   286                                              : NOT_CALLED_FROM_JSOP_EVAL);
   288         const char *introducerFilename = filename;
   289         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
   290             introducerFilename = maybeScript->scriptSource()->introducerFilename();
   292         CompileOptions options(cx);
   293         options.setFileAndLine(filename, 1)
   294                .setCompileAndGo(true)
   295                .setForEval(true)
   296                .setNoScriptRval(false)
   297                .setOriginPrincipals(originPrincipals)
   298                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
   299         SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership);
   300         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
   301                                                      scopeobj, callerScript, options,
   302                                                      srcBuf, flatStr, staticLevel);
   303         if (!compiled)
   304             return false;
   306         esg.setNewScript(compiled);
   307     }
   309     return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
   310                          NullFramePtr() /* evalInFrame */, args.rval().address());
   311 }
   313 bool
   314 js::DirectEvalStringFromIon(JSContext *cx,
   315                             HandleObject scopeobj, HandleScript callerScript,
   316                             HandleValue thisValue, HandleString str,
   317                             jsbytecode *pc, MutableHandleValue vp)
   318 {
   319     AssertInnerizedScopeChain(cx, *scopeobj);
   321     Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
   322     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
   323         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
   324         return false;
   325     }
   327     // ES5 15.1.2.1 steps 2-8.
   329     unsigned staticLevel = callerScript->staticLevel() + 1;
   331     Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
   332     if (!flatStr)
   333         return false;
   335     size_t length = flatStr->length();
   336     ConstTwoByteChars chars(flatStr->chars(), length);
   338     EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, vp);
   339     if (ejr != EvalJSON_NotJSON)
   340         return ejr == EvalJSON_Success;
   342     EvalScriptGuard esg(cx);
   344     esg.lookupInEvalCache(flatStr, callerScript, pc);
   346     if (!esg.foundScript()) {
   347         RootedScript maybeScript(cx);
   348         const char *filename;
   349         unsigned lineno;
   350         JSPrincipals *originPrincipals;
   351         uint32_t pcOffset;
   352         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
   353                                               &originPrincipals, CALLED_FROM_JSOP_EVAL);
   355         const char *introducerFilename = filename;
   356         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
   357             introducerFilename = maybeScript->scriptSource()->introducerFilename();
   359         CompileOptions options(cx);
   360         options.setFileAndLine(filename, 1)
   361                .setCompileAndGo(true)
   362                .setForEval(true)
   363                .setNoScriptRval(false)
   364                .setOriginPrincipals(originPrincipals)
   365                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
   366         SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership);
   367         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
   368                                                      scopeobj, callerScript, options,
   369                                                      srcBuf, flatStr, staticLevel);
   370         if (!compiled)
   371             return false;
   373         esg.setNewScript(compiled);
   374     }
   376     // Primitive 'this' values should have been filtered out by Ion. If boxed,
   377     // the calling frame cannot be updated to store the new object.
   378     JS_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull());
   380     return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL),
   381                          NullFramePtr() /* evalInFrame */, vp.address());
   382 }
   384 bool
   385 js::DirectEvalValueFromIon(JSContext *cx,
   386                            HandleObject scopeobj, HandleScript callerScript,
   387                            HandleValue thisValue, HandleValue evalArg,
   388                            jsbytecode *pc, MutableHandleValue vp)
   389 {
   390     // Act as identity on non-strings per ES5 15.1.2.1 step 1.
   391     if (!evalArg.isString()) {
   392         vp.set(evalArg);
   393         return true;
   394     }
   396     RootedString string(cx, evalArg.toString());
   397     return DirectEvalStringFromIon(cx, scopeobj, callerScript, thisValue, string, pc, vp);
   398 }
   400 bool
   401 js::IndirectEval(JSContext *cx, unsigned argc, Value *vp)
   402 {
   403     CallArgs args = CallArgsFromVp(argc, vp);
   404     Rooted<GlobalObject*> global(cx, &args.callee().global());
   405     return EvalKernel(cx, args, INDIRECT_EVAL, NullFramePtr(), global, nullptr);
   406 }
   408 bool
   409 js::DirectEval(JSContext *cx, const CallArgs &args)
   410 {
   411     // Direct eval can assume it was called from an interpreted or baseline frame.
   412     ScriptFrameIter iter(cx);
   413     AbstractFramePtr caller = iter.abstractFramePtr();
   415     JS_ASSERT(caller.scopeChain()->global().valueIsEval(args.calleev()));
   416     JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
   417               JSOp(*iter.pc()) == JSOP_SPREADEVAL);
   418     JS_ASSERT_IF(caller.isFunctionFrame(),
   419                  caller.compartment() == caller.callee()->compartment());
   421     RootedObject scopeChain(cx, caller.scopeChain());
   422     return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc());
   423 }
   425 bool
   426 js::IsAnyBuiltinEval(JSFunction *fun)
   427 {
   428     return fun->maybeNative() == IndirectEval;
   429 }

mercurial