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