Bug 1908539 restrict MacOS platform audio processing to Nightly r=webrtc-reviewers...
[gecko.git] / js / src / builtin / Eval.cpp
blob5fffed2140b467e91c038a739726979bd27e0257
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, 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 void EvalCacheLookup::trace(JSTracer* trc) {
86 TraceNullableRoot(trc, &str, "EvalCacheLookup::str");
87 TraceNullableRoot(trc, &callerScript, "EvalCacheLookup::callerScript");
90 // Add the script to the eval cache when EvalKernel is finished
91 class EvalScriptGuard {
92 JSContext* cx_;
93 Rooted<JSScript*> script_;
95 /* These fields are only valid if lookup_.str is non-nullptr. */
96 Rooted<EvalCacheLookup> lookup_;
97 mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
99 Rooted<JSLinearString*> lookupStr_;
101 public:
102 explicit EvalScriptGuard(JSContext* cx)
103 : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
105 ~EvalScriptGuard() {
106 if (script_ && !cx_->isExceptionPending()) {
107 script_->cacheForEval();
108 EvalCacheLookup& lookup = lookup_.get();
109 EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup.callerScript,
110 lookup.pc};
111 lookup.str = lookupStr_;
112 if (lookup.str && IsEvalCacheCandidate(script_)) {
113 // Ignore failure to add cache entry.
114 if (!p_->add(cx_, cx_->caches().evalCache, lookup, cacheEntry)) {
115 cx_->recoverFromOutOfMemory();
121 void lookupInEvalCache(JSLinearString* str, JSScript* callerScript,
122 jsbytecode* pc) {
123 lookupStr_ = str;
124 EvalCacheLookup& lookup = lookup_.get();
125 lookup.str = str;
126 lookup.callerScript = callerScript;
127 lookup.pc = pc;
128 p_.emplace(cx_, cx_->caches().evalCache, lookup);
129 if (*p_) {
130 script_ = (*p_)->script;
131 p_->remove(cx_, cx_->caches().evalCache, lookup);
135 void setNewScript(JSScript* script) {
136 // JSScript::fullyInitFromStencil has already called js_CallNewScriptHook.
137 MOZ_ASSERT(!script_ && script);
138 script_ = script;
141 bool foundScript() { return !!script_; }
143 HandleScript script() {
144 MOZ_ASSERT(script_);
145 return script_;
149 enum class EvalJSONResult { Failure, Success, NotJSON };
151 template <typename CharT>
152 static bool EvalStringMightBeJSON(const mozilla::Range<const CharT> chars) {
153 // If the eval string starts with '(' or '[' and ends with ')' or ']', it
154 // may be JSON. Try the JSON parser first because it's much faster. If
155 // the eval string isn't JSON, JSON parsing will probably fail quickly, so
156 // little time will be lost.
157 size_t length = chars.length();
158 if (length < 2) {
159 return false;
162 // It used to be that strings in JavaScript forbid U+2028 LINE SEPARATOR
163 // and U+2029 PARAGRAPH SEPARATOR, so something like
165 // eval("['" + "\u2028" + "']");
167 // i.e. an array containing a string with a line separator in it, *would*
168 // be JSON but *would not* be valid JavaScript. Handing such a string to
169 // the JSON parser would then fail to recognize a syntax error. As of
170 // <https://tc39.github.io/proposal-json-superset/> JavaScript strings may
171 // contain these two code points, so it's safe to JSON-parse eval strings
172 // that contain them.
174 CharT first = chars[0], last = chars[length - 1];
175 return (first == '[' && last == ']') || (first == '(' && last == ')');
178 template <typename CharT>
179 static EvalJSONResult ParseEvalStringAsJSON(
180 JSContext* cx, const mozilla::Range<const CharT> chars,
181 MutableHandleValue rval) {
182 size_t len = chars.length();
183 MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
184 (chars[0] == '[' && chars[len - 1] == ']'));
186 auto jsonChars = (chars[0] == '[') ? chars
187 : mozilla::Range<const CharT>(
188 chars.begin().get() + 1U, len - 2);
190 Rooted<JSONParser<CharT>> parser(
191 cx, cx, jsonChars, JSONParser<CharT>::ParseType::AttemptForEval);
192 if (!parser.parse(rval)) {
193 return EvalJSONResult::Failure;
196 return rval.isUndefined() ? EvalJSONResult::NotJSON : EvalJSONResult::Success;
199 static EvalJSONResult TryEvalJSON(JSContext* cx, JSLinearString* str,
200 MutableHandleValue rval) {
201 if (str->hasLatin1Chars()) {
202 AutoCheckCannotGC nogc;
203 if (!EvalStringMightBeJSON(str->latin1Range(nogc))) {
204 return EvalJSONResult::NotJSON;
206 } else {
207 AutoCheckCannotGC nogc;
208 if (!EvalStringMightBeJSON(str->twoByteRange(nogc))) {
209 return EvalJSONResult::NotJSON;
213 AutoStableStringChars linearChars(cx);
214 if (!linearChars.init(cx, str)) {
215 return EvalJSONResult::Failure;
218 return linearChars.isLatin1()
219 ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
220 : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
223 enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
225 // 18.2.1.1 PerformEval
227 // Common code implementing direct and indirect eval.
229 // Evaluate v, if it is a string, in the context of the given calling
230 // frame, with the provided scope chain, with the semantics of either a direct
231 // or indirect eval (see ES5 10.4.2). If this is an indirect eval, env
232 // must be the global lexical environment.
234 // On success, store the completion value in call.rval and return true.
235 static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
236 AbstractFramePtr caller, HandleObject env,
237 jsbytecode* pc, MutableHandleValue vp) {
238 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
239 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
240 MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
241 AssertInnerizedEnvironmentChain(cx, *env);
243 // Step 2.
244 if (!v.isString()) {
245 vp.set(v);
246 return true;
249 // Steps 3-4.
250 RootedString str(cx, v.toString());
251 if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, str)) {
252 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
253 JSMSG_CSP_BLOCKED_EVAL);
254 return false;
257 // Step 5 ff.
259 // Per ES5, indirect eval runs in the global scope. (eval is specified this
260 // way so that the compiler can make assumptions about what bindings may or
261 // may not exist in the current frame if it doesn't see 'eval'.)
262 MOZ_ASSERT_IF(
263 evalType != DIRECT_EVAL,
264 cx->global() == &env->as<GlobalLexicalEnvironmentObject>().global());
266 Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
267 if (!linearStr) {
268 return false;
271 RootedScript callerScript(cx, caller ? caller.script() : nullptr);
272 EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
273 if (ejr != EvalJSONResult::NotJSON) {
274 return ejr == EvalJSONResult::Success;
277 EvalScriptGuard esg(cx);
279 if (evalType == DIRECT_EVAL && caller.isFunctionFrame()) {
280 esg.lookupInEvalCache(linearStr, callerScript, pc);
283 if (!esg.foundScript()) {
284 RootedScript maybeScript(cx);
285 uint32_t lineno;
286 const char* filename;
287 bool mutedErrors;
288 uint32_t pcOffset;
289 if (evalType == DIRECT_EVAL) {
290 DescribeScriptedCallerForDirectEval(cx, callerScript, pc, &filename,
291 &lineno, &pcOffset, &mutedErrors);
292 maybeScript = callerScript;
293 } else {
294 DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
295 &pcOffset, &mutedErrors);
298 const char* introducerFilename = filename;
299 if (maybeScript && maybeScript->scriptSource()->introducerFilename()) {
300 introducerFilename = maybeScript->scriptSource()->introducerFilename();
303 Rooted<Scope*> enclosing(cx);
304 if (evalType == DIRECT_EVAL) {
305 enclosing = callerScript->innermostScope(pc);
306 } else {
307 enclosing = &cx->global()->emptyGlobalScope();
310 CompileOptions options(cx);
311 options.setIsRunOnce(true)
312 .setNoScriptRval(false)
313 .setMutedErrors(mutedErrors)
314 .setDeferDebugMetadata();
316 RootedScript introScript(cx);
318 if (evalType == DIRECT_EVAL && IsStrictEvalPC(pc)) {
319 options.setForceStrictMode();
322 if (introducerFilename) {
323 options.setFileAndLine(filename, 1);
324 options.setIntroductionInfo(introducerFilename, "eval", lineno, pcOffset);
325 introScript = maybeScript;
326 } else {
327 options.setFileAndLine("eval", 1);
328 options.setIntroductionType("eval");
330 options.setNonSyntacticScope(
331 enclosing->hasOnChain(ScopeKind::NonSyntactic));
333 AutoStableStringChars linearChars(cx);
334 if (!linearChars.initTwoByte(cx, linearStr)) {
335 return false;
338 SourceText<char16_t> srcBuf;
339 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
340 return false;
343 RootedScript script(
344 cx, frontend::CompileEvalScript(cx, options, srcBuf, enclosing, env));
345 if (!script) {
346 return false;
349 RootedValue undefValue(cx);
350 JS::InstantiateOptions instantiateOptions(options);
351 if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, undefValue,
352 nullptr, introScript, maybeScript)) {
353 return false;
356 esg.setNewScript(script);
359 return ExecuteKernel(cx, esg.script(), env, NullFramePtr() /* evalInFrame */,
360 vp);
363 bool js::IndirectEval(JSContext* cx, unsigned argc, Value* vp) {
364 CallArgs args = CallArgsFromVp(argc, vp);
366 RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
368 // Note we'll just pass |undefined| here, then return it directly (or throw
369 // if runtime codegen is disabled), if no argument is provided.
370 return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(),
371 globalLexical, nullptr, args.rval());
374 bool js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp) {
375 // Direct eval can assume it was called from an interpreted or baseline frame.
376 ScriptFrameIter iter(cx);
377 AbstractFramePtr caller = iter.abstractFramePtr();
379 MOZ_ASSERT(JSOp(*iter.pc()) == JSOp::Eval ||
380 JSOp(*iter.pc()) == JSOp::StrictEval ||
381 JSOp(*iter.pc()) == JSOp::SpreadEval ||
382 JSOp(*iter.pc()) == JSOp::StrictSpreadEval);
383 MOZ_ASSERT(caller.realm() == caller.script()->realm());
385 RootedObject envChain(cx, caller.environmentChain());
386 return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
389 bool js::IsAnyBuiltinEval(JSFunction* fun) {
390 return fun->maybeNative() == IndirectEval;
393 static bool ExecuteInExtensibleLexicalEnvironment(
394 JSContext* cx, HandleScript scriptArg,
395 Handle<ExtensibleLexicalEnvironmentObject*> env) {
396 CHECK_THREAD(cx);
397 cx->check(env);
398 cx->check(scriptArg);
399 MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
401 RootedValue rval(cx);
402 return ExecuteKernel(cx, scriptArg, env, NullFramePtr() /* evalInFrame */,
403 &rval);
406 JS_PUBLIC_API bool js::ExecuteInFrameScriptEnvironment(
407 JSContext* cx, HandleObject objArg, HandleScript scriptArg,
408 MutableHandleObject envArg) {
409 RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
410 if (!varEnv) {
411 return false;
414 RootedObjectVector envChain(cx);
415 if (!envChain.append(objArg)) {
416 return false;
419 RootedObject env(cx);
420 if (!js::CreateObjectsForEnvironmentChain(cx, envChain, varEnv, &env)) {
421 return false;
424 // Create lexical environment with |this| == objArg, which should be a Gecko
425 // MessageManager.
426 // NOTE: This is required behavior for Gecko FrameScriptLoader, where some
427 // callers try to bind methods from the message manager in their scope chain
428 // to |this|, and will fail if it is not bound to a message manager.
429 ObjectRealm& realm = ObjectRealm::get(varEnv);
430 Rooted<NonSyntacticLexicalEnvironmentObject*> lexicalEnv(
432 realm.getOrCreateNonSyntacticLexicalEnvironment(cx, env, varEnv, objArg));
433 if (!lexicalEnv) {
434 return false;
437 if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexicalEnv)) {
438 return false;
441 envArg.set(lexicalEnv);
442 return true;
445 JS_PUBLIC_API JSObject* JS::NewJSMEnvironment(JSContext* cx) {
446 RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
447 if (!varEnv) {
448 return nullptr;
451 // Force the NonSyntacticLexicalEnvironmentObject to be created.
452 ObjectRealm& realm = ObjectRealm::get(varEnv);
453 MOZ_ASSERT(!realm.getNonSyntacticLexicalEnvironment(varEnv));
454 if (!realm.getOrCreateNonSyntacticLexicalEnvironment(cx, varEnv)) {
455 return nullptr;
458 return varEnv;
461 JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx,
462 HandleScript scriptArg,
463 HandleObject varEnv) {
464 RootedObjectVector emptyChain(cx);
465 return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
468 JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx,
469 HandleScript scriptArg,
470 HandleObject varEnv,
471 HandleObjectVector targetObj) {
472 cx->check(varEnv);
473 MOZ_ASSERT(
474 ObjectRealm::get(varEnv).getNonSyntacticLexicalEnvironment(varEnv));
475 MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
477 Rooted<ExtensibleLexicalEnvironmentObject*> env(
478 cx, ExtensibleLexicalEnvironmentObject::forVarEnvironment(varEnv));
480 // If the Gecko subscript loader specifies target objects, we need to add
481 // them to the environment. These are added after the NSVO environment.
482 if (!targetObj.empty()) {
483 // The environment chain will be as follows:
484 // GlobalObject / BackstagePass
485 // GlobalLexicalEnvironmentObject[this=global]
486 // NonSyntacticVariablesObject (the JSMEnvironment)
487 // NonSyntacticLexicalEnvironmentObject[this=nsvo]
488 // WithEnvironmentObject[target=targetObj]
489 // NonSyntacticLexicalEnvironmentObject[this=targetObj] (*)
491 // (*) This environment intercepts JSOp::GlobalThis.
493 // Wrap the target objects in WithEnvironments.
494 RootedObject envChain(cx);
495 if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &envChain)) {
496 return false;
499 // See CreateNonSyntacticEnvironmentChain
500 if (!JSObject::setQualifiedVarObj(cx, envChain)) {
501 return false;
504 // Create an extensible lexical environment for the target object.
505 env = ObjectRealm::get(envChain).getOrCreateNonSyntacticLexicalEnvironment(
506 cx, envChain);
507 if (!env) {
508 return false;
512 return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
515 JS_PUBLIC_API JSObject* JS::GetJSMEnvironmentOfScriptedCaller(JSContext* cx) {
516 FrameIter iter(cx);
517 if (iter.done()) {
518 return nullptr;
521 // WASM frames don't always provide their environment, but we also shouldn't
522 // expect to see any calling into here.
523 MOZ_RELEASE_ASSERT(!iter.isWasm());
525 RootedObject env(cx, iter.environmentChain(cx));
526 while (env && !env->is<NonSyntacticVariablesObject>()) {
527 env = env->enclosingEnvironment();
530 return env;
533 JS_PUBLIC_API bool JS::IsJSMEnvironment(JSObject* obj) {
534 // NOTE: This also returns true if the NonSyntacticVariablesObject was
535 // created for reasons other than the JSM loader.
536 return obj->is<NonSyntacticVariablesObject>();
539 #ifdef JSGC_HASH_TABLE_CHECKS
540 void RuntimeCaches::checkEvalCacheAfterMinorGC() {
541 gc::CheckTableAfterMovingGC(evalCache, [](const auto& entry) {
542 CheckGCThingAfterMovingGC(entry.str);
543 CheckGCThingAfterMovingGC(entry.script);
544 CheckGCThingAfterMovingGC(entry.callerScript);
545 return EvalCacheLookup(entry.str, entry.callerScript, entry.pc);
548 #endif