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