1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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 "frontend/BytecodeCompiler.h"
13 #include "gc/HashUtil.h"
14 #include "vm/Debugger.h"
15 #include "vm/GlobalObject.h"
16 #include "vm/JSContext.h"
17 #include "vm/JSONParser.h"
18
19 #include "vm/Interpreter-inl.h"
20
21 using namespace js;
22
23 using mozilla::AddToHash;
24 using mozilla::HashString;
25 using mozilla::RangedPtr;
26
27 using JS::AutoCheckCannotGC;
28
29 // We should be able to assert this for *any* fp->environmentChain().
AssertInnerizedEnvironmentChain(JSContext * cx,JSObject & env)30 static void AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env) {
31 #ifdef DEBUG
32 RootedObject obj(cx);
33 for (obj = &env; obj; obj = obj->enclosingEnvironment())
34 MOZ_ASSERT(!IsWindowProxy(obj));
35 #endif
36 }
37
IsEvalCacheCandidate(JSScript * script)38 static bool IsEvalCacheCandidate(JSScript* script) {
39 // Make sure there are no inner objects which might use the wrong parent
40 // and/or call scope by reusing the previous eval's script.
41 return script->isDirectEvalInFunction() && !script->hasSingletons() &&
42 !script->hasObjects();
43 }
44
hash(const EvalCacheLookup & l)45 /* static */ HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) {
46 AutoCheckCannotGC nogc;
47 uint32_t hash = l.str->hasLatin1Chars()
48 ? HashString(l.str->latin1Chars(nogc), l.str->length())
49 : HashString(l.str->twoByteChars(nogc), l.str->length());
50 return AddToHash(hash, l.callerScript.get(), l.pc);
51 }
52
match(const EvalCacheEntry & cacheEntry,const EvalCacheLookup & l)53 /* static */ bool EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry,
54 const EvalCacheLookup& l) {
55 MOZ_ASSERT(IsEvalCacheCandidate(cacheEntry.script));
56
57 return EqualStrings(cacheEntry.str, l.str) &&
58 cacheEntry.callerScript == l.callerScript && cacheEntry.pc == l.pc;
59 }
60
61 // Add the script to the eval cache when EvalKernel is finished
62 class EvalScriptGuard {
63 JSContext* cx_;
64 Rooted<JSScript*> script_;
65
66 /* These fields are only valid if lookup_.str is non-nullptr. */
67 EvalCacheLookup lookup_;
68 mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
69
70 RootedLinearString lookupStr_;
71
72 public:
EvalScriptGuard(JSContext * cx)73 explicit EvalScriptGuard(JSContext* cx)
74 : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
75
~EvalScriptGuard()76 ~EvalScriptGuard() {
77 if (script_ && !cx_->isExceptionPending()) {
78 script_->cacheForEval();
79 EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript,
80 lookup_.pc};
81 lookup_.str = lookupStr_;
82 if (lookup_.str && IsEvalCacheCandidate(script_)) {
83 // Ignore failure to add cache entry.
84 if (!p_->add(cx_, cx_->caches().evalCache, lookup_, cacheEntry))
85 cx_->recoverFromOutOfMemory();
86 }
87 }
88 }
89
lookupInEvalCache(JSLinearString * str,JSScript * callerScript,jsbytecode * pc)90 void lookupInEvalCache(JSLinearString* str, JSScript* callerScript,
91 jsbytecode* pc) {
92 lookupStr_ = str;
93 lookup_.str = str;
94 lookup_.callerScript = callerScript;
95 lookup_.pc = pc;
96 p_.emplace(cx_, cx_->caches().evalCache, lookup_);
97 if (*p_) {
98 script_ = (*p_)->script;
99 p_->remove(cx_, cx_->caches().evalCache, lookup_);
100 script_->uncacheForEval();
101 }
102 }
103
setNewScript(JSScript * script)104 void setNewScript(JSScript* script) {
105 // JSScript::initFromEmitter has already called js_CallNewScriptHook.
106 MOZ_ASSERT(!script_ && script);
107 script_ = script;
108 script_->setActiveEval();
109 }
110
foundScript()111 bool foundScript() { return !!script_; }
112
script()113 HandleScript script() {
114 MOZ_ASSERT(script_);
115 return script_;
116 }
117 };
118
119 enum EvalJSONResult { EvalJSON_Failure, EvalJSON_Success, EvalJSON_NotJSON };
120
121 template <typename CharT>
EvalStringMightBeJSON(const mozilla::Range<const CharT> chars)122 static bool EvalStringMightBeJSON(const mozilla::Range<const CharT> chars) {
123 // If the eval string starts with '(' or '[' and ends with ')' or ']', it may
124 // be JSON. Try the JSON parser first because it's much faster. If the eval
125 // string isn't JSON, JSON parsing will probably fail quickly, so little time
126 // will be lost.
127 size_t length = chars.length();
128 if (length > 2 && ((chars[0] == '[' && chars[length - 1] == ']') ||
129 (chars[0] == '(' && chars[length - 1] == ')'))) {
130 // Remarkably, JavaScript syntax is not a superset of JSON syntax:
131 // strings in JavaScript cannot contain the Unicode line and paragraph
132 // terminator characters U+2028 and U+2029, but strings in JSON can.
133 // Rather than force the JSON parser to handle this quirk when used by
134 // eval, we simply don't use the JSON parser when either character
135 // appears in the provided string. See bug 657367.
136 if (sizeof(CharT) > 1) {
137 for (RangedPtr<const CharT> cp = chars.begin() + 1, end = chars.end() - 1;
138 cp < end; cp++) {
139 char16_t c = *cp;
140 if (c == 0x2028 || c == 0x2029) return false;
141 }
142 }
143
144 return true;
145 }
146 return false;
147 }
148
149 template <typename CharT>
ParseEvalStringAsJSON(JSContext * cx,const mozilla::Range<const CharT> chars,MutableHandleValue rval)150 static EvalJSONResult ParseEvalStringAsJSON(
151 JSContext* cx, const mozilla::Range<const CharT> chars,
152 MutableHandleValue rval) {
153 size_t len = chars.length();
154 MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
155 (chars[0] == '[' && chars[len - 1] == ']'));
156
157 auto jsonChars = (chars[0] == '[') ? chars
158 : mozilla::Range<const CharT>(
159 chars.begin().get() + 1U, len - 2);
160
161 Rooted<JSONParser<CharT>> parser(
162 cx, JSONParser<CharT>(cx, jsonChars, JSONParserBase::NoError));
163 if (!parser.parse(rval)) return EvalJSON_Failure;
164
165 return rval.isUndefined() ? EvalJSON_NotJSON : EvalJSON_Success;
166 }
167
TryEvalJSON(JSContext * cx,JSLinearString * str,MutableHandleValue rval)168 static EvalJSONResult TryEvalJSON(JSContext* cx, JSLinearString* str,
169 MutableHandleValue rval) {
170 if (str->hasLatin1Chars()) {
171 AutoCheckCannotGC nogc;
172 if (!EvalStringMightBeJSON(str->latin1Range(nogc))) return EvalJSON_NotJSON;
173 } else {
174 AutoCheckCannotGC nogc;
175 if (!EvalStringMightBeJSON(str->twoByteRange(nogc)))
176 return EvalJSON_NotJSON;
177 }
178
179 AutoStableStringChars linearChars(cx);
180 if (!linearChars.init(cx, str)) return EvalJSON_Failure;
181
182 return linearChars.isLatin1()
183 ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
184 : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
185 }
186
187 enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
188
189 // Common code implementing direct and indirect eval.
190 //
191 // Evaluate call.argv[2], if it is a string, in the context of the given calling
192 // frame, with the provided scope chain, with the semantics of either a direct
193 // or indirect eval (see ES5 10.4.2). If this is an indirect eval, env
194 // must be a global object.
195 //
196 // 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)197 static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
198 AbstractFramePtr caller, HandleObject env,
199 jsbytecode* pc, MutableHandleValue vp) {
200 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
201 MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
202 MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
203 AssertInnerizedEnvironmentChain(cx, *env);
204
205 Rooted<GlobalObject*> envGlobal(cx, &env->global());
206 if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
207 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
208 JSMSG_CSP_BLOCKED_EVAL);
209 return false;
210 }
211
212 // ES5 15.1.2.1 step 1.
213 if (!v.isString()) {
214 vp.set(v);
215 return true;
216 }
217 RootedString str(cx, v.toString());
218
219 // ES5 15.1.2.1 steps 2-8.
220
221 // Per ES5, indirect eval runs in the global scope. (eval is specified this
222 // way so that the compiler can make assumptions about what bindings may or
223 // may not exist in the current frame if it doesn't see 'eval'.)
224 MOZ_ASSERT_IF(evalType != DIRECT_EVAL,
225 cx->global() == &env->as<LexicalEnvironmentObject>().global());
226
227 RootedLinearString linearStr(cx, str->ensureLinear(cx));
228 if (!linearStr) return false;
229
230 RootedScript callerScript(cx, caller ? caller.script() : nullptr);
231 EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
232 if (ejr != EvalJSON_NotJSON) return ejr == EvalJSON_Success;
233
234 EvalScriptGuard esg(cx);
235
236 if (evalType == DIRECT_EVAL && caller.isFunctionFrame())
237 esg.lookupInEvalCache(linearStr, callerScript, pc);
238
239 if (!esg.foundScript()) {
240 RootedScript maybeScript(cx);
241 unsigned lineno;
242 const char* filename;
243 bool mutedErrors;
244 uint32_t pcOffset;
245 DescribeScriptedCallerForCompilation(
246 cx, &maybeScript, &filename, &lineno, &pcOffset, &mutedErrors,
247 evalType == DIRECT_EVAL ? CALLED_FROM_JSOP_EVAL
248 : NOT_CALLED_FROM_JSOP_EVAL);
249
250 const char* introducerFilename = filename;
251 if (maybeScript && maybeScript->scriptSource()->introducerFilename())
252 introducerFilename = maybeScript->scriptSource()->introducerFilename();
253
254 RootedScope enclosing(cx);
255 if (evalType == DIRECT_EVAL)
256 enclosing = callerScript->innermostScope(pc);
257 else
258 enclosing = &cx->global()->emptyGlobalScope();
259
260 CompileOptions options(cx);
261 options.setIsRunOnce(true)
262 .setNoScriptRval(false)
263 .setMutedErrors(mutedErrors)
264 .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
265
266 if (introducerFilename) {
267 options.setFileAndLine(filename, 1);
268 options.setIntroductionInfo(introducerFilename, "eval", lineno,
269 maybeScript, pcOffset);
270 } else {
271 options.setFileAndLine("eval", 1);
272 options.setIntroductionType("eval");
273 }
274
275 AutoStableStringChars linearChars(cx);
276 if (!linearChars.initTwoByte(cx, linearStr)) return false;
277
278 const char16_t* chars = linearChars.twoByteRange().begin().get();
279 SourceBufferHolder::Ownership ownership =
280 linearChars.maybeGiveOwnershipToCaller()
281 ? SourceBufferHolder::GiveOwnership
282 : SourceBufferHolder::NoOwnership;
283 SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
284 JSScript* compiled = frontend::CompileEvalScript(
285 cx, cx->tempLifoAlloc(), env, enclosing, options, srcBuf);
286 if (!compiled) return false;
287
288 esg.setNewScript(compiled);
289 }
290
291 // Look up the newTarget from the frame iterator.
292 Value newTargetVal = NullValue();
293 return ExecuteKernel(cx, esg.script(), *env, newTargetVal,
294 NullFramePtr() /* evalInFrame */, vp.address());
295 }
296
DirectEvalStringFromIon(JSContext * cx,HandleObject env,HandleScript callerScript,HandleValue newTargetValue,HandleString str,jsbytecode * pc,MutableHandleValue vp)297 bool js::DirectEvalStringFromIon(JSContext* cx, HandleObject env,
298 HandleScript callerScript,
299 HandleValue newTargetValue, HandleString str,
300 jsbytecode* pc, MutableHandleValue vp) {
301 AssertInnerizedEnvironmentChain(cx, *env);
302
303 Rooted<GlobalObject*> envGlobal(cx, &env->global());
304 if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
305 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
306 JSMSG_CSP_BLOCKED_EVAL);
307 return false;
308 }
309
310 // ES5 15.1.2.1 steps 2-8.
311
312 RootedLinearString linearStr(cx, str->ensureLinear(cx));
313 if (!linearStr) return false;
314
315 EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
316 if (ejr != EvalJSON_NotJSON) return ejr == EvalJSON_Success;
317
318 EvalScriptGuard esg(cx);
319
320 esg.lookupInEvalCache(linearStr, callerScript, pc);
321
322 if (!esg.foundScript()) {
323 RootedScript maybeScript(cx);
324 const char* filename;
325 unsigned lineno;
326 bool mutedErrors;
327 uint32_t pcOffset;
328 DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
329 &pcOffset, &mutedErrors,
330 CALLED_FROM_JSOP_EVAL);
331
332 const char* introducerFilename = filename;
333 if (maybeScript && maybeScript->scriptSource()->introducerFilename())
334 introducerFilename = maybeScript->scriptSource()->introducerFilename();
335
336 RootedScope enclosing(cx, callerScript->innermostScope(pc));
337
338 CompileOptions options(cx);
339 options.setIsRunOnce(true)
340 .setNoScriptRval(false)
341 .setMutedErrors(mutedErrors)
342 .maybeMakeStrictMode(IsStrictEvalPC(pc));
343
344 if (introducerFilename) {
345 options.setFileAndLine(filename, 1);
346 options.setIntroductionInfo(introducerFilename, "eval", lineno,
347 maybeScript, pcOffset);
348 } else {
349 options.setFileAndLine("eval", 1);
350 options.setIntroductionType("eval");
351 }
352
353 AutoStableStringChars linearChars(cx);
354 if (!linearChars.initTwoByte(cx, linearStr)) return false;
355
356 const char16_t* chars = linearChars.twoByteRange().begin().get();
357 SourceBufferHolder::Ownership ownership =
358 linearChars.maybeGiveOwnershipToCaller()
359 ? SourceBufferHolder::GiveOwnership
360 : SourceBufferHolder::NoOwnership;
361 SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
362 JSScript* compiled = frontend::CompileEvalScript(
363 cx, cx->tempLifoAlloc(), env, enclosing, options, srcBuf);
364 if (!compiled) return false;
365
366 esg.setNewScript(compiled);
367 }
368
369 return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
370 NullFramePtr() /* evalInFrame */, vp.address());
371 }
372
IndirectEval(JSContext * cx,unsigned argc,Value * vp)373 bool js::IndirectEval(JSContext* cx, unsigned argc, Value* vp) {
374 CallArgs args = CallArgsFromVp(argc, vp);
375
376 Rooted<GlobalObject*> global(cx, &args.callee().global());
377 RootedObject globalLexical(cx, &global->lexicalEnvironment());
378
379 // Note we'll just pass |undefined| here, then return it directly (or throw
380 // if runtime codegen is disabled), if no argument is provided.
381 return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(),
382 globalLexical, nullptr, args.rval());
383 }
384
DirectEval(JSContext * cx,HandleValue v,MutableHandleValue vp)385 bool js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp) {
386 // Direct eval can assume it was called from an interpreted or baseline frame.
387 ScriptFrameIter iter(cx);
388 AbstractFramePtr caller = iter.abstractFramePtr();
389
390 MOZ_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
391 JSOp(*iter.pc()) == JSOP_STRICTEVAL ||
392 JSOp(*iter.pc()) == JSOP_SPREADEVAL ||
393 JSOp(*iter.pc()) == JSOP_STRICTSPREADEVAL);
394 MOZ_ASSERT(caller.compartment() == caller.script()->compartment());
395
396 RootedObject envChain(cx, caller.environmentChain());
397 return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
398 }
399
IsAnyBuiltinEval(JSFunction * fun)400 bool js::IsAnyBuiltinEval(JSFunction* fun) {
401 return fun->maybeNative() == IndirectEval;
402 }
403
ExecuteInExtensibleLexicalEnvironment(JSContext * cx,HandleScript scriptArg,HandleObject env)404 static bool ExecuteInExtensibleLexicalEnvironment(JSContext* cx,
405 HandleScript scriptArg,
406 HandleObject env) {
407 CHECK_REQUEST(cx);
408 assertSameCompartment(cx, env);
409 MOZ_ASSERT(IsExtensibleLexicalEnvironment(env));
410 MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
411
412 RootedScript script(cx, scriptArg);
413 if (script->compartment() != cx->compartment()) {
414 script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
415 if (!script) return false;
416
417 Debugger::onNewScript(cx, script);
418 }
419
420 RootedValue rval(cx);
421 return ExecuteKernel(cx, script, *env, UndefinedValue(),
422 NullFramePtr() /* evalInFrame */, rval.address());
423 }
424
ExecuteInGlobalAndReturnScope(JSContext * cx,HandleObject global,HandleScript scriptArg,MutableHandleObject envArg)425 JS_FRIEND_API bool js::ExecuteInGlobalAndReturnScope(
426 JSContext* cx, HandleObject global, HandleScript scriptArg,
427 MutableHandleObject envArg) {
428 RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
429 if (!varEnv) return false;
430
431 // Create lexical environment with |this| == global.
432 // NOTE: This is required behavior for Gecko FrameScriptLoader
433 RootedObject lexEnv(
434 cx, LexicalEnvironmentObject::createNonSyntactic(cx, varEnv, global));
435 if (!lexEnv) return false;
436
437 if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexEnv))
438 return false;
439
440 envArg.set(lexEnv);
441 return true;
442 }
443
NewJSMEnvironment(JSContext * cx)444 JS_FRIEND_API JSObject* js::NewJSMEnvironment(JSContext* cx) {
445 RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
446 if (!varEnv) return nullptr;
447
448 // Force LexicalEnvironmentObject to be created
449 MOZ_ASSERT(!cx->compartment()->getNonSyntacticLexicalEnvironment(varEnv));
450 if (!cx->compartment()->getOrCreateNonSyntacticLexicalEnvironment(cx, varEnv))
451 return nullptr;
452
453 return varEnv;
454 }
455
ExecuteInJSMEnvironment(JSContext * cx,HandleScript scriptArg,HandleObject varEnv)456 JS_FRIEND_API bool js::ExecuteInJSMEnvironment(JSContext* cx,
457 HandleScript scriptArg,
458 HandleObject varEnv) {
459 AutoObjectVector emptyChain(cx);
460 return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
461 }
462
ExecuteInJSMEnvironment(JSContext * cx,HandleScript scriptArg,HandleObject varEnv,AutoObjectVector & targetObj)463 JS_FRIEND_API bool js::ExecuteInJSMEnvironment(JSContext* cx,
464 HandleScript scriptArg,
465 HandleObject varEnv,
466 AutoObjectVector& targetObj) {
467 assertSameCompartment(cx, varEnv);
468 MOZ_ASSERT(cx->compartment()->getNonSyntacticLexicalEnvironment(varEnv));
469 MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
470
471 RootedObject env(cx, JS_ExtensibleLexicalEnvironment(varEnv));
472
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 // LexicalEnvironmentObject[this=global]
479 // NonSyntacticVariablesObject (the JSMEnvironment)
480 // LexicalEnvironmentObject[this=nsvo]
481 // WithEnvironmentObject[target=targetObj]
482 // LexicalEnvironmentObject[this=targetObj] (*)
483 //
484 // (*) This environment intentionally intercepts JSOP_GLOBALTHIS, but
485 // not JSOP_FUNCTIONTHIS (which instead will fallback to the NSVO). I
486 // don't make the rules, I just record them.
487
488 // Wrap the target objects in WithEnvironments.
489 if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &env))
490 return false;
491
492 // See CreateNonSyntacticEnvironmentChain
493 if (!JSObject::setQualifiedVarObj(cx, env)) return false;
494
495 // Create an extensible LexicalEnvironmentObject for target object
496 env = cx->compartment()->getOrCreateNonSyntacticLexicalEnvironment(cx, env);
497 if (!env) return false;
498 }
499
500 return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
501 }
502
GetJSMEnvironmentOfScriptedCaller(JSContext * cx)503 JS_FRIEND_API JSObject* js::GetJSMEnvironmentOfScriptedCaller(JSContext* cx) {
504 FrameIter iter(cx);
505 if (iter.done()) return nullptr;
506
507 // WASM frames don't always provide their environment, but we also shouldn't
508 // expect to see any calling into here.
509 MOZ_RELEASE_ASSERT(!iter.isWasm());
510
511 RootedObject env(cx, iter.environmentChain(cx));
512 while (env && !env->is<NonSyntacticVariablesObject>())
513 env = env->enclosingEnvironment();
514
515 return env;
516 }
517
IsJSMEnvironment(JSObject * obj)518 JS_FRIEND_API bool js::IsJSMEnvironment(JSObject* obj) {
519 // NOTE: This also returns true if the NonSyntacticVariablesObject was
520 // created for reasons other than the JSM loader.
521 return obj->is<NonSyntacticVariablesObject>();
522 }
523