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"
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
;
44 // We should be able to assert this for *any* fp->environmentChain().
45 static void AssertInnerizedEnvironmentChain(JSContext
* cx
, JSObject
& env
) {
48 for (obj
= &env
; obj
; obj
= obj
->enclosingEnvironment()) {
49 MOZ_ASSERT(!IsWindowProxy(obj
));
54 static bool IsEvalCacheCandidate(JSScript
* script
) {
55 if (!script
->isDirectEvalInFunction()) {
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
>()) {
71 HashNumber
EvalCacheHashPolicy::hash(const EvalCacheLookup
& l
) {
72 HashNumber hash
= HashStringChars(l
.str
);
73 return AddToHash(hash
, l
.callerScript
, l
.pc
);
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
{
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_
;
102 explicit EvalScriptGuard(JSContext
* cx
)
103 : cx_(cx
), script_(cx
), lookup_(cx
), lookupStr_(cx
) {}
106 if (script_
&& !cx_
->isExceptionPending()) {
107 script_
->cacheForEval();
108 EvalCacheLookup
& lookup
= lookup_
.get();
109 EvalCacheEntry cacheEntry
= {lookupStr_
, script_
, lookup
.callerScript
,
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
,
124 EvalCacheLookup
& lookup
= lookup_
.get();
126 lookup
.callerScript
= callerScript
;
128 p_
.emplace(cx_
, cx_
->caches().evalCache
, lookup
);
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
);
141 bool foundScript() { return !!script_
; }
143 HandleScript
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();
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
;
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
);
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
);
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'.)
263 evalType
!= DIRECT_EVAL
,
264 cx
->global() == &env
->as
<GlobalLexicalEnvironmentObject
>().global());
266 Rooted
<JSLinearString
*> linearStr(cx
, str
->ensureLinear(cx
));
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
);
286 const char* filename
;
289 if (evalType
== DIRECT_EVAL
) {
290 DescribeScriptedCallerForDirectEval(cx
, callerScript
, pc
, &filename
,
291 &lineno
, &pcOffset
, &mutedErrors
);
292 maybeScript
= callerScript
;
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
);
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
;
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
)) {
338 SourceText
<char16_t
> srcBuf
;
339 if (!srcBuf
.initMaybeBorrowed(cx
, linearChars
)) {
344 cx
, frontend::CompileEvalScript(cx
, options
, srcBuf
, enclosing
, env
));
349 RootedValue
undefValue(cx
);
350 JS::InstantiateOptions
instantiateOptions(options
);
351 if (!JS::UpdateDebugMetadata(cx
, script
, instantiateOptions
, undefValue
,
352 nullptr, introScript
, maybeScript
)) {
356 esg
.setNewScript(script
);
359 return ExecuteKernel(cx
, esg
.script(), env
, NullFramePtr() /* evalInFrame */,
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
) {
398 cx
->check(scriptArg
);
399 MOZ_RELEASE_ASSERT(scriptArg
->hasNonSyntacticScope());
401 RootedValue
rval(cx
);
402 return ExecuteKernel(cx
, scriptArg
, env
, NullFramePtr() /* evalInFrame */,
406 JS_PUBLIC_API
bool js::ExecuteInFrameScriptEnvironment(
407 JSContext
* cx
, HandleObject objArg
, HandleScript scriptArg
,
408 MutableHandleObject envArg
) {
409 RootedObject
varEnv(cx
, NonSyntacticVariablesObject::create(cx
));
414 RootedObjectVector
envChain(cx
);
415 if (!envChain
.append(objArg
)) {
419 RootedObject
env(cx
);
420 if (!js::CreateObjectsForEnvironmentChain(cx
, envChain
, varEnv
, &env
)) {
424 // Create lexical environment with |this| == objArg, which should be a Gecko
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
));
437 if (!ExecuteInExtensibleLexicalEnvironment(cx
, scriptArg
, lexicalEnv
)) {
441 envArg
.set(lexicalEnv
);
445 JS_PUBLIC_API JSObject
* JS::NewJSMEnvironment(JSContext
* cx
) {
446 RootedObject
varEnv(cx
, NonSyntacticVariablesObject::create(cx
));
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
)) {
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
,
471 HandleObjectVector targetObj
) {
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
)) {
499 // See CreateNonSyntacticEnvironmentChain
500 if (!JSObject::setQualifiedVarObj(cx
, envChain
)) {
504 // Create an extensible lexical environment for the target object.
505 env
= ObjectRealm::get(envChain
).getOrCreateNonSyntacticLexicalEnvironment(
512 return ExecuteInExtensibleLexicalEnvironment(cx
, scriptArg
, env
);
515 JS_PUBLIC_API JSObject
* JS::GetJSMEnvironmentOfScriptedCaller(JSContext
* cx
) {
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();
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
);