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