Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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/. */
7 #include "builtin/Eval.h"
9 #include "mozilla/HashFunctions.h"
11 #include "jscntxt.h"
12 #include "jsonparser.h"
14 #include "frontend/BytecodeCompiler.h"
15 #include "vm/GlobalObject.h"
17 #include "vm/Interpreter-inl.h"
19 using namespace js;
21 using mozilla::AddToHash;
22 using mozilla::HashString;
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 }
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 }
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 }
59 /* static */ bool
60 EvalCacheHashPolicy::match(const EvalCacheEntry &cacheEntry, const EvalCacheLookup &l)
61 {
62 JSScript *script = cacheEntry.script;
64 JS_ASSERT(IsEvalCacheCandidate(script));
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];
70 return EqualStrings(keyStr, l.str) &&
71 cacheEntry.callerScript == l.callerScript &&
72 script->getVersion() == l.version &&
73 cacheEntry.pc == l.pc;
74 }
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_;
89 /* These fields are only valid if lookup_.str is non-nullptr. */
90 EvalCacheLookup lookup_;
91 EvalCache::AddPtr p_;
93 Rooted<JSLinearString*> lookupStr_;
95 public:
96 EvalScriptGuard(JSContext *cx)
97 : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
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 }
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 }
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 }
133 bool foundScript() {
134 return !!script_;
135 }
137 HandleScript script() {
138 JS_ASSERT(script_);
139 return script_;
140 }
141 };
143 enum EvalJSONResult {
144 EvalJSON_Failure,
145 EvalJSON_Success,
146 EvalJSON_NotJSON
147 };
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;
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 }
194 // Define subset of ExecuteType so that casting performs the injection.
195 enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL };
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);
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 }
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());
231 // ES5 15.1.2.1 steps 2-8.
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;
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;
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 }
259 Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
260 if (!flatStr)
261 return false;
263 size_t length = flatStr->length();
264 ConstTwoByteChars chars(flatStr->chars(), length);
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;
271 EvalScriptGuard esg(cx);
273 if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
274 esg.lookupInEvalCache(flatStr, callerScript, pc);
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);
288 const char *introducerFilename = filename;
289 if (maybeScript && maybeScript->scriptSource()->introducerFilename())
290 introducerFilename = maybeScript->scriptSource()->introducerFilename();
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;
306 esg.setNewScript(compiled);
307 }
309 return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
310 NullFramePtr() /* evalInFrame */, args.rval().address());
311 }
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);
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 }
327 // ES5 15.1.2.1 steps 2-8.
329 unsigned staticLevel = callerScript->staticLevel() + 1;
331 Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
332 if (!flatStr)
333 return false;
335 size_t length = flatStr->length();
336 ConstTwoByteChars chars(flatStr->chars(), length);
338 EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, vp);
339 if (ejr != EvalJSON_NotJSON)
340 return ejr == EvalJSON_Success;
342 EvalScriptGuard esg(cx);
344 esg.lookupInEvalCache(flatStr, callerScript, pc);
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);
355 const char *introducerFilename = filename;
356 if (maybeScript && maybeScript->scriptSource()->introducerFilename())
357 introducerFilename = maybeScript->scriptSource()->introducerFilename();
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;
373 esg.setNewScript(compiled);
374 }
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());
380 return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL),
381 NullFramePtr() /* evalInFrame */, vp.address());
382 }
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 }
396 RootedString string(cx, evalArg.toString());
397 return DirectEvalStringFromIon(cx, scopeobj, callerScript, thisValue, string, pc, vp);
398 }
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 }
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();
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());
421 RootedObject scopeChain(cx, caller.scopeChain());
422 return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc());
423 }
425 bool
426 js::IsAnyBuiltinEval(JSFunction *fun)
427 {
428 return fun->maybeNative() == IndirectEval;
429 }