js/src/builtin/Eval.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/builtin/Eval.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,429 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "builtin/Eval.h"
    1.11 +
    1.12 +#include "mozilla/HashFunctions.h"
    1.13 +
    1.14 +#include "jscntxt.h"
    1.15 +#include "jsonparser.h"
    1.16 +
    1.17 +#include "frontend/BytecodeCompiler.h"
    1.18 +#include "vm/GlobalObject.h"
    1.19 +
    1.20 +#include "vm/Interpreter-inl.h"
    1.21 +
    1.22 +using namespace js;
    1.23 +
    1.24 +using mozilla::AddToHash;
    1.25 +using mozilla::HashString;
    1.26 +
    1.27 +// We should be able to assert this for *any* fp->scopeChain().
    1.28 +static void
    1.29 +AssertInnerizedScopeChain(JSContext *cx, JSObject &scopeobj)
    1.30 +{
    1.31 +#ifdef DEBUG
    1.32 +    RootedObject obj(cx);
    1.33 +    for (obj = &scopeobj; obj; obj = obj->enclosingScope()) {
    1.34 +        if (JSObjectOp op = obj->getClass()->ext.innerObject) {
    1.35 +            JS_ASSERT(op(cx, obj) == obj);
    1.36 +        }
    1.37 +    }
    1.38 +#endif
    1.39 +}
    1.40 +
    1.41 +static bool
    1.42 +IsEvalCacheCandidate(JSScript *script)
    1.43 +{
    1.44 +    // Make sure there are no inner objects which might use the wrong parent
    1.45 +    // and/or call scope by reusing the previous eval's script. Skip the
    1.46 +    // script's first object, which entrains the eval's scope.
    1.47 +    return script->savedCallerFun() &&
    1.48 +           !script->hasSingletons() &&
    1.49 +           script->objects()->length == 1 &&
    1.50 +           !script->hasRegexps();
    1.51 +}
    1.52 +
    1.53 +/* static */ HashNumber
    1.54 +EvalCacheHashPolicy::hash(const EvalCacheLookup &l)
    1.55 +{
    1.56 +    return AddToHash(HashString(l.str->chars(), l.str->length()),
    1.57 +                     l.callerScript.get(),
    1.58 +                     l.version,
    1.59 +                     l.pc);
    1.60 +}
    1.61 +
    1.62 +/* static */ bool
    1.63 +EvalCacheHashPolicy::match(const EvalCacheEntry &cacheEntry, const EvalCacheLookup &l)
    1.64 +{
    1.65 +    JSScript *script = cacheEntry.script;
    1.66 +
    1.67 +    JS_ASSERT(IsEvalCacheCandidate(script));
    1.68 +
    1.69 +    // Get the source string passed for safekeeping in the atom map
    1.70 +    // by the prior eval to frontend::CompileScript.
    1.71 +    JSAtom *keyStr = script->atoms[0];
    1.72 +
    1.73 +    return EqualStrings(keyStr, l.str) &&
    1.74 +           cacheEntry.callerScript == l.callerScript &&
    1.75 +           script->getVersion() == l.version &&
    1.76 +           cacheEntry.pc == l.pc;
    1.77 +}
    1.78 +
    1.79 +// There are two things we want to do with each script executed in EvalKernel:
    1.80 +//  1. notify OldDebugAPI about script creation/destruction
    1.81 +//  2. add the script to the eval cache when EvalKernel is finished
    1.82 +//
    1.83 +// NB: Although the eval cache keeps a script alive wrt to the JS engine, from
    1.84 +// an OldDebugAPI  user's perspective, we want each eval() to create and
    1.85 +// destroy a script. This hides implementation details and means we don't have
    1.86 +// to deal with calls to JS_GetScriptObject for scripts in the eval cache.
    1.87 +class EvalScriptGuard
    1.88 +{
    1.89 +    JSContext *cx_;
    1.90 +    Rooted<JSScript*> script_;
    1.91 +
    1.92 +    /* These fields are only valid if lookup_.str is non-nullptr. */
    1.93 +    EvalCacheLookup lookup_;
    1.94 +    EvalCache::AddPtr p_;
    1.95 +
    1.96 +    Rooted<JSLinearString*> lookupStr_;
    1.97 +
    1.98 +  public:
    1.99 +    EvalScriptGuard(JSContext *cx)
   1.100 +        : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
   1.101 +
   1.102 +    ~EvalScriptGuard() {
   1.103 +        if (script_) {
   1.104 +            CallDestroyScriptHook(cx_->runtime()->defaultFreeOp(), script_);
   1.105 +            script_->cacheForEval();
   1.106 +            EvalCacheEntry cacheEntry = {script_, lookup_.callerScript, lookup_.pc};
   1.107 +            lookup_.str = lookupStr_;
   1.108 +            if (lookup_.str && IsEvalCacheCandidate(script_))
   1.109 +                cx_->runtime()->evalCache.relookupOrAdd(p_, lookup_, cacheEntry);
   1.110 +        }
   1.111 +    }
   1.112 +
   1.113 +    void lookupInEvalCache(JSLinearString *str, JSScript *callerScript, jsbytecode *pc)
   1.114 +    {
   1.115 +        lookupStr_ = str;
   1.116 +        lookup_.str = str;
   1.117 +        lookup_.callerScript = callerScript;
   1.118 +        lookup_.version = cx_->findVersion();
   1.119 +        lookup_.pc = pc;
   1.120 +        p_ = cx_->runtime()->evalCache.lookupForAdd(lookup_);
   1.121 +        if (p_) {
   1.122 +            script_ = p_->script;
   1.123 +            cx_->runtime()->evalCache.remove(p_);
   1.124 +            CallNewScriptHook(cx_, script_, NullPtr());
   1.125 +            script_->uncacheForEval();
   1.126 +        }
   1.127 +    }
   1.128 +
   1.129 +    void setNewScript(JSScript *script) {
   1.130 +        // JSScript::initFromEmitter has already called js_CallNewScriptHook.
   1.131 +        JS_ASSERT(!script_ && script);
   1.132 +        script_ = script;
   1.133 +        script_->setActiveEval();
   1.134 +    }
   1.135 +
   1.136 +    bool foundScript() {
   1.137 +        return !!script_;
   1.138 +    }
   1.139 +
   1.140 +    HandleScript script() {
   1.141 +        JS_ASSERT(script_);
   1.142 +        return script_;
   1.143 +    }
   1.144 +};
   1.145 +
   1.146 +enum EvalJSONResult {
   1.147 +    EvalJSON_Failure,
   1.148 +    EvalJSON_Success,
   1.149 +    EvalJSON_NotJSON
   1.150 +};
   1.151 +
   1.152 +static EvalJSONResult
   1.153 +TryEvalJSON(JSContext *cx, JSScript *callerScript,
   1.154 +            ConstTwoByteChars chars, size_t length, MutableHandleValue rval)
   1.155 +{
   1.156 +    // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON.
   1.157 +    // Try the JSON parser first because it's much faster.  If the eval string
   1.158 +    // isn't JSON, JSON parsing will probably fail quickly, so little time
   1.159 +    // will be lost.
   1.160 +    //
   1.161 +    // Don't use the JSON parser if the caller is strict mode code, because in
   1.162 +    // strict mode object literals must not have repeated properties, and the
   1.163 +    // JSON parser cheerfully (and correctly) accepts them.  If you're parsing
   1.164 +    // JSON with eval and using strict mode, you deserve to be slow.
   1.165 +    if (length > 2 &&
   1.166 +        ((chars[0] == '[' && chars[length - 1] == ']') ||
   1.167 +        (chars[0] == '(' && chars[length - 1] == ')')) &&
   1.168 +        (!callerScript || !callerScript->strict()))
   1.169 +    {
   1.170 +        // Remarkably, JavaScript syntax is not a superset of JSON syntax:
   1.171 +        // strings in JavaScript cannot contain the Unicode line and paragraph
   1.172 +        // terminator characters U+2028 and U+2029, but strings in JSON can.
   1.173 +        // Rather than force the JSON parser to handle this quirk when used by
   1.174 +        // eval, we simply don't use the JSON parser when either character
   1.175 +        // appears in the provided string.  See bug 657367.
   1.176 +        for (const jschar *cp = &chars[1], *end = &chars[length - 2]; ; cp++) {
   1.177 +            if (*cp == 0x2028 || *cp == 0x2029)
   1.178 +                break;
   1.179 +
   1.180 +            if (cp == end) {
   1.181 +                bool isArray = (chars[0] == '[');
   1.182 +                JSONParser parser(cx, isArray ? chars : chars + 1U, isArray ? length : length - 2,
   1.183 +                                  JSONParser::NoError);
   1.184 +                RootedValue tmp(cx);
   1.185 +                if (!parser.parse(&tmp))
   1.186 +                    return EvalJSON_Failure;
   1.187 +                if (tmp.isUndefined())
   1.188 +                    return EvalJSON_NotJSON;
   1.189 +                rval.set(tmp);
   1.190 +                return EvalJSON_Success;
   1.191 +            }
   1.192 +        }
   1.193 +    }
   1.194 +    return EvalJSON_NotJSON;
   1.195 +}
   1.196 +
   1.197 +// Define subset of ExecuteType so that casting performs the injection.
   1.198 +enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL };
   1.199 +
   1.200 +// Common code implementing direct and indirect eval.
   1.201 +//
   1.202 +// Evaluate call.argv[2], if it is a string, in the context of the given calling
   1.203 +// frame, with the provided scope chain, with the semantics of either a direct
   1.204 +// or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
   1.205 +// must be a global object.
   1.206 +//
   1.207 +// On success, store the completion value in call.rval and return true.
   1.208 +static bool
   1.209 +EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, AbstractFramePtr caller,
   1.210 +           HandleObject scopeobj, jsbytecode *pc)
   1.211 +{
   1.212 +    JS_ASSERT((evalType == INDIRECT_EVAL) == !caller);
   1.213 +    JS_ASSERT((evalType == INDIRECT_EVAL) == !pc);
   1.214 +    JS_ASSERT_IF(evalType == INDIRECT_EVAL, scopeobj->is<GlobalObject>());
   1.215 +    AssertInnerizedScopeChain(cx, *scopeobj);
   1.216 +
   1.217 +    Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
   1.218 +    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
   1.219 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
   1.220 +        return false;
   1.221 +    }
   1.222 +
   1.223 +    // ES5 15.1.2.1 step 1.
   1.224 +    if (args.length() < 1) {
   1.225 +        args.rval().setUndefined();
   1.226 +        return true;
   1.227 +    }
   1.228 +    if (!args[0].isString()) {
   1.229 +        args.rval().set(args[0]);
   1.230 +        return true;
   1.231 +    }
   1.232 +    RootedString str(cx, args[0].toString());
   1.233 +
   1.234 +    // ES5 15.1.2.1 steps 2-8.
   1.235 +
   1.236 +    // Per ES5, indirect eval runs in the global scope. (eval is specified this
   1.237 +    // way so that the compiler can make assumptions about what bindings may or
   1.238 +    // may not exist in the current frame if it doesn't see 'eval'.)
   1.239 +    unsigned staticLevel;
   1.240 +    RootedValue thisv(cx);
   1.241 +    if (evalType == DIRECT_EVAL) {
   1.242 +        JS_ASSERT_IF(caller.isInterpreterFrame(), !caller.asInterpreterFrame()->runningInJit());
   1.243 +        staticLevel = caller.script()->staticLevel() + 1;
   1.244 +
   1.245 +        // Direct calls to eval are supposed to see the caller's |this|. If we
   1.246 +        // haven't wrapped that yet, do so now, before we make a copy of it for
   1.247 +        // the eval code to use.
   1.248 +        if (!ComputeThis(cx, caller))
   1.249 +            return false;
   1.250 +        thisv = caller.thisValue();
   1.251 +    } else {
   1.252 +        JS_ASSERT(args.callee().global() == *scopeobj);
   1.253 +        staticLevel = 0;
   1.254 +
   1.255 +        // Use the global as 'this', modulo outerization.
   1.256 +        JSObject *thisobj = JSObject::thisObject(cx, scopeobj);
   1.257 +        if (!thisobj)
   1.258 +            return false;
   1.259 +        thisv = ObjectValue(*thisobj);
   1.260 +    }
   1.261 +
   1.262 +    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
   1.263 +    if (!flatStr)
   1.264 +        return false;
   1.265 +
   1.266 +    size_t length = flatStr->length();
   1.267 +    ConstTwoByteChars chars(flatStr->chars(), length);
   1.268 +
   1.269 +    RootedScript callerScript(cx, caller ? caller.script() : nullptr);
   1.270 +    EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, args.rval());
   1.271 +    if (ejr != EvalJSON_NotJSON)
   1.272 +        return ejr == EvalJSON_Success;
   1.273 +
   1.274 +    EvalScriptGuard esg(cx);
   1.275 +
   1.276 +    if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
   1.277 +        esg.lookupInEvalCache(flatStr, callerScript, pc);
   1.278 +
   1.279 +    if (!esg.foundScript()) {
   1.280 +        RootedScript maybeScript(cx);
   1.281 +        unsigned lineno;
   1.282 +        const char *filename;
   1.283 +        JSPrincipals *originPrincipals;
   1.284 +        uint32_t pcOffset;
   1.285 +        DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
   1.286 +                                             &originPrincipals,
   1.287 +                                             evalType == DIRECT_EVAL
   1.288 +                                             ? CALLED_FROM_JSOP_EVAL
   1.289 +                                             : NOT_CALLED_FROM_JSOP_EVAL);
   1.290 +
   1.291 +        const char *introducerFilename = filename;
   1.292 +        if (maybeScript && maybeScript->scriptSource()->introducerFilename())
   1.293 +            introducerFilename = maybeScript->scriptSource()->introducerFilename();
   1.294 +
   1.295 +        CompileOptions options(cx);
   1.296 +        options.setFileAndLine(filename, 1)
   1.297 +               .setCompileAndGo(true)
   1.298 +               .setForEval(true)
   1.299 +               .setNoScriptRval(false)
   1.300 +               .setOriginPrincipals(originPrincipals)
   1.301 +               .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
   1.302 +        SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership);
   1.303 +        JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
   1.304 +                                                     scopeobj, callerScript, options,
   1.305 +                                                     srcBuf, flatStr, staticLevel);
   1.306 +        if (!compiled)
   1.307 +            return false;
   1.308 +
   1.309 +        esg.setNewScript(compiled);
   1.310 +    }
   1.311 +
   1.312 +    return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
   1.313 +                         NullFramePtr() /* evalInFrame */, args.rval().address());
   1.314 +}
   1.315 +
   1.316 +bool
   1.317 +js::DirectEvalStringFromIon(JSContext *cx,
   1.318 +                            HandleObject scopeobj, HandleScript callerScript,
   1.319 +                            HandleValue thisValue, HandleString str,
   1.320 +                            jsbytecode *pc, MutableHandleValue vp)
   1.321 +{
   1.322 +    AssertInnerizedScopeChain(cx, *scopeobj);
   1.323 +
   1.324 +    Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
   1.325 +    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
   1.326 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
   1.327 +        return false;
   1.328 +    }
   1.329 +
   1.330 +    // ES5 15.1.2.1 steps 2-8.
   1.331 +
   1.332 +    unsigned staticLevel = callerScript->staticLevel() + 1;
   1.333 +
   1.334 +    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
   1.335 +    if (!flatStr)
   1.336 +        return false;
   1.337 +
   1.338 +    size_t length = flatStr->length();
   1.339 +    ConstTwoByteChars chars(flatStr->chars(), length);
   1.340 +
   1.341 +    EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, vp);
   1.342 +    if (ejr != EvalJSON_NotJSON)
   1.343 +        return ejr == EvalJSON_Success;
   1.344 +
   1.345 +    EvalScriptGuard esg(cx);
   1.346 +
   1.347 +    esg.lookupInEvalCache(flatStr, callerScript, pc);
   1.348 +
   1.349 +    if (!esg.foundScript()) {
   1.350 +        RootedScript maybeScript(cx);
   1.351 +        const char *filename;
   1.352 +        unsigned lineno;
   1.353 +        JSPrincipals *originPrincipals;
   1.354 +        uint32_t pcOffset;
   1.355 +        DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
   1.356 +                                              &originPrincipals, CALLED_FROM_JSOP_EVAL);
   1.357 +
   1.358 +        const char *introducerFilename = filename;
   1.359 +        if (maybeScript && maybeScript->scriptSource()->introducerFilename())
   1.360 +            introducerFilename = maybeScript->scriptSource()->introducerFilename();
   1.361 +
   1.362 +        CompileOptions options(cx);
   1.363 +        options.setFileAndLine(filename, 1)
   1.364 +               .setCompileAndGo(true)
   1.365 +               .setForEval(true)
   1.366 +               .setNoScriptRval(false)
   1.367 +               .setOriginPrincipals(originPrincipals)
   1.368 +               .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
   1.369 +        SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership);
   1.370 +        JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
   1.371 +                                                     scopeobj, callerScript, options,
   1.372 +                                                     srcBuf, flatStr, staticLevel);
   1.373 +        if (!compiled)
   1.374 +            return false;
   1.375 +
   1.376 +        esg.setNewScript(compiled);
   1.377 +    }
   1.378 +
   1.379 +    // Primitive 'this' values should have been filtered out by Ion. If boxed,
   1.380 +    // the calling frame cannot be updated to store the new object.
   1.381 +    JS_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull());
   1.382 +
   1.383 +    return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL),
   1.384 +                         NullFramePtr() /* evalInFrame */, vp.address());
   1.385 +}
   1.386 +
   1.387 +bool
   1.388 +js::DirectEvalValueFromIon(JSContext *cx,
   1.389 +                           HandleObject scopeobj, HandleScript callerScript,
   1.390 +                           HandleValue thisValue, HandleValue evalArg,
   1.391 +                           jsbytecode *pc, MutableHandleValue vp)
   1.392 +{
   1.393 +    // Act as identity on non-strings per ES5 15.1.2.1 step 1.
   1.394 +    if (!evalArg.isString()) {
   1.395 +        vp.set(evalArg);
   1.396 +        return true;
   1.397 +    }
   1.398 +
   1.399 +    RootedString string(cx, evalArg.toString());
   1.400 +    return DirectEvalStringFromIon(cx, scopeobj, callerScript, thisValue, string, pc, vp);
   1.401 +}
   1.402 +
   1.403 +bool
   1.404 +js::IndirectEval(JSContext *cx, unsigned argc, Value *vp)
   1.405 +{
   1.406 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.407 +    Rooted<GlobalObject*> global(cx, &args.callee().global());
   1.408 +    return EvalKernel(cx, args, INDIRECT_EVAL, NullFramePtr(), global, nullptr);
   1.409 +}
   1.410 +
   1.411 +bool
   1.412 +js::DirectEval(JSContext *cx, const CallArgs &args)
   1.413 +{
   1.414 +    // Direct eval can assume it was called from an interpreted or baseline frame.
   1.415 +    ScriptFrameIter iter(cx);
   1.416 +    AbstractFramePtr caller = iter.abstractFramePtr();
   1.417 +
   1.418 +    JS_ASSERT(caller.scopeChain()->global().valueIsEval(args.calleev()));
   1.419 +    JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
   1.420 +              JSOp(*iter.pc()) == JSOP_SPREADEVAL);
   1.421 +    JS_ASSERT_IF(caller.isFunctionFrame(),
   1.422 +                 caller.compartment() == caller.callee()->compartment());
   1.423 +
   1.424 +    RootedObject scopeChain(cx, caller.scopeChain());
   1.425 +    return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc());
   1.426 +}
   1.427 +
   1.428 +bool
   1.429 +js::IsAnyBuiltinEval(JSFunction *fun)
   1.430 +{
   1.431 +    return fun->maybeNative() == IndirectEval;
   1.432 +}

mercurial