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 +}