|
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 |
|
11 #include "jscntxt.h" |
|
12 #include "jsonparser.h" |
|
13 |
|
14 #include "frontend/BytecodeCompiler.h" |
|
15 #include "vm/GlobalObject.h" |
|
16 |
|
17 #include "vm/Interpreter-inl.h" |
|
18 |
|
19 using namespace js; |
|
20 |
|
21 using mozilla::AddToHash; |
|
22 using mozilla::HashString; |
|
23 |
|
24 // We should be able to assert this for *any* fp->scopeChain(). |
|
25 static void |
|
26 AssertInnerizedScopeChain(JSContext *cx, JSObject &scopeobj) |
|
27 { |
|
28 #ifdef DEBUG |
|
29 RootedObject obj(cx); |
|
30 for (obj = &scopeobj; obj; obj = obj->enclosingScope()) { |
|
31 if (JSObjectOp op = obj->getClass()->ext.innerObject) { |
|
32 JS_ASSERT(op(cx, obj) == obj); |
|
33 } |
|
34 } |
|
35 #endif |
|
36 } |
|
37 |
|
38 static bool |
|
39 IsEvalCacheCandidate(JSScript *script) |
|
40 { |
|
41 // Make sure there are no inner objects which might use the wrong parent |
|
42 // and/or call scope by reusing the previous eval's script. Skip the |
|
43 // script's first object, which entrains the eval's scope. |
|
44 return script->savedCallerFun() && |
|
45 !script->hasSingletons() && |
|
46 script->objects()->length == 1 && |
|
47 !script->hasRegexps(); |
|
48 } |
|
49 |
|
50 /* static */ HashNumber |
|
51 EvalCacheHashPolicy::hash(const EvalCacheLookup &l) |
|
52 { |
|
53 return AddToHash(HashString(l.str->chars(), l.str->length()), |
|
54 l.callerScript.get(), |
|
55 l.version, |
|
56 l.pc); |
|
57 } |
|
58 |
|
59 /* static */ bool |
|
60 EvalCacheHashPolicy::match(const EvalCacheEntry &cacheEntry, const EvalCacheLookup &l) |
|
61 { |
|
62 JSScript *script = cacheEntry.script; |
|
63 |
|
64 JS_ASSERT(IsEvalCacheCandidate(script)); |
|
65 |
|
66 // Get the source string passed for safekeeping in the atom map |
|
67 // by the prior eval to frontend::CompileScript. |
|
68 JSAtom *keyStr = script->atoms[0]; |
|
69 |
|
70 return EqualStrings(keyStr, l.str) && |
|
71 cacheEntry.callerScript == l.callerScript && |
|
72 script->getVersion() == l.version && |
|
73 cacheEntry.pc == l.pc; |
|
74 } |
|
75 |
|
76 // There are two things we want to do with each script executed in EvalKernel: |
|
77 // 1. notify OldDebugAPI about script creation/destruction |
|
78 // 2. add the script to the eval cache when EvalKernel is finished |
|
79 // |
|
80 // NB: Although the eval cache keeps a script alive wrt to the JS engine, from |
|
81 // an OldDebugAPI user's perspective, we want each eval() to create and |
|
82 // destroy a script. This hides implementation details and means we don't have |
|
83 // to deal with calls to JS_GetScriptObject for scripts in the eval cache. |
|
84 class EvalScriptGuard |
|
85 { |
|
86 JSContext *cx_; |
|
87 Rooted<JSScript*> script_; |
|
88 |
|
89 /* These fields are only valid if lookup_.str is non-nullptr. */ |
|
90 EvalCacheLookup lookup_; |
|
91 EvalCache::AddPtr p_; |
|
92 |
|
93 Rooted<JSLinearString*> lookupStr_; |
|
94 |
|
95 public: |
|
96 EvalScriptGuard(JSContext *cx) |
|
97 : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {} |
|
98 |
|
99 ~EvalScriptGuard() { |
|
100 if (script_) { |
|
101 CallDestroyScriptHook(cx_->runtime()->defaultFreeOp(), script_); |
|
102 script_->cacheForEval(); |
|
103 EvalCacheEntry cacheEntry = {script_, lookup_.callerScript, lookup_.pc}; |
|
104 lookup_.str = lookupStr_; |
|
105 if (lookup_.str && IsEvalCacheCandidate(script_)) |
|
106 cx_->runtime()->evalCache.relookupOrAdd(p_, lookup_, cacheEntry); |
|
107 } |
|
108 } |
|
109 |
|
110 void lookupInEvalCache(JSLinearString *str, JSScript *callerScript, jsbytecode *pc) |
|
111 { |
|
112 lookupStr_ = str; |
|
113 lookup_.str = str; |
|
114 lookup_.callerScript = callerScript; |
|
115 lookup_.version = cx_->findVersion(); |
|
116 lookup_.pc = pc; |
|
117 p_ = cx_->runtime()->evalCache.lookupForAdd(lookup_); |
|
118 if (p_) { |
|
119 script_ = p_->script; |
|
120 cx_->runtime()->evalCache.remove(p_); |
|
121 CallNewScriptHook(cx_, script_, NullPtr()); |
|
122 script_->uncacheForEval(); |
|
123 } |
|
124 } |
|
125 |
|
126 void setNewScript(JSScript *script) { |
|
127 // JSScript::initFromEmitter has already called js_CallNewScriptHook. |
|
128 JS_ASSERT(!script_ && script); |
|
129 script_ = script; |
|
130 script_->setActiveEval(); |
|
131 } |
|
132 |
|
133 bool foundScript() { |
|
134 return !!script_; |
|
135 } |
|
136 |
|
137 HandleScript script() { |
|
138 JS_ASSERT(script_); |
|
139 return script_; |
|
140 } |
|
141 }; |
|
142 |
|
143 enum EvalJSONResult { |
|
144 EvalJSON_Failure, |
|
145 EvalJSON_Success, |
|
146 EvalJSON_NotJSON |
|
147 }; |
|
148 |
|
149 static EvalJSONResult |
|
150 TryEvalJSON(JSContext *cx, JSScript *callerScript, |
|
151 ConstTwoByteChars chars, size_t length, MutableHandleValue rval) |
|
152 { |
|
153 // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON. |
|
154 // Try the JSON parser first because it's much faster. If the eval string |
|
155 // isn't JSON, JSON parsing will probably fail quickly, so little time |
|
156 // will be lost. |
|
157 // |
|
158 // Don't use the JSON parser if the caller is strict mode code, because in |
|
159 // strict mode object literals must not have repeated properties, and the |
|
160 // JSON parser cheerfully (and correctly) accepts them. If you're parsing |
|
161 // JSON with eval and using strict mode, you deserve to be slow. |
|
162 if (length > 2 && |
|
163 ((chars[0] == '[' && chars[length - 1] == ']') || |
|
164 (chars[0] == '(' && chars[length - 1] == ')')) && |
|
165 (!callerScript || !callerScript->strict())) |
|
166 { |
|
167 // Remarkably, JavaScript syntax is not a superset of JSON syntax: |
|
168 // strings in JavaScript cannot contain the Unicode line and paragraph |
|
169 // terminator characters U+2028 and U+2029, but strings in JSON can. |
|
170 // Rather than force the JSON parser to handle this quirk when used by |
|
171 // eval, we simply don't use the JSON parser when either character |
|
172 // appears in the provided string. See bug 657367. |
|
173 for (const jschar *cp = &chars[1], *end = &chars[length - 2]; ; cp++) { |
|
174 if (*cp == 0x2028 || *cp == 0x2029) |
|
175 break; |
|
176 |
|
177 if (cp == end) { |
|
178 bool isArray = (chars[0] == '['); |
|
179 JSONParser parser(cx, isArray ? chars : chars + 1U, isArray ? length : length - 2, |
|
180 JSONParser::NoError); |
|
181 RootedValue tmp(cx); |
|
182 if (!parser.parse(&tmp)) |
|
183 return EvalJSON_Failure; |
|
184 if (tmp.isUndefined()) |
|
185 return EvalJSON_NotJSON; |
|
186 rval.set(tmp); |
|
187 return EvalJSON_Success; |
|
188 } |
|
189 } |
|
190 } |
|
191 return EvalJSON_NotJSON; |
|
192 } |
|
193 |
|
194 // Define subset of ExecuteType so that casting performs the injection. |
|
195 enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL }; |
|
196 |
|
197 // Common code implementing direct and indirect eval. |
|
198 // |
|
199 // Evaluate call.argv[2], if it is a string, in the context of the given calling |
|
200 // frame, with the provided scope chain, with the semantics of either a direct |
|
201 // or indirect eval (see ES5 10.4.2). If this is an indirect eval, scopeobj |
|
202 // must be a global object. |
|
203 // |
|
204 // On success, store the completion value in call.rval and return true. |
|
205 static bool |
|
206 EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, AbstractFramePtr caller, |
|
207 HandleObject scopeobj, jsbytecode *pc) |
|
208 { |
|
209 JS_ASSERT((evalType == INDIRECT_EVAL) == !caller); |
|
210 JS_ASSERT((evalType == INDIRECT_EVAL) == !pc); |
|
211 JS_ASSERT_IF(evalType == INDIRECT_EVAL, scopeobj->is<GlobalObject>()); |
|
212 AssertInnerizedScopeChain(cx, *scopeobj); |
|
213 |
|
214 Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global()); |
|
215 if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) { |
|
216 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL); |
|
217 return false; |
|
218 } |
|
219 |
|
220 // ES5 15.1.2.1 step 1. |
|
221 if (args.length() < 1) { |
|
222 args.rval().setUndefined(); |
|
223 return true; |
|
224 } |
|
225 if (!args[0].isString()) { |
|
226 args.rval().set(args[0]); |
|
227 return true; |
|
228 } |
|
229 RootedString str(cx, args[0].toString()); |
|
230 |
|
231 // ES5 15.1.2.1 steps 2-8. |
|
232 |
|
233 // Per ES5, indirect eval runs in the global scope. (eval is specified this |
|
234 // way so that the compiler can make assumptions about what bindings may or |
|
235 // may not exist in the current frame if it doesn't see 'eval'.) |
|
236 unsigned staticLevel; |
|
237 RootedValue thisv(cx); |
|
238 if (evalType == DIRECT_EVAL) { |
|
239 JS_ASSERT_IF(caller.isInterpreterFrame(), !caller.asInterpreterFrame()->runningInJit()); |
|
240 staticLevel = caller.script()->staticLevel() + 1; |
|
241 |
|
242 // Direct calls to eval are supposed to see the caller's |this|. If we |
|
243 // haven't wrapped that yet, do so now, before we make a copy of it for |
|
244 // the eval code to use. |
|
245 if (!ComputeThis(cx, caller)) |
|
246 return false; |
|
247 thisv = caller.thisValue(); |
|
248 } else { |
|
249 JS_ASSERT(args.callee().global() == *scopeobj); |
|
250 staticLevel = 0; |
|
251 |
|
252 // Use the global as 'this', modulo outerization. |
|
253 JSObject *thisobj = JSObject::thisObject(cx, scopeobj); |
|
254 if (!thisobj) |
|
255 return false; |
|
256 thisv = ObjectValue(*thisobj); |
|
257 } |
|
258 |
|
259 Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx)); |
|
260 if (!flatStr) |
|
261 return false; |
|
262 |
|
263 size_t length = flatStr->length(); |
|
264 ConstTwoByteChars chars(flatStr->chars(), length); |
|
265 |
|
266 RootedScript callerScript(cx, caller ? caller.script() : nullptr); |
|
267 EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, args.rval()); |
|
268 if (ejr != EvalJSON_NotJSON) |
|
269 return ejr == EvalJSON_Success; |
|
270 |
|
271 EvalScriptGuard esg(cx); |
|
272 |
|
273 if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame()) |
|
274 esg.lookupInEvalCache(flatStr, callerScript, pc); |
|
275 |
|
276 if (!esg.foundScript()) { |
|
277 RootedScript maybeScript(cx); |
|
278 unsigned lineno; |
|
279 const char *filename; |
|
280 JSPrincipals *originPrincipals; |
|
281 uint32_t pcOffset; |
|
282 DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset, |
|
283 &originPrincipals, |
|
284 evalType == DIRECT_EVAL |
|
285 ? CALLED_FROM_JSOP_EVAL |
|
286 : NOT_CALLED_FROM_JSOP_EVAL); |
|
287 |
|
288 const char *introducerFilename = filename; |
|
289 if (maybeScript && maybeScript->scriptSource()->introducerFilename()) |
|
290 introducerFilename = maybeScript->scriptSource()->introducerFilename(); |
|
291 |
|
292 CompileOptions options(cx); |
|
293 options.setFileAndLine(filename, 1) |
|
294 .setCompileAndGo(true) |
|
295 .setForEval(true) |
|
296 .setNoScriptRval(false) |
|
297 .setOriginPrincipals(originPrincipals) |
|
298 .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset); |
|
299 SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership); |
|
300 JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), |
|
301 scopeobj, callerScript, options, |
|
302 srcBuf, flatStr, staticLevel); |
|
303 if (!compiled) |
|
304 return false; |
|
305 |
|
306 esg.setNewScript(compiled); |
|
307 } |
|
308 |
|
309 return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType), |
|
310 NullFramePtr() /* evalInFrame */, args.rval().address()); |
|
311 } |
|
312 |
|
313 bool |
|
314 js::DirectEvalStringFromIon(JSContext *cx, |
|
315 HandleObject scopeobj, HandleScript callerScript, |
|
316 HandleValue thisValue, HandleString str, |
|
317 jsbytecode *pc, MutableHandleValue vp) |
|
318 { |
|
319 AssertInnerizedScopeChain(cx, *scopeobj); |
|
320 |
|
321 Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global()); |
|
322 if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) { |
|
323 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL); |
|
324 return false; |
|
325 } |
|
326 |
|
327 // ES5 15.1.2.1 steps 2-8. |
|
328 |
|
329 unsigned staticLevel = callerScript->staticLevel() + 1; |
|
330 |
|
331 Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx)); |
|
332 if (!flatStr) |
|
333 return false; |
|
334 |
|
335 size_t length = flatStr->length(); |
|
336 ConstTwoByteChars chars(flatStr->chars(), length); |
|
337 |
|
338 EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, vp); |
|
339 if (ejr != EvalJSON_NotJSON) |
|
340 return ejr == EvalJSON_Success; |
|
341 |
|
342 EvalScriptGuard esg(cx); |
|
343 |
|
344 esg.lookupInEvalCache(flatStr, callerScript, pc); |
|
345 |
|
346 if (!esg.foundScript()) { |
|
347 RootedScript maybeScript(cx); |
|
348 const char *filename; |
|
349 unsigned lineno; |
|
350 JSPrincipals *originPrincipals; |
|
351 uint32_t pcOffset; |
|
352 DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset, |
|
353 &originPrincipals, CALLED_FROM_JSOP_EVAL); |
|
354 |
|
355 const char *introducerFilename = filename; |
|
356 if (maybeScript && maybeScript->scriptSource()->introducerFilename()) |
|
357 introducerFilename = maybeScript->scriptSource()->introducerFilename(); |
|
358 |
|
359 CompileOptions options(cx); |
|
360 options.setFileAndLine(filename, 1) |
|
361 .setCompileAndGo(true) |
|
362 .setForEval(true) |
|
363 .setNoScriptRval(false) |
|
364 .setOriginPrincipals(originPrincipals) |
|
365 .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset); |
|
366 SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership); |
|
367 JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), |
|
368 scopeobj, callerScript, options, |
|
369 srcBuf, flatStr, staticLevel); |
|
370 if (!compiled) |
|
371 return false; |
|
372 |
|
373 esg.setNewScript(compiled); |
|
374 } |
|
375 |
|
376 // Primitive 'this' values should have been filtered out by Ion. If boxed, |
|
377 // the calling frame cannot be updated to store the new object. |
|
378 JS_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull()); |
|
379 |
|
380 return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL), |
|
381 NullFramePtr() /* evalInFrame */, vp.address()); |
|
382 } |
|
383 |
|
384 bool |
|
385 js::DirectEvalValueFromIon(JSContext *cx, |
|
386 HandleObject scopeobj, HandleScript callerScript, |
|
387 HandleValue thisValue, HandleValue evalArg, |
|
388 jsbytecode *pc, MutableHandleValue vp) |
|
389 { |
|
390 // Act as identity on non-strings per ES5 15.1.2.1 step 1. |
|
391 if (!evalArg.isString()) { |
|
392 vp.set(evalArg); |
|
393 return true; |
|
394 } |
|
395 |
|
396 RootedString string(cx, evalArg.toString()); |
|
397 return DirectEvalStringFromIon(cx, scopeobj, callerScript, thisValue, string, pc, vp); |
|
398 } |
|
399 |
|
400 bool |
|
401 js::IndirectEval(JSContext *cx, unsigned argc, Value *vp) |
|
402 { |
|
403 CallArgs args = CallArgsFromVp(argc, vp); |
|
404 Rooted<GlobalObject*> global(cx, &args.callee().global()); |
|
405 return EvalKernel(cx, args, INDIRECT_EVAL, NullFramePtr(), global, nullptr); |
|
406 } |
|
407 |
|
408 bool |
|
409 js::DirectEval(JSContext *cx, const CallArgs &args) |
|
410 { |
|
411 // Direct eval can assume it was called from an interpreted or baseline frame. |
|
412 ScriptFrameIter iter(cx); |
|
413 AbstractFramePtr caller = iter.abstractFramePtr(); |
|
414 |
|
415 JS_ASSERT(caller.scopeChain()->global().valueIsEval(args.calleev())); |
|
416 JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL || |
|
417 JSOp(*iter.pc()) == JSOP_SPREADEVAL); |
|
418 JS_ASSERT_IF(caller.isFunctionFrame(), |
|
419 caller.compartment() == caller.callee()->compartment()); |
|
420 |
|
421 RootedObject scopeChain(cx, caller.scopeChain()); |
|
422 return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc()); |
|
423 } |
|
424 |
|
425 bool |
|
426 js::IsAnyBuiltinEval(JSFunction *fun) |
|
427 { |
|
428 return fun->maybeNative() == IndirectEval; |
|
429 } |