michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "builtin/Eval.h" michael@0: michael@0: #include "mozilla/HashFunctions.h" michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jsonparser.h" michael@0: michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "vm/GlobalObject.h" michael@0: michael@0: #include "vm/Interpreter-inl.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::AddToHash; michael@0: using mozilla::HashString; michael@0: michael@0: // We should be able to assert this for *any* fp->scopeChain(). michael@0: static void michael@0: AssertInnerizedScopeChain(JSContext *cx, JSObject &scopeobj) michael@0: { michael@0: #ifdef DEBUG michael@0: RootedObject obj(cx); michael@0: for (obj = &scopeobj; obj; obj = obj->enclosingScope()) { michael@0: if (JSObjectOp op = obj->getClass()->ext.innerObject) { michael@0: JS_ASSERT(op(cx, obj) == obj); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: static bool michael@0: IsEvalCacheCandidate(JSScript *script) michael@0: { michael@0: // Make sure there are no inner objects which might use the wrong parent michael@0: // and/or call scope by reusing the previous eval's script. Skip the michael@0: // script's first object, which entrains the eval's scope. michael@0: return script->savedCallerFun() && michael@0: !script->hasSingletons() && michael@0: script->objects()->length == 1 && michael@0: !script->hasRegexps(); michael@0: } michael@0: michael@0: /* static */ HashNumber michael@0: EvalCacheHashPolicy::hash(const EvalCacheLookup &l) michael@0: { michael@0: return AddToHash(HashString(l.str->chars(), l.str->length()), michael@0: l.callerScript.get(), michael@0: l.version, michael@0: l.pc); michael@0: } michael@0: michael@0: /* static */ bool michael@0: EvalCacheHashPolicy::match(const EvalCacheEntry &cacheEntry, const EvalCacheLookup &l) michael@0: { michael@0: JSScript *script = cacheEntry.script; michael@0: michael@0: JS_ASSERT(IsEvalCacheCandidate(script)); michael@0: michael@0: // Get the source string passed for safekeeping in the atom map michael@0: // by the prior eval to frontend::CompileScript. michael@0: JSAtom *keyStr = script->atoms[0]; michael@0: michael@0: return EqualStrings(keyStr, l.str) && michael@0: cacheEntry.callerScript == l.callerScript && michael@0: script->getVersion() == l.version && michael@0: cacheEntry.pc == l.pc; michael@0: } michael@0: michael@0: // There are two things we want to do with each script executed in EvalKernel: michael@0: // 1. notify OldDebugAPI about script creation/destruction michael@0: // 2. add the script to the eval cache when EvalKernel is finished michael@0: // michael@0: // NB: Although the eval cache keeps a script alive wrt to the JS engine, from michael@0: // an OldDebugAPI user's perspective, we want each eval() to create and michael@0: // destroy a script. This hides implementation details and means we don't have michael@0: // to deal with calls to JS_GetScriptObject for scripts in the eval cache. michael@0: class EvalScriptGuard michael@0: { michael@0: JSContext *cx_; michael@0: Rooted script_; michael@0: michael@0: /* These fields are only valid if lookup_.str is non-nullptr. */ michael@0: EvalCacheLookup lookup_; michael@0: EvalCache::AddPtr p_; michael@0: michael@0: Rooted lookupStr_; michael@0: michael@0: public: michael@0: EvalScriptGuard(JSContext *cx) michael@0: : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {} michael@0: michael@0: ~EvalScriptGuard() { michael@0: if (script_) { michael@0: CallDestroyScriptHook(cx_->runtime()->defaultFreeOp(), script_); michael@0: script_->cacheForEval(); michael@0: EvalCacheEntry cacheEntry = {script_, lookup_.callerScript, lookup_.pc}; michael@0: lookup_.str = lookupStr_; michael@0: if (lookup_.str && IsEvalCacheCandidate(script_)) michael@0: cx_->runtime()->evalCache.relookupOrAdd(p_, lookup_, cacheEntry); michael@0: } michael@0: } michael@0: michael@0: void lookupInEvalCache(JSLinearString *str, JSScript *callerScript, jsbytecode *pc) michael@0: { michael@0: lookupStr_ = str; michael@0: lookup_.str = str; michael@0: lookup_.callerScript = callerScript; michael@0: lookup_.version = cx_->findVersion(); michael@0: lookup_.pc = pc; michael@0: p_ = cx_->runtime()->evalCache.lookupForAdd(lookup_); michael@0: if (p_) { michael@0: script_ = p_->script; michael@0: cx_->runtime()->evalCache.remove(p_); michael@0: CallNewScriptHook(cx_, script_, NullPtr()); michael@0: script_->uncacheForEval(); michael@0: } michael@0: } michael@0: michael@0: void setNewScript(JSScript *script) { michael@0: // JSScript::initFromEmitter has already called js_CallNewScriptHook. michael@0: JS_ASSERT(!script_ && script); michael@0: script_ = script; michael@0: script_->setActiveEval(); michael@0: } michael@0: michael@0: bool foundScript() { michael@0: return !!script_; michael@0: } michael@0: michael@0: HandleScript script() { michael@0: JS_ASSERT(script_); michael@0: return script_; michael@0: } michael@0: }; michael@0: michael@0: enum EvalJSONResult { michael@0: EvalJSON_Failure, michael@0: EvalJSON_Success, michael@0: EvalJSON_NotJSON michael@0: }; michael@0: michael@0: static EvalJSONResult michael@0: TryEvalJSON(JSContext *cx, JSScript *callerScript, michael@0: ConstTwoByteChars chars, size_t length, MutableHandleValue rval) michael@0: { michael@0: // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON. michael@0: // Try the JSON parser first because it's much faster. If the eval string michael@0: // isn't JSON, JSON parsing will probably fail quickly, so little time michael@0: // will be lost. michael@0: // michael@0: // Don't use the JSON parser if the caller is strict mode code, because in michael@0: // strict mode object literals must not have repeated properties, and the michael@0: // JSON parser cheerfully (and correctly) accepts them. If you're parsing michael@0: // JSON with eval and using strict mode, you deserve to be slow. michael@0: if (length > 2 && michael@0: ((chars[0] == '[' && chars[length - 1] == ']') || michael@0: (chars[0] == '(' && chars[length - 1] == ')')) && michael@0: (!callerScript || !callerScript->strict())) michael@0: { michael@0: // Remarkably, JavaScript syntax is not a superset of JSON syntax: michael@0: // strings in JavaScript cannot contain the Unicode line and paragraph michael@0: // terminator characters U+2028 and U+2029, but strings in JSON can. michael@0: // Rather than force the JSON parser to handle this quirk when used by michael@0: // eval, we simply don't use the JSON parser when either character michael@0: // appears in the provided string. See bug 657367. michael@0: for (const jschar *cp = &chars[1], *end = &chars[length - 2]; ; cp++) { michael@0: if (*cp == 0x2028 || *cp == 0x2029) michael@0: break; michael@0: michael@0: if (cp == end) { michael@0: bool isArray = (chars[0] == '['); michael@0: JSONParser parser(cx, isArray ? chars : chars + 1U, isArray ? length : length - 2, michael@0: JSONParser::NoError); michael@0: RootedValue tmp(cx); michael@0: if (!parser.parse(&tmp)) michael@0: return EvalJSON_Failure; michael@0: if (tmp.isUndefined()) michael@0: return EvalJSON_NotJSON; michael@0: rval.set(tmp); michael@0: return EvalJSON_Success; michael@0: } michael@0: } michael@0: } michael@0: return EvalJSON_NotJSON; michael@0: } michael@0: michael@0: // Define subset of ExecuteType so that casting performs the injection. michael@0: enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL }; michael@0: michael@0: // Common code implementing direct and indirect eval. michael@0: // michael@0: // Evaluate call.argv[2], if it is a string, in the context of the given calling michael@0: // frame, with the provided scope chain, with the semantics of either a direct michael@0: // or indirect eval (see ES5 10.4.2). If this is an indirect eval, scopeobj michael@0: // must be a global object. michael@0: // michael@0: // On success, store the completion value in call.rval and return true. michael@0: static bool michael@0: EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, AbstractFramePtr caller, michael@0: HandleObject scopeobj, jsbytecode *pc) michael@0: { michael@0: JS_ASSERT((evalType == INDIRECT_EVAL) == !caller); michael@0: JS_ASSERT((evalType == INDIRECT_EVAL) == !pc); michael@0: JS_ASSERT_IF(evalType == INDIRECT_EVAL, scopeobj->is()); michael@0: AssertInnerizedScopeChain(cx, *scopeobj); michael@0: michael@0: Rooted scopeObjGlobal(cx, &scopeobj->global()); michael@0: if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL); michael@0: return false; michael@0: } michael@0: michael@0: // ES5 15.1.2.1 step 1. michael@0: if (args.length() < 1) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: if (!args[0].isString()) { michael@0: args.rval().set(args[0]); michael@0: return true; michael@0: } michael@0: RootedString str(cx, args[0].toString()); michael@0: michael@0: // ES5 15.1.2.1 steps 2-8. michael@0: michael@0: // Per ES5, indirect eval runs in the global scope. (eval is specified this michael@0: // way so that the compiler can make assumptions about what bindings may or michael@0: // may not exist in the current frame if it doesn't see 'eval'.) michael@0: unsigned staticLevel; michael@0: RootedValue thisv(cx); michael@0: if (evalType == DIRECT_EVAL) { michael@0: JS_ASSERT_IF(caller.isInterpreterFrame(), !caller.asInterpreterFrame()->runningInJit()); michael@0: staticLevel = caller.script()->staticLevel() + 1; michael@0: michael@0: // Direct calls to eval are supposed to see the caller's |this|. If we michael@0: // haven't wrapped that yet, do so now, before we make a copy of it for michael@0: // the eval code to use. michael@0: if (!ComputeThis(cx, caller)) michael@0: return false; michael@0: thisv = caller.thisValue(); michael@0: } else { michael@0: JS_ASSERT(args.callee().global() == *scopeobj); michael@0: staticLevel = 0; michael@0: michael@0: // Use the global as 'this', modulo outerization. michael@0: JSObject *thisobj = JSObject::thisObject(cx, scopeobj); michael@0: if (!thisobj) michael@0: return false; michael@0: thisv = ObjectValue(*thisobj); michael@0: } michael@0: michael@0: Rooted flatStr(cx, str->ensureFlat(cx)); michael@0: if (!flatStr) michael@0: return false; michael@0: michael@0: size_t length = flatStr->length(); michael@0: ConstTwoByteChars chars(flatStr->chars(), length); michael@0: michael@0: RootedScript callerScript(cx, caller ? caller.script() : nullptr); michael@0: EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, args.rval()); michael@0: if (ejr != EvalJSON_NotJSON) michael@0: return ejr == EvalJSON_Success; michael@0: michael@0: EvalScriptGuard esg(cx); michael@0: michael@0: if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame()) michael@0: esg.lookupInEvalCache(flatStr, callerScript, pc); michael@0: michael@0: if (!esg.foundScript()) { michael@0: RootedScript maybeScript(cx); michael@0: unsigned lineno; michael@0: const char *filename; michael@0: JSPrincipals *originPrincipals; michael@0: uint32_t pcOffset; michael@0: DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset, michael@0: &originPrincipals, michael@0: evalType == DIRECT_EVAL michael@0: ? CALLED_FROM_JSOP_EVAL michael@0: : NOT_CALLED_FROM_JSOP_EVAL); michael@0: michael@0: const char *introducerFilename = filename; michael@0: if (maybeScript && maybeScript->scriptSource()->introducerFilename()) michael@0: introducerFilename = maybeScript->scriptSource()->introducerFilename(); michael@0: michael@0: CompileOptions options(cx); michael@0: options.setFileAndLine(filename, 1) michael@0: .setCompileAndGo(true) michael@0: .setForEval(true) michael@0: .setNoScriptRval(false) michael@0: .setOriginPrincipals(originPrincipals) michael@0: .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset); michael@0: SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership); michael@0: JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), michael@0: scopeobj, callerScript, options, michael@0: srcBuf, flatStr, staticLevel); michael@0: if (!compiled) michael@0: return false; michael@0: michael@0: esg.setNewScript(compiled); michael@0: } michael@0: michael@0: return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType), michael@0: NullFramePtr() /* evalInFrame */, args.rval().address()); michael@0: } michael@0: michael@0: bool michael@0: js::DirectEvalStringFromIon(JSContext *cx, michael@0: HandleObject scopeobj, HandleScript callerScript, michael@0: HandleValue thisValue, HandleString str, michael@0: jsbytecode *pc, MutableHandleValue vp) michael@0: { michael@0: AssertInnerizedScopeChain(cx, *scopeobj); michael@0: michael@0: Rooted scopeObjGlobal(cx, &scopeobj->global()); michael@0: if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL); michael@0: return false; michael@0: } michael@0: michael@0: // ES5 15.1.2.1 steps 2-8. michael@0: michael@0: unsigned staticLevel = callerScript->staticLevel() + 1; michael@0: michael@0: Rooted flatStr(cx, str->ensureFlat(cx)); michael@0: if (!flatStr) michael@0: return false; michael@0: michael@0: size_t length = flatStr->length(); michael@0: ConstTwoByteChars chars(flatStr->chars(), length); michael@0: michael@0: EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, vp); michael@0: if (ejr != EvalJSON_NotJSON) michael@0: return ejr == EvalJSON_Success; michael@0: michael@0: EvalScriptGuard esg(cx); michael@0: michael@0: esg.lookupInEvalCache(flatStr, callerScript, pc); michael@0: michael@0: if (!esg.foundScript()) { michael@0: RootedScript maybeScript(cx); michael@0: const char *filename; michael@0: unsigned lineno; michael@0: JSPrincipals *originPrincipals; michael@0: uint32_t pcOffset; michael@0: DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset, michael@0: &originPrincipals, CALLED_FROM_JSOP_EVAL); michael@0: michael@0: const char *introducerFilename = filename; michael@0: if (maybeScript && maybeScript->scriptSource()->introducerFilename()) michael@0: introducerFilename = maybeScript->scriptSource()->introducerFilename(); michael@0: michael@0: CompileOptions options(cx); michael@0: options.setFileAndLine(filename, 1) michael@0: .setCompileAndGo(true) michael@0: .setForEval(true) michael@0: .setNoScriptRval(false) michael@0: .setOriginPrincipals(originPrincipals) michael@0: .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset); michael@0: SourceBufferHolder srcBuf(chars.get(), length, SourceBufferHolder::NoOwnership); michael@0: JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), michael@0: scopeobj, callerScript, options, michael@0: srcBuf, flatStr, staticLevel); michael@0: if (!compiled) michael@0: return false; michael@0: michael@0: esg.setNewScript(compiled); michael@0: } michael@0: michael@0: // Primitive 'this' values should have been filtered out by Ion. If boxed, michael@0: // the calling frame cannot be updated to store the new object. michael@0: JS_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull()); michael@0: michael@0: return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL), michael@0: NullFramePtr() /* evalInFrame */, vp.address()); michael@0: } michael@0: michael@0: bool michael@0: js::DirectEvalValueFromIon(JSContext *cx, michael@0: HandleObject scopeobj, HandleScript callerScript, michael@0: HandleValue thisValue, HandleValue evalArg, michael@0: jsbytecode *pc, MutableHandleValue vp) michael@0: { michael@0: // Act as identity on non-strings per ES5 15.1.2.1 step 1. michael@0: if (!evalArg.isString()) { michael@0: vp.set(evalArg); michael@0: return true; michael@0: } michael@0: michael@0: RootedString string(cx, evalArg.toString()); michael@0: return DirectEvalStringFromIon(cx, scopeobj, callerScript, thisValue, string, pc, vp); michael@0: } michael@0: michael@0: bool michael@0: js::IndirectEval(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Rooted global(cx, &args.callee().global()); michael@0: return EvalKernel(cx, args, INDIRECT_EVAL, NullFramePtr(), global, nullptr); michael@0: } michael@0: michael@0: bool michael@0: js::DirectEval(JSContext *cx, const CallArgs &args) michael@0: { michael@0: // Direct eval can assume it was called from an interpreted or baseline frame. michael@0: ScriptFrameIter iter(cx); michael@0: AbstractFramePtr caller = iter.abstractFramePtr(); michael@0: michael@0: JS_ASSERT(caller.scopeChain()->global().valueIsEval(args.calleev())); michael@0: JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL || michael@0: JSOp(*iter.pc()) == JSOP_SPREADEVAL); michael@0: JS_ASSERT_IF(caller.isFunctionFrame(), michael@0: caller.compartment() == caller.callee()->compartment()); michael@0: michael@0: RootedObject scopeChain(cx, caller.scopeChain()); michael@0: return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc()); michael@0: } michael@0: michael@0: bool michael@0: js::IsAnyBuiltinEval(JSFunction *fun) michael@0: { michael@0: return fun->maybeNative() == IndirectEval; michael@0: }