Eval.cpp (18961B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 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 "frontend/BytecodeCompiler.h" // frontend::CompileEvalScript 13 #include "gc/HashUtil.h" 14 #include "js/CompilationAndEvaluation.h" 15 #include "js/EnvironmentChain.h" // JS::EnvironmentChain 16 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 17 #include "js/friend/JSMEnvironment.h" // JS::NewJSMEnvironment, JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::IsJSMEnvironment 18 #include "js/friend/WindowProxy.h" // js::IsWindowProxy 19 #include "js/SourceText.h" 20 #include "js/StableStringChars.h" 21 #include "vm/EnvironmentObject.h" 22 #include "vm/FrameIter.h" 23 #include "vm/GlobalObject.h" 24 #include "vm/Interpreter.h" 25 #include "vm/JSContext.h" 26 #include "vm/JSONParser.h" 27 28 #include "gc/Marking-inl.h" 29 #include "vm/EnvironmentObject-inl.h" 30 #include "vm/JSContext-inl.h" 31 #include "vm/Stack-inl.h" 32 33 using namespace js; 34 35 using mozilla::AddToHash; 36 37 using JS::AutoCheckCannotGC; 38 using JS::AutoStableStringChars; 39 using JS::CompileOptions; 40 using JS::SourceText; 41 42 // We should be able to assert this for *any* fp->environmentChain(). 43 static void AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env) { 44 #ifdef DEBUG 45 RootedObject obj(cx); 46 for (obj = &env; obj; obj = obj->enclosingEnvironment()) { 47 MOZ_ASSERT(!IsWindowProxy(obj)); 48 } 49 #endif 50 } 51 52 static bool IsEvalCacheCandidate(JSScript* script) { 53 if (!script->isDirectEvalInFunction()) { 54 return false; 55 } 56 57 // Make sure there are no inner objects (which may be used directly by script 58 // and clobbered) or inner functions (which may have wrong scope). 59 for (JS::GCCellPtr gcThing : script->gcthings()) { 60 if (gcThing.is<JSObject>()) { 61 return false; 62 } 63 } 64 65 return true; 66 } 67 68 /* static */ 69 HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) { 70 HashNumber hash = HashStringChars(l.str); 71 return AddToHash(hash, l.callerScript, l.pc); 72 } 73 74 /* static */ 75 bool EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry, 76 const EvalCacheLookup& l) { 77 MOZ_ASSERT(IsEvalCacheCandidate(cacheEntry.script)); 78 79 return EqualStrings(cacheEntry.str, l.str) && 80 cacheEntry.callerScript == l.callerScript && cacheEntry.pc == l.pc; 81 } 82 83 void EvalCacheLookup::trace(JSTracer* trc) { 84 TraceNullableRoot(trc, &str, "EvalCacheLookup::str"); 85 TraceNullableRoot(trc, &callerScript, "EvalCacheLookup::callerScript"); 86 } 87 88 // Add the script to the eval cache when EvalKernel is finished 89 class EvalScriptGuard { 90 JSContext* cx_; 91 Rooted<JSScript*> script_; 92 93 /* These fields are only valid if lookup_.str is non-nullptr. */ 94 Rooted<EvalCacheLookup> lookup_; 95 mozilla::Maybe<DependentAddPtr<EvalCache>> p_; 96 97 Rooted<JSLinearString*> lookupStr_; 98 99 public: 100 explicit EvalScriptGuard(JSContext* cx) 101 : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {} 102 103 ~EvalScriptGuard() { 104 if (script_ && !cx_->isExceptionPending()) { 105 script_->cacheForEval(); 106 EvalCacheLookup& lookup = lookup_.get(); 107 EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup.callerScript, 108 lookup.pc}; 109 lookup.str = lookupStr_; 110 if (lookup.str && IsEvalCacheCandidate(script_)) { 111 // Ignore failure to add cache entry. 112 if (!p_->add(cx_, cx_->caches().evalCache, lookup, cacheEntry)) { 113 cx_->recoverFromOutOfMemory(); 114 } 115 } 116 } 117 } 118 119 void lookupInEvalCache(JSLinearString* str, JSScript* callerScript, 120 jsbytecode* pc) { 121 lookupStr_ = str; 122 EvalCacheLookup& lookup = lookup_.get(); 123 lookup.str = str; 124 lookup.callerScript = callerScript; 125 lookup.pc = pc; 126 p_.emplace(cx_, cx_->caches().evalCache, lookup); 127 if (*p_) { 128 script_ = (*p_)->script; 129 p_->remove(cx_, cx_->caches().evalCache, lookup); 130 } 131 } 132 133 void setNewScript(JSScript* script) { 134 // JSScript::fullyInitFromStencil has already called js_CallNewScriptHook. 135 MOZ_ASSERT(!script_ && script); 136 script_ = script; 137 } 138 139 bool foundScript() { return !!script_; } 140 141 HandleScript script() { 142 MOZ_ASSERT(script_); 143 return script_; 144 } 145 }; 146 147 enum class EvalJSONResult { Failure, Success, NotJSON }; 148 149 template <typename CharT> 150 static bool EvalStringMightBeJSON(const mozilla::Range<const CharT> chars) { 151 // If the eval string starts with '(' or '[' and ends with ')' or ']', it 152 // may be JSON. Try the JSON parser first because it's much faster. If 153 // the eval string isn't JSON, JSON parsing will probably fail quickly, so 154 // little time will be lost. 155 size_t length = chars.length(); 156 if (length < 2) { 157 return false; 158 } 159 160 // It used to be that strings in JavaScript forbid U+2028 LINE SEPARATOR 161 // and U+2029 PARAGRAPH SEPARATOR, so something like 162 // 163 // eval("['" + "\u2028" + "']"); 164 // 165 // i.e. an array containing a string with a line separator in it, *would* 166 // be JSON but *would not* be valid JavaScript. Handing such a string to 167 // the JSON parser would then fail to recognize a syntax error. As of 168 // <https://tc39.github.io/proposal-json-superset/> JavaScript strings may 169 // contain these two code points, so it's safe to JSON-parse eval strings 170 // that contain them. 171 172 CharT first = chars[0], last = chars[length - 1]; 173 return (first == '[' && last == ']') || (first == '(' && last == ')'); 174 } 175 176 template <typename CharT> 177 static EvalJSONResult ParseEvalStringAsJSON( 178 JSContext* cx, const mozilla::Range<const CharT> chars, 179 MutableHandleValue rval) { 180 size_t len = chars.length(); 181 MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') || 182 (chars[0] == '[' && chars[len - 1] == ']')); 183 184 auto jsonChars = (chars[0] == '[') ? chars 185 : mozilla::Range<const CharT>( 186 chars.begin().get() + 1U, len - 2); 187 188 Rooted<JSONParser<CharT>> parser( 189 cx, cx, jsonChars, JSONParser<CharT>::ParseType::AttemptForEval); 190 if (!parser.parse(rval)) { 191 return EvalJSONResult::Failure; 192 } 193 194 return rval.isUndefined() ? EvalJSONResult::NotJSON : EvalJSONResult::Success; 195 } 196 197 static EvalJSONResult TryEvalJSON(JSContext* cx, JSLinearString* str, 198 MutableHandleValue rval) { 199 if (str->hasLatin1Chars()) { 200 AutoCheckCannotGC nogc; 201 if (!EvalStringMightBeJSON(str->latin1Range(nogc))) { 202 return EvalJSONResult::NotJSON; 203 } 204 } else { 205 AutoCheckCannotGC nogc; 206 if (!EvalStringMightBeJSON(str->twoByteRange(nogc))) { 207 return EvalJSONResult::NotJSON; 208 } 209 } 210 211 AutoStableStringChars linearChars(cx); 212 if (!linearChars.init(cx, str)) { 213 return EvalJSONResult::Failure; 214 } 215 216 return linearChars.isLatin1() 217 ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval) 218 : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval); 219 } 220 221 enum EvalType { DIRECT_EVAL, INDIRECT_EVAL }; 222 223 // 18.2.1.1 PerformEval 224 // 225 // Common code implementing direct and indirect eval. 226 // 227 // Evaluate v, if it is a string, in the context of the given calling 228 // frame, with the provided scope chain, with the semantics of either a direct 229 // or indirect eval (see ES5 10.4.2). If this is an indirect eval, env 230 // must be the global lexical environment. 231 // 232 // On success, store the completion value in call.rval and return true. 233 static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType, 234 AbstractFramePtr caller, HandleObject env, 235 jsbytecode* pc, MutableHandleValue vp) { 236 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller); 237 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc); 238 MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, 239 env->is<GlobalLexicalEnvironmentObject>()); 240 AssertInnerizedEnvironmentChain(cx, *env); 241 242 // "Dynamic Code Brand Checks" adds support for Object values. 243 // https://tc39.es/proposal-dynamic-code-brand-checks/#sec-performeval 244 // Steps 2-4. 245 RootedString str(cx); 246 if (v.isString()) { 247 str = v.toString(); 248 } else if (v.isObject()) { 249 RootedObject obj(cx, &v.toObject()); 250 if (!cx->getCodeForEval(obj, &str)) { 251 return false; 252 } 253 } 254 if (!str) { 255 vp.set(v); 256 return true; 257 } 258 259 // Steps 6-8. 260 JS::RootedVector<JSString*> parameterStrings(cx); 261 JS::RootedVector<Value> parameterArgs(cx); 262 bool canCompileStrings = cx->bypassCSPForDebugger; 263 264 if (!canCompileStrings && 265 !cx->isRuntimeCodeGenEnabled( 266 JS::RuntimeCode::JS, str, 267 evalType == DIRECT_EVAL ? JS::CompilationType::DirectEval 268 : JS::CompilationType::IndirectEval, 269 parameterStrings, str, parameterArgs, v, &canCompileStrings)) { 270 return false; 271 } 272 if (!canCompileStrings) { 273 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 274 JSMSG_CSP_BLOCKED_EVAL); 275 return false; 276 } 277 278 // Step 9 ff. 279 280 // Per ES5, indirect eval runs in the global scope. (eval is specified this 281 // way so that the compiler can make assumptions about what bindings may or 282 // may not exist in the current frame if it doesn't see 'eval'.) 283 MOZ_ASSERT_IF( 284 evalType != DIRECT_EVAL, 285 cx->global() == &env->as<GlobalLexicalEnvironmentObject>().global()); 286 287 Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx)); 288 if (!linearStr) { 289 return false; 290 } 291 292 RootedScript callerScript(cx, caller ? caller.script() : nullptr); 293 EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp); 294 if (ejr != EvalJSONResult::NotJSON) { 295 return ejr == EvalJSONResult::Success; 296 } 297 298 EvalScriptGuard esg(cx); 299 300 if (evalType == DIRECT_EVAL && caller.isFunctionFrame()) { 301 esg.lookupInEvalCache(linearStr, callerScript, pc); 302 } 303 304 if (!esg.foundScript()) { 305 RootedScript maybeScript(cx); 306 uint32_t lineno; 307 const char* filename; 308 bool mutedErrors; 309 uint32_t pcOffset; 310 if (evalType == DIRECT_EVAL) { 311 DescribeScriptedCallerForDirectEval(cx, callerScript, pc, &filename, 312 &lineno, &pcOffset, &mutedErrors); 313 maybeScript = callerScript; 314 } else { 315 DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, 316 &pcOffset, &mutedErrors); 317 } 318 319 const char* introducerFilename = filename; 320 if (maybeScript && maybeScript->scriptSource()->introducerFilename()) { 321 introducerFilename = maybeScript->scriptSource()->introducerFilename(); 322 } 323 324 Rooted<Scope*> enclosing(cx); 325 if (evalType == DIRECT_EVAL) { 326 enclosing = callerScript->innermostScope(pc); 327 } else { 328 enclosing = &cx->global()->emptyGlobalScope(); 329 } 330 331 CompileOptions options(cx); 332 options.setIsRunOnce(true) 333 .setNoScriptRval(false) 334 .setMutedErrors(mutedErrors) 335 .setDeferDebugMetadata(); 336 337 RootedScript introScript(cx); 338 339 if (evalType == DIRECT_EVAL && IsStrictEvalPC(pc)) { 340 options.setForceStrictMode(); 341 } 342 343 if (introducerFilename) { 344 options.setFileAndLine(filename, 1); 345 options.setIntroductionInfo(introducerFilename, "eval", lineno, pcOffset); 346 introScript = maybeScript; 347 } else { 348 options.setFileAndLine("eval", 1); 349 options.setIntroductionType("eval"); 350 } 351 options.setNonSyntacticScope( 352 enclosing->hasOnChain(ScopeKind::NonSyntactic)); 353 354 AutoStableStringChars linearChars(cx); 355 if (!linearChars.initTwoByte(cx, linearStr)) { 356 return false; 357 } 358 359 SourceText<char16_t> srcBuf; 360 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) { 361 return false; 362 } 363 364 RootedScript script( 365 cx, frontend::CompileEvalScript(cx, options, srcBuf, enclosing, env)); 366 if (!script) { 367 return false; 368 } 369 370 RootedValue undefValue(cx); 371 JS::InstantiateOptions instantiateOptions(options); 372 if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, undefValue, 373 nullptr, introScript, maybeScript)) { 374 return false; 375 } 376 377 esg.setNewScript(script); 378 } 379 380 return ExecuteKernel(cx, esg.script(), env, NullFramePtr() /* evalInFrame */, 381 vp); 382 } 383 384 bool js::IndirectEval(JSContext* cx, unsigned argc, Value* vp) { 385 CallArgs args = CallArgsFromVp(argc, vp); 386 387 RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment()); 388 389 // Note we'll just pass |undefined| here, then return it directly (or throw 390 // if runtime codegen is disabled), if no argument is provided. 391 return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(), 392 globalLexical, nullptr, args.rval()); 393 } 394 395 bool js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp) { 396 // Direct eval can assume it was called from an interpreted or baseline frame. 397 ScriptFrameIter iter(cx); 398 AbstractFramePtr caller = iter.abstractFramePtr(); 399 400 MOZ_ASSERT(JSOp(*iter.pc()) == JSOp::Eval || 401 JSOp(*iter.pc()) == JSOp::StrictEval || 402 JSOp(*iter.pc()) == JSOp::SpreadEval || 403 JSOp(*iter.pc()) == JSOp::StrictSpreadEval); 404 MOZ_ASSERT(caller.realm() == caller.script()->realm()); 405 406 RootedObject envChain(cx, caller.environmentChain()); 407 return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp); 408 } 409 410 bool js::IsAnyBuiltinEval(JSFunction* fun) { 411 return fun->maybeNative() == IndirectEval; 412 } 413 414 static bool ExecuteInExtensibleLexicalEnvironment( 415 JSContext* cx, HandleScript scriptArg, 416 Handle<ExtensibleLexicalEnvironmentObject*> env) { 417 CHECK_THREAD(cx); 418 cx->check(env, scriptArg); 419 MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope()); 420 421 RootedValue rval(cx); 422 return ExecuteKernel(cx, scriptArg, env, NullFramePtr() /* evalInFrame */, 423 &rval); 424 } 425 426 JS_PUBLIC_API bool js::ExecuteInFrameScriptEnvironment( 427 JSContext* cx, HandleObject objArg, HandleScript scriptArg, 428 MutableHandleObject envArg) { 429 Rooted<NonSyntacticVariablesObject*> varEnv( 430 cx, NonSyntacticVariablesObject::create(cx)); 431 if (!varEnv) { 432 return false; 433 } 434 435 JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); 436 if (!envChain.append(objArg)) { 437 return false; 438 } 439 440 Rooted<WithEnvironmentObject*> env( 441 cx, js::CreateObjectsForEnvironmentChain(cx, envChain, varEnv)); 442 if (!env) { 443 return false; 444 } 445 446 // Create lexical environment with |this| == objArg, which should be a Gecko 447 // MessageManager. 448 // NOTE: This is required behavior for Gecko FrameScriptLoader, where some 449 // callers try to bind methods from the message manager in their scope chain 450 // to |this|, and will fail if it is not bound to a message manager. 451 ObjectRealm& realm = ObjectRealm::get(varEnv); 452 Rooted<NonSyntacticLexicalEnvironmentObject*> lexicalEnv( 453 cx, realm.getOrCreateNonSyntacticLexicalEnvironment(cx, env, varEnv)); 454 if (!lexicalEnv) { 455 return false; 456 } 457 458 if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexicalEnv)) { 459 return false; 460 } 461 462 envArg.set(lexicalEnv); 463 return true; 464 } 465 466 JS_PUBLIC_API JSObject* JS::NewJSMEnvironment(JSContext* cx) { 467 Rooted<NonSyntacticVariablesObject*> varEnv( 468 cx, NonSyntacticVariablesObject::create(cx)); 469 if (!varEnv) { 470 return nullptr; 471 } 472 473 // Force the NonSyntacticLexicalEnvironmentObject to be created. 474 ObjectRealm& realm = ObjectRealm::get(varEnv); 475 MOZ_ASSERT(!realm.getNonSyntacticLexicalEnvironment(varEnv)); 476 if (!realm.getOrCreateNonSyntacticLexicalEnvironment(cx, varEnv)) { 477 return nullptr; 478 } 479 480 return varEnv; 481 } 482 483 JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx, 484 HandleScript scriptArg, 485 HandleObject varEnv) { 486 JS::EnvironmentChain emptyChain(cx, JS::SupportUnscopables::No); 487 return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain); 488 } 489 490 JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment( 491 JSContext* cx, HandleScript scriptArg, HandleObject varEnv, 492 const EnvironmentChain& targetObj) { 493 cx->check(varEnv); 494 MOZ_ASSERT( 495 ObjectRealm::get(varEnv).getNonSyntacticLexicalEnvironment(varEnv)); 496 MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval()); 497 498 Rooted<ExtensibleLexicalEnvironmentObject*> env( 499 cx, ExtensibleLexicalEnvironmentObject::forVarEnvironment(varEnv)); 500 501 // If the Gecko subscript loader specifies target objects, we need to add 502 // them to the environment. These are added after the NSVO environment. 503 if (!targetObj.empty()) { 504 // The environment chain will be as follows: 505 // GlobalObject / SystemGlobal 506 // GlobalLexicalEnvironmentObject[this=global] 507 // NonSyntacticVariablesObject (the JSMEnvironment) 508 // NonSyntacticLexicalEnvironmentObject[this=nsvo] 509 // WithEnvironmentObject[target=targetObj] 510 // NonSyntacticLexicalEnvironmentObject[this=targetObj] (*) 511 // 512 // (*) This environment intercepts JSOp::GlobalThis. 513 514 // Wrap the target objects in WithEnvironments. 515 Rooted<WithEnvironmentObject*> envChain( 516 cx, js::CreateObjectsForEnvironmentChain(cx, targetObj, env)); 517 if (!envChain) { 518 return false; 519 } 520 521 // See CreateNonSyntacticEnvironmentChain 522 if (!JSObject::setQualifiedVarObj(cx, envChain)) { 523 return false; 524 } 525 526 // Create an extensible lexical environment for the target object. 527 env = ObjectRealm::get(envChain).getOrCreateNonSyntacticLexicalEnvironment( 528 cx, envChain); 529 if (!env) { 530 return false; 531 } 532 } 533 534 return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env); 535 } 536 537 JS_PUBLIC_API JSObject* JS::GetJSMEnvironmentOfScriptedCaller(JSContext* cx) { 538 FrameIter iter(cx); 539 if (iter.done()) { 540 return nullptr; 541 } 542 543 // WASM frames don't always provide their environment, but we also shouldn't 544 // expect to see any calling into here. 545 MOZ_RELEASE_ASSERT(!iter.isWasm()); 546 547 RootedObject env(cx, iter.environmentChain(cx)); 548 while (env && !env->is<NonSyntacticVariablesObject>()) { 549 env = env->enclosingEnvironment(); 550 } 551 552 return env; 553 } 554 555 JS_PUBLIC_API bool JS::IsJSMEnvironment(JSObject* obj) { 556 // NOTE: This also returns true if the NonSyntacticVariablesObject was 557 // created for reasons other than the JSM loader. 558 return obj->is<NonSyntacticVariablesObject>(); 559 } 560 561 #ifdef JSGC_HASH_TABLE_CHECKS 562 void RuntimeCaches::checkEvalCacheAfterMinorGC() { 563 gc::CheckTableAfterMovingGC(evalCache, [](const auto& entry) { 564 CheckGCThingAfterMovingGC(entry.str); 565 CheckGCThingAfterMovingGC(entry.script); 566 CheckGCThingAfterMovingGC(entry.callerScript); 567 return EvalCacheLookup(entry.str, entry.callerScript, entry.pc); 568 }); 569 } 570 #endif