Bug 1853305 - Part 2: Use AutoSelectGCHeap in JSON parsing r=sfink
[gecko.git] / js / src / builtin / Eval.cpp
blob0cff2486ad4f6bfcbf2763bc0fa9c7a2cc7f26df
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/. */
7 #include "builtin/Eval.h"
9 #include "mozilla/HashFunctions.h"
10 #include "mozilla/Range.h"
12 #include "frontend/BytecodeCompiler.h" // frontend::CompileEvalScript
13 #include "gc/HashUtil.h"
14 #include "js/CompilationAndEvaluation.h"
15 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
16 #include "js/friend/JSMEnvironment.h" // JS::NewJSMEnvironment, JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::IsJSMEnvironment
17 #include "js/friend/WindowProxy.h" // js::IsWindowProxy
18 #include "js/SourceText.h"
19 #include "js/StableStringChars.h"
20 #include "vm/EnvironmentObject.h"
21 #include "vm/FrameIter.h"
22 #include "vm/GlobalObject.h"
23 #include "vm/Interpreter.h"
24 #include "vm/JSContext.h"
25 #include "vm/JSONParser.h"
27 #include "gc/Marking-inl.h"
28 #include "vm/EnvironmentObject-inl.h"
29 #include "vm/JSContext-inl.h"
30 #include "vm/Stack-inl.h"
32 using namespace js;
34 using mozilla::AddToHash;
35 using mozilla::HashString;
36 using mozilla::RangedPtr;
38 using JS::AutoCheckCannotGC;
39 using JS::AutoStableStringChars;
40 using JS::CompileOptions;
41 using JS::SourceOwnership;
42 using JS::SourceText;
44 // We should be able to assert this for *any* fp->environmentChain().
45 static void AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env) {
46 #ifdef DEBUG
47 RootedObject obj(cx);
48 for (obj = &env; obj; obj = obj->enclosingEnvironment()) {
49 MOZ_ASSERT(!IsWindowProxy(obj));
51 #endif
54 static bool IsEvalCacheCandidate(JSScript* script) {
55 if (!script->isDirectEvalInFunction()) {
56 return false;
59 // Make sure there are no inner objects (which may be used directly by script
60 // and clobbered) or inner functions (which may have wrong scope).
61 for (JS::GCCellPtr gcThing : script->gcthings()) {
62 if (gcThing.is<JSObject>()) {
63 return false;
67 return true;
70 /* static */
71 HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) {
72 HashNumber hash = HashStringChars(l.str);
73 return AddToHash(hash, l.callerScript.get(), l.pc);
76 /* static */
77 bool EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry,
78 const EvalCacheLookup& l) {
79 MOZ_ASSERT(IsEvalCacheCandidate(cacheEntry.script));
81 return EqualStrings(cacheEntry.str, l.str) &&
82 cacheEntry.callerScript == l.callerScript && cacheEntry.pc == l.pc;
85 // Add the script to the eval cache when EvalKernel is finished
86 class EvalScriptGuard {
87 JSContext* cx_;
88 Rooted<JSScript*> script_;
90 /* These fields are only valid if lookup_.str is non-nullptr. */
91 EvalCacheLookup lookup_;
92 mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
94 Rooted<JSLinearString*> lookupStr_;
96 public:
97 explicit EvalScriptGuard(JSContext* cx)
98 : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
100 ~EvalScriptGuard() {
101 if (script_ && !cx_->isExceptionPending()) {
102 script_->cacheForEval();
103 EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript,
104 lookup_.pc};
105 lookup_.str = lookupStr_;
106 if (lookup_.str && IsEvalCacheCandidate(script_)) {
107 // Ignore failure to add cache entry.
108 if (!p_->add(cx_, cx_->caches().evalCache, lookup_, cacheEntry)) {
109 cx_->recoverFromOutOfMemory();
115 void lookupInEvalCache(JSLinearString* str, JSScript* callerScript,
116 jsbytecode* pc) {
117 lookupStr_ = str;
118 lookup_.str = str;
119 lookup_.callerScript = callerScript;
120 lookup_.pc = pc;
121 p_.emplace(cx_, cx_->caches().evalCache, lookup_);
122 if (*p_) {
123 script_ = (*p_)->script;
124 p_->remove(cx_, cx_->caches().evalCache, lookup_);
128 void setNewScript(JSScript* script) {
129 // JSScript::fullyInitFromStencil has already called js_CallNewScriptHook.
130 MOZ_ASSERT(!script_ && script);
131 script_ = script;
134 bool foundScript() { return !!script_; }
136 HandleScript script() {
137 MOZ_ASSERT(script_);
138 return script_;
142 enum class EvalJSONResult { Failure, Success, NotJSON };
144 template <typename CharT>
145 static bool EvalStringMightBeJSON(const mozilla::Range<const CharT> chars) {
146 // If the eval string starts with '(' or '[' and ends with ')' or ']', it
147 // may be JSON. Try the JSON parser first because it's much faster. If
148 // the eval string isn't JSON, JSON parsing will probably fail quickly, so
149 // little time will be lost.
150 size_t length = chars.length();
151 if (length < 2) {
152 return false;
155 // It used to be that strings in JavaScript forbid U+2028 LINE SEPARATOR
156 // and U+2029 PARAGRAPH SEPARATOR, so something like
158 // eval("['" + "\u2028" + "']");
160 // i.e. an array containing a string with a line separator in it, *would*
161 // be JSON but *would not* be valid JavaScript. Handing such a string to
162 // the JSON parser would then fail to recognize a syntax error. As of
163 // <https://tc39.github.io/proposal-json-superset/> JavaScript strings may
164 // contain these two code points, so it's safe to JSON-parse eval strings
165 // that contain them.
167 CharT first = chars[0], last = chars[length - 1];
168 return (first == '[' && last == ']') || (first == '(' && last == ')');
171 template <typename CharT>
172 static EvalJSONResult ParseEvalStringAsJSON(
173 JSContext* cx, const mozilla::Range<const CharT> chars,
174 MutableHandleValue rval) {
175 size_t len = chars.length();
176 MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
177 (chars[0] == '[' && chars[len - 1] == ']'));
179 auto jsonChars = (chars[0] == '[') ? chars
180 : mozilla::Range<const CharT>(
181 chars.begin().get() + 1U, len - 2);
183 Rooted<JSONParser<CharT>> parser(
184 cx, cx, jsonChars, JSONParser<CharT>::ParseType::AttemptForEval);
185 if (!parser.parse(rval)) {
186 return EvalJSONResult::Failure;
189 return rval.isUndefined() ? EvalJSONResult::NotJSON : EvalJSONResult::Success;
192 static EvalJSONResult TryEvalJSON(JSContext* cx, JSLinearString* str,
193 MutableHandleValue rval) {
194 if (str->hasLatin1Chars()) {
195 AutoCheckCannotGC nogc;
196 if (!EvalStringMightBeJSON(str->latin1Range(nogc))) {
197 return EvalJSONResult::NotJSON;
199 } else {
200 AutoCheckCannotGC nogc;
201 if (!EvalStringMightBeJSON(str->twoByteRange(nogc))) {
202 return EvalJSONResult::NotJSON;
206 AutoStableStringChars linearChars(cx);
207 if (!linearChars.init(cx, str)) {
208 return EvalJSONResult::Failure;
211 return linearChars.isLatin1()
212 ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
213 : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
216 enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
218 // 18.2.1.1 PerformEval
220 // Common code implementing direct and indirect eval.
222 // Evaluate v, if it is a string, in the context of the given calling
223 // frame, with the provided scope chain, with the semantics of either a direct
224 // or indirect eval (see ES5 10.4.2). If this is an indirect eval, env
225 // must be the global lexical environment.
227 // On success, store the completion value in call.rval and return true.
228 static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
229 AbstractFramePtr caller, HandleObject env,
230 jsbytecode* pc, MutableHandleValue vp) {
231 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
232 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
233 MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
234 AssertInnerizedEnvironmentChain(cx, *env);
236 // Step 2.
237 if (!v.isString()) {
238 vp.set(v);
239 return true;
242 // Steps 3-4.
243 RootedString str(cx, v.toString());
244 if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, str)) {
245 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
246 JSMSG_CSP_BLOCKED_EVAL);
247 return false;
250 // Step 5 ff.
252 // Per ES5, indirect eval runs in the global scope. (eval is specified this
253 // way so that the compiler can make assumptions about what bindings may or
254 // may not exist in the current frame if it doesn't see 'eval'.)
255 MOZ_ASSERT_IF(
256 evalType != DIRECT_EVAL,
257 cx->global() == &env->as<GlobalLexicalEnvironmentObject>().global());
259 Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
260 if (!linearStr) {
261 return false;
264 RootedScript callerScript(cx, caller ? caller.script() : nullptr);
265 EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
266 if (ejr != EvalJSONResult::NotJSON) {
267 return ejr == EvalJSONResult::Success;
270 EvalScriptGuard esg(cx);
272 if (evalType == DIRECT_EVAL && caller.isFunctionFrame()) {
273 esg.lookupInEvalCache(linearStr, callerScript, pc);
276 if (!esg.foundScript()) {
277 RootedScript maybeScript(cx);
278 uint32_t lineno;
279 const char* filename;
280 bool mutedErrors;
281 uint32_t pcOffset;
282 if (evalType == DIRECT_EVAL) {
283 DescribeScriptedCallerForDirectEval(cx, callerScript, pc, &filename,
284 &lineno, &pcOffset, &mutedErrors);
285 maybeScript = callerScript;
286 } else {
287 DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
288 &pcOffset, &mutedErrors);
291 const char* introducerFilename = filename;
292 if (maybeScript && maybeScript->scriptSource()->introducerFilename()) {
293 introducerFilename = maybeScript->scriptSource()->introducerFilename();
296 Rooted<Scope*> enclosing(cx);
297 if (evalType == DIRECT_EVAL) {
298 enclosing = callerScript->innermostScope(pc);
299 } else {
300 enclosing = &cx->global()->emptyGlobalScope();
303 CompileOptions options(cx);
304 options.setIsRunOnce(true)
305 .setNoScriptRval(false)
306 .setMutedErrors(mutedErrors)
307 .setDeferDebugMetadata();
309 RootedScript introScript(cx);
311 if (evalType == DIRECT_EVAL && IsStrictEvalPC(pc)) {
312 options.setForceStrictMode();
315 if (introducerFilename) {
316 options.setFileAndLine(filename, 1);
317 options.setIntroductionInfo(introducerFilename, "eval", lineno, pcOffset);
318 introScript = maybeScript;
319 } else {
320 options.setFileAndLine("eval", 1);
321 options.setIntroductionType("eval");
323 options.setNonSyntacticScope(
324 enclosing->hasOnChain(ScopeKind::NonSyntactic));
326 AutoStableStringChars linearChars(cx);
327 if (!linearChars.initTwoByte(cx, linearStr)) {
328 return false;
331 SourceText<char16_t> srcBuf;
332 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
333 return false;
336 RootedScript script(
337 cx, frontend::CompileEvalScript(cx, options, srcBuf, enclosing, env));
338 if (!script) {
339 return false;
342 RootedValue undefValue(cx);
343 JS::InstantiateOptions instantiateOptions(options);
344 if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, undefValue,
345 nullptr, introScript, maybeScript)) {
346 return false;
349 esg.setNewScript(script);
352 return ExecuteKernel(cx, esg.script(), env, NullFramePtr() /* evalInFrame */,
353 vp);
356 bool js::IndirectEval(JSContext* cx, unsigned argc, Value* vp) {
357 CallArgs args = CallArgsFromVp(argc, vp);
359 RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
361 // Note we'll just pass |undefined| here, then return it directly (or throw
362 // if runtime codegen is disabled), if no argument is provided.
363 return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(),
364 globalLexical, nullptr, args.rval());
367 bool js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp) {
368 // Direct eval can assume it was called from an interpreted or baseline frame.
369 ScriptFrameIter iter(cx);
370 AbstractFramePtr caller = iter.abstractFramePtr();
372 MOZ_ASSERT(JSOp(*iter.pc()) == JSOp::Eval ||
373 JSOp(*iter.pc()) == JSOp::StrictEval ||
374 JSOp(*iter.pc()) == JSOp::SpreadEval ||
375 JSOp(*iter.pc()) == JSOp::StrictSpreadEval);
376 MOZ_ASSERT(caller.realm() == caller.script()->realm());
378 RootedObject envChain(cx, caller.environmentChain());
379 return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
382 bool js::IsAnyBuiltinEval(JSFunction* fun) {
383 return fun->maybeNative() == IndirectEval;
386 static bool ExecuteInExtensibleLexicalEnvironment(
387 JSContext* cx, HandleScript scriptArg,
388 Handle<ExtensibleLexicalEnvironmentObject*> env) {
389 CHECK_THREAD(cx);
390 cx->check(env);
391 cx->check(scriptArg);
392 MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
394 RootedValue rval(cx);
395 return ExecuteKernel(cx, scriptArg, env, NullFramePtr() /* evalInFrame */,
396 &rval);
399 JS_PUBLIC_API bool js::ExecuteInFrameScriptEnvironment(
400 JSContext* cx, HandleObject objArg, HandleScript scriptArg,
401 MutableHandleObject envArg) {
402 RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
403 if (!varEnv) {
404 return false;
407 RootedObjectVector envChain(cx);
408 if (!envChain.append(objArg)) {
409 return false;
412 RootedObject env(cx);
413 if (!js::CreateObjectsForEnvironmentChain(cx, envChain, varEnv, &env)) {
414 return false;
417 // Create lexical environment with |this| == objArg, which should be a Gecko
418 // MessageManager.
419 // NOTE: This is required behavior for Gecko FrameScriptLoader, where some
420 // callers try to bind methods from the message manager in their scope chain
421 // to |this|, and will fail if it is not bound to a message manager.
422 ObjectRealm& realm = ObjectRealm::get(varEnv);
423 Rooted<NonSyntacticLexicalEnvironmentObject*> lexicalEnv(
425 realm.getOrCreateNonSyntacticLexicalEnvironment(cx, env, varEnv, objArg));
426 if (!lexicalEnv) {
427 return false;
430 if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexicalEnv)) {
431 return false;
434 envArg.set(lexicalEnv);
435 return true;
438 JS_PUBLIC_API JSObject* JS::NewJSMEnvironment(JSContext* cx) {
439 RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
440 if (!varEnv) {
441 return nullptr;
444 // Force the NonSyntacticLexicalEnvironmentObject to be created.
445 ObjectRealm& realm = ObjectRealm::get(varEnv);
446 MOZ_ASSERT(!realm.getNonSyntacticLexicalEnvironment(varEnv));
447 if (!realm.getOrCreateNonSyntacticLexicalEnvironment(cx, varEnv)) {
448 return nullptr;
451 return varEnv;
454 JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx,
455 HandleScript scriptArg,
456 HandleObject varEnv) {
457 RootedObjectVector emptyChain(cx);
458 return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
461 JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx,
462 HandleScript scriptArg,
463 HandleObject varEnv,
464 HandleObjectVector targetObj) {
465 cx->check(varEnv);
466 MOZ_ASSERT(
467 ObjectRealm::get(varEnv).getNonSyntacticLexicalEnvironment(varEnv));
468 MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
470 Rooted<ExtensibleLexicalEnvironmentObject*> env(
471 cx, ExtensibleLexicalEnvironmentObject::forVarEnvironment(varEnv));
473 // If the Gecko subscript loader specifies target objects, we need to add
474 // them to the environment. These are added after the NSVO environment.
475 if (!targetObj.empty()) {
476 // The environment chain will be as follows:
477 // GlobalObject / BackstagePass
478 // GlobalLexicalEnvironmentObject[this=global]
479 // NonSyntacticVariablesObject (the JSMEnvironment)
480 // NonSyntacticLexicalEnvironmentObject[this=nsvo]
481 // WithEnvironmentObject[target=targetObj]
482 // NonSyntacticLexicalEnvironmentObject[this=targetObj] (*)
484 // (*) This environment intercepts JSOp::GlobalThis.
486 // Wrap the target objects in WithEnvironments.
487 RootedObject envChain(cx);
488 if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &envChain)) {
489 return false;
492 // See CreateNonSyntacticEnvironmentChain
493 if (!JSObject::setQualifiedVarObj(cx, envChain)) {
494 return false;
497 // Create an extensible lexical environment for the target object.
498 env = ObjectRealm::get(envChain).getOrCreateNonSyntacticLexicalEnvironment(
499 cx, envChain);
500 if (!env) {
501 return false;
505 return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
508 JS_PUBLIC_API JSObject* JS::GetJSMEnvironmentOfScriptedCaller(JSContext* cx) {
509 FrameIter iter(cx);
510 if (iter.done()) {
511 return nullptr;
514 // WASM frames don't always provide their environment, but we also shouldn't
515 // expect to see any calling into here.
516 MOZ_RELEASE_ASSERT(!iter.isWasm());
518 RootedObject env(cx, iter.environmentChain(cx));
519 while (env && !env->is<NonSyntacticVariablesObject>()) {
520 env = env->enclosingEnvironment();
523 return env;
526 JS_PUBLIC_API bool JS::IsJSMEnvironment(JSObject* obj) {
527 // NOTE: This also returns true if the NonSyntacticVariablesObject was
528 // created for reasons other than the JSM loader.
529 return obj->is<NonSyntacticVariablesObject>();
532 #ifdef JSGC_HASH_TABLE_CHECKS
533 void RuntimeCaches::checkEvalCacheAfterMinorGC() {
534 JSContext* cx = TlsContext.get();
535 for (auto r = evalCache.all(); !r.empty(); r.popFront()) {
536 const EvalCacheEntry& entry = r.front();
537 CheckGCThingAfterMovingGC(entry.str);
538 EvalCacheLookup lookup(cx);
539 lookup.str = entry.str;
540 lookup.callerScript = entry.callerScript;
541 lookup.pc = entry.pc;
542 auto ptr = evalCache.lookup(lookup);
543 MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
546 #endif