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
.get(), 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 // Add the script to the eval cache when EvalKernel is finished
86 class EvalScriptGuard
{
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_
;
97 explicit EvalScriptGuard(JSContext
* cx
)
98 : cx_(cx
), script_(cx
), lookup_(cx
), lookupStr_(cx
) {}
101 if (script_
&& !cx_
->isExceptionPending()) {
102 script_
->cacheForEval();
103 EvalCacheEntry cacheEntry
= {lookupStr_
, script_
, lookup_
.callerScript
,
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
,
119 lookup_
.callerScript
= callerScript
;
121 p_
.emplace(cx_
, cx_
->caches().evalCache
, lookup_
);
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
);
134 bool foundScript() { return !!script_
; }
136 HandleScript
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();
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
;
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
);
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
);
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'.)
256 evalType
!= DIRECT_EVAL
,
257 cx
->global() == &env
->as
<GlobalLexicalEnvironmentObject
>().global());
259 Rooted
<JSLinearString
*> linearStr(cx
, str
->ensureLinear(cx
));
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
);
279 const char* filename
;
282 if (evalType
== DIRECT_EVAL
) {
283 DescribeScriptedCallerForDirectEval(cx
, callerScript
, pc
, &filename
,
284 &lineno
, &pcOffset
, &mutedErrors
);
285 maybeScript
= callerScript
;
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
);
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
;
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
)) {
331 SourceText
<char16_t
> srcBuf
;
332 if (!srcBuf
.initMaybeBorrowed(cx
, linearChars
)) {
337 cx
, frontend::CompileEvalScript(cx
, options
, srcBuf
, enclosing
, env
));
342 RootedValue
undefValue(cx
);
343 JS::InstantiateOptions
instantiateOptions(options
);
344 if (!JS::UpdateDebugMetadata(cx
, script
, instantiateOptions
, undefValue
,
345 nullptr, introScript
, maybeScript
)) {
349 esg
.setNewScript(script
);
352 return ExecuteKernel(cx
, esg
.script(), env
, NullFramePtr() /* evalInFrame */,
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
) {
391 cx
->check(scriptArg
);
392 MOZ_RELEASE_ASSERT(scriptArg
->hasNonSyntacticScope());
394 RootedValue
rval(cx
);
395 return ExecuteKernel(cx
, scriptArg
, env
, NullFramePtr() /* evalInFrame */,
399 JS_PUBLIC_API
bool js::ExecuteInFrameScriptEnvironment(
400 JSContext
* cx
, HandleObject objArg
, HandleScript scriptArg
,
401 MutableHandleObject envArg
) {
402 RootedObject
varEnv(cx
, NonSyntacticVariablesObject::create(cx
));
407 RootedObjectVector
envChain(cx
);
408 if (!envChain
.append(objArg
)) {
412 RootedObject
env(cx
);
413 if (!js::CreateObjectsForEnvironmentChain(cx
, envChain
, varEnv
, &env
)) {
417 // Create lexical environment with |this| == objArg, which should be a Gecko
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
));
430 if (!ExecuteInExtensibleLexicalEnvironment(cx
, scriptArg
, lexicalEnv
)) {
434 envArg
.set(lexicalEnv
);
438 JS_PUBLIC_API JSObject
* JS::NewJSMEnvironment(JSContext
* cx
) {
439 RootedObject
varEnv(cx
, NonSyntacticVariablesObject::create(cx
));
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
)) {
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
,
464 HandleObjectVector targetObj
) {
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
)) {
492 // See CreateNonSyntacticEnvironmentChain
493 if (!JSObject::setQualifiedVarObj(cx
, envChain
)) {
497 // Create an extensible lexical environment for the target object.
498 env
= ObjectRealm::get(envChain
).getOrCreateNonSyntacticLexicalEnvironment(
505 return ExecuteInExtensibleLexicalEnvironment(cx
, scriptArg
, env
);
508 JS_PUBLIC_API JSObject
* JS::GetJSMEnvironmentOfScriptedCaller(JSContext
* cx
) {
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();
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());