js/src/builtin/Eval.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial