Bug 604504 - Implement an eval kernel that obj_eval and JSOP_EVAL can each call. r=jorendorff,dvander
authorJeff Walden <jwalden@mit.edu>
Thu, 21 Oct 2010 14:31:29 -0700
changeset 56775 6abb9e45a79a26fd39418bd68f2cb31991aa4d47
parent 56774 5c77882891f7f2c00a201a8ae9fd6794f8d35029
child 56777 48515da9356a94dabb7e541a82a5c3f9ea7bf37c
push id16665
push userrsayre@mozilla.com
push dateSun, 31 Oct 2010 10:52:31 +0000
treeherdermozilla-central@504a46e82712 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, dvander
bugs604504
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 604504 - Implement an eval kernel that obj_eval and JSOP_EVAL can each call. r=jorendorff,dvander
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsprobes.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/StubCalls.h
js/src/tests/ecma_5/Global/eval-native-callback-is-indirect.js
js/src/tests/ecma_5/Global/jstests.list
js/src/tests/ecma_5/extensions/eval-native-callback-is-indirect.js
js/src/tests/ecma_5/extensions/jstests.list
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -1323,16 +1323,37 @@ InvokeConstructorWithGivenThis(JSContext
         ok = Invoke(cx, args, JSINVOKE_CONSTRUCT);
     }
 
     *rval = args.rval();
     return ok;
 }
 
 bool
+DirectEval(JSContext *cx, JSFunction *evalfun, uint32 argc, Value *vp)
+{
+    JS_ASSERT(vp == cx->regs->sp - argc - 2);
+    JS_ASSERT(vp[0].isObject());
+    JS_ASSERT(vp[0].toObject().isFunction());
+    JS_ASSERT(vp[0].toObject().getFunctionPrivate() == evalfun);
+    JS_ASSERT(IsBuiltinEvalFunction(evalfun));
+
+    AutoFunctionCallProbe callProbe(cx, evalfun);
+
+    JSStackFrame *caller = cx->fp();
+    JS_ASSERT(caller->isScriptFrame());
+    JSObject *scopeChain =
+        GetScopeChainFast(cx, caller, JSOP_EVAL, JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);
+    if (!scopeChain || !EvalKernel(cx, argc, vp, DIRECT_EVAL, caller, scopeChain))
+        return false;
+    cx->regs->sp = vp + 1;
+    return true;
+}
+
+bool
 ValueToId(JSContext *cx, const Value &v, jsid *idp)
 {
     int32_t i;
     if (ValueFitsInInt32(v, &i) && INT_FITS_IN_JSID(i)) {
         *idp = INT_TO_JSID(i);
         return true;
     }
 
@@ -4627,21 +4648,17 @@ BEGIN_CASE(JSOP_EVAL)
 
     if (!IsFunctionObject(*vp, &callee))
         goto call_using_invoke;
 
     newfun = callee->getFunctionPrivate();
     if (!IsBuiltinEvalFunction(newfun))
         goto not_direct_eval;
 
-    Probes::enterJSFun(cx, newfun);
-    JSBool ok = CallJSNative(cx, newfun->u.n.native, argc, vp);
-    Probes::exitJSFun(cx, newfun);
-    regs.sp = vp + 1;
-    if (!ok)
+    if (!DirectEval(cx, newfun, argc, vp))
         goto error;
 }
 END_CASE(JSOP_EVAL)
 
 BEGIN_CASE(JSOP_CALL)
 BEGIN_CASE(JSOP_APPLY)
 {
     argc = GET_ARGC(regs.pc);
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -969,16 +969,26 @@ ExternalGetOrSet(JSContext *cx, JSObject
 extern JS_REQUIRES_STACK bool
 InvokeConstructor(JSContext *cx, const CallArgs &args);
 
 extern JS_REQUIRES_STACK bool
 InvokeConstructorWithGivenThis(JSContext *cx, JSObject *thisobj, const Value &fval,
                                uintN argc, Value *argv, Value *rval);
 
 /*
+ * Performs a direct eval for the given arguments, which must correspond to the
+ * currently-executing stack frame, which must be a script frame.  evalfun must
+ * be the built-in eval function and must correspond to the callee in vp[0].
+ * When this function succeeds it returns the result in *vp, adjusts the JS
+ * stack pointer, and returns true.
+ */
+extern JS_REQUIRES_STACK bool
+DirectEval(JSContext *cx, JSFunction *evalfun, uint32 argc, Value *vp);
+
+/*
  * Executes a script with the given scope chain in the context of the given
  * frame.
  */
 extern JS_FORCES_STACK bool
 Execute(JSContext *cx, JSObject *chain, JSScript *script,
         JSStackFrame *prev, uintN flags, Value *result);
 
 /*
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -926,43 +926,37 @@ js_CheckPrincipalsAccess(JSContext *cx, 
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                  JSMSG_BAD_INDIRECT_CALL, callerstr);
             return JS_FALSE;
         }
     }
     return JS_TRUE;
 }
 
-static JSObject *
-CheckScopeChainValidity(JSContext *cx, JSObject *scopeobj, const char *caller)
-{
-    JSObject *inner;
-
-    if (!scopeobj)
-        goto bad;
-
-    OBJ_TO_INNER_OBJECT(cx, scopeobj);
-    if (!scopeobj)
-        return NULL;
+static bool
+CheckScopeChainValidity(JSContext *cx, JSObject *scopeobj)
+{
+    JSObject *inner = scopeobj;
+    OBJ_TO_INNER_OBJECT(cx, inner);
+    if (!inner)
+        return false;
+    JS_ASSERT(inner == scopeobj);
 
     /* XXX This is an awful gross hack. */
-    inner = scopeobj;
     while (scopeobj) {
         JSObjectOp op = scopeobj->getClass()->ext.innerObject;
-        if (op && op(cx, scopeobj) != scopeobj)
-            goto bad;
+        if (op && op(cx, scopeobj) != scopeobj) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_INDIRECT_CALL,
+                                 js_eval_str);
+            return false;
+        }
         scopeobj = scopeobj->getParent();
     }
 
-    return inner;
-
-bad:
-    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                         JSMSG_BAD_INDIRECT_CALL, caller);
-    return NULL;
+    return true;
 }
 
 const char *
 js_ComputeFilename(JSContext *cx, JSStackFrame *caller,
                    JSPrincipals *principals, uintN *linenop)
 {
     uint32 flags;
 #ifdef DEBUG
@@ -1088,151 +1082,161 @@ EvalCacheLookup(JSContext *cx, JSString 
     }
     return NULL;
 }
 
 /* ES5 15.1.2.1. */
 static JSBool
 eval(JSContext *cx, uintN argc, Value *vp)
 {
-    if (argc < 1) {
-        vp->setUndefined();
-        return true;
-    }
+    /*
+     * NB: This method handles only indirect eval: direct eval is handled by
+     *     JSOP_EVAL.
+     */
 
     JSStackFrame *caller = js_GetScriptedCaller(cx, NULL);
+
+    /* FIXME Bug 602994: This really should be perfectly cromulent. */
     if (!caller) {
         /* Eval code needs to inherit principals from the caller. */
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_BAD_INDIRECT_CALL, js_eval_str);
         return false;
     }
 
-    jsbytecode *callerPC = caller->pc(cx);
-    bool directCall = (callerPC && js_GetOpcode(cx, caller->script(), callerPC) == JSOP_EVAL);
-
-    /*
-     * If the callee was originally a cross-compartment wrapper, this is an
-     * indirect call.
-     */
-    if (directCall && caller->scopeChain().compartment() != vp[0].toObject().compartment())
-        directCall = false;
-
+    return EvalKernel(cx, argc, vp, INDIRECT_EVAL, caller, vp[0].toObject().getGlobal());
+}
+
+namespace js {
+
+bool
+EvalKernel(JSContext *cx, uintN argc, Value *vp, EvalType evalType, JSStackFrame *caller,
+           JSObject *scopeobj)
+{
     /*
-     * Direct calls to eval are supposed to see the caller's |this|. If we
-     * haven't wrapped that yet, do so now, before we make a copy of it for
-     * the eval code to use.
+     * FIXME Bug 602994: Calls with no scripted caller should be permitted and
+     *       should be implemented as indirect calls.
      */
-    if (!caller->computeThis(cx))
-        return false;
-        
-    Value *argv = JS_ARGV(cx, vp);
-    if (!argv[0].isString()) {
-        *vp = argv[0];
-        return true;
-    }
+    JS_ASSERT(caller);
+    JS_ASSERT(scopeobj);
 
     /*
      * We once supported a second argument to eval to use as the scope chain
      * when evaluating the code string.  Warn when such uses are seen so that
      * authors will know that support for eval(s, o) has been removed.
      */
-    if (argc > 1 && !caller->script()->warnedAboutTwoArgumentEval) {
+    JSScript *callerScript = caller->script();
+    if (argc > 1 && !callerScript->warnedAboutTwoArgumentEval) {
         static const char TWO_ARGUMENT_WARNING[] =
             "Support for eval(code, scopeObject) has been removed. "
             "Use |with (scopeObject) eval(code);| instead.";
         if (!JS_ReportWarning(cx, TWO_ARGUMENT_WARNING))
             return false;
-        caller->script()->warnedAboutTwoArgumentEval = true;
-    }
-
-    /*
-     * Per ES5, if we see an indirect call, then run in the global scope.
-     * (eval is specified this way so that the compiler can make assumptions
-     * about what bindings may or may not exist in the current frame if it
-     * doesn't see 'eval'.)
-     */
-    uintN staticLevel;
-    JSObject *scopeobj;
-    if (directCall) {
-        /* Compile using the caller's current scope object. */
-        staticLevel = caller->script()->staticLevel + 1;
-        scopeobj = GetScopeChainFast(cx, caller, JSOP_EVAL,
-                                     JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);
-        if (!scopeobj)
-            return false;
-
-        JS_ASSERT_IF(caller->isFunctionFrame(), caller->hasCallObj());
-    } else {
-        /* Pretend that we're top level. */
-        staticLevel = 0;
-        scopeobj = vp[0].toObject().getGlobal();
-    }
-
-    /* Ensure we compile this eval with the right object in the scope chain. */
-    JSObject *result = CheckScopeChainValidity(cx, scopeobj, js_eval_str);
-    if (!result)
-        return false;
-    JS_ASSERT(result == scopeobj);
+        callerScript->warnedAboutTwoArgumentEval = true;
+    }
 
     /*
      * CSP check: Is eval() allowed at all?
      * Report errors via CSP is done in the script security mgr.
      */
     if (!js_CheckContentSecurityPolicy(cx)) {
         JS_ReportError(cx, "call to eval() blocked by CSP");
         return false;
     }
 
-    JSObject *callee = &vp[0].toObject();
+    /* ES5 15.1.2.1 step 1. */
+    if (argc < 1) {
+        vp->setUndefined();
+        return true;
+    }
+    if (!vp[2].isString()) {
+        *vp = vp[2];
+        return true;
+    }
+    JSString *str = vp[2].toString();
+
+    /* ES5 15.1.2.1 steps 2-8. */
+    JSObject *callee = JSVAL_TO_OBJECT(JS_CALLEE(cx, Jsvalify(vp)));
+    JS_ASSERT(IsBuiltinEvalFunction(callee->getFunctionPrivate()));
     JSPrincipals *principals = js_EvalFramePrincipals(cx, callee, caller);
-    uintN line;
-    const char *file = js_ComputeFilename(cx, caller, principals, &line);
-
-    JSString *str = argv[0].toString();
-    JSScript *script = NULL;
+
+    /*
+     * Per ES5, indirect eval runs in the global scope. (eval is specified this
+     * way so that the compiler can make assumptions about what bindings may or
+     * may not exist in the current frame if it doesn't see 'eval'.)
+     */
+    uintN staticLevel;
+    if (evalType == DIRECT_EVAL) {
+        staticLevel = caller->script()->staticLevel + 1;
+
+#ifdef DEBUG
+        jsbytecode *callerPC = caller->pc(cx);
+        JS_ASSERT_IF(caller->isFunctionFrame(), caller->hasCallObj());
+        JS_ASSERT(callerPC && js_GetOpcode(cx, caller->script(), callerPC) == JSOP_EVAL);
+#endif
+    } else {
+        /* Pretend that we're top level. */
+        staticLevel = 0;
+
+        JS_ASSERT(scopeobj == scopeobj->getGlobal());
+        JS_ASSERT(scopeobj->isGlobal());
+    }
+
+    /* Ensure we compile this eval with the right object in the scope chain. */
+    if (!CheckScopeChainValidity(cx, scopeobj))
+        return false;
 
     const jschar *chars;
     size_t length;
     str->getCharsAndLength(chars, length);
 
     /*
      * If the eval string starts with '(' and ends with ')', it may be JSON.
      * Try the JSON parser first because it's much faster.  If the eval string
      * isn't JSON, JSON parsing will probably fail quickly, so little time
      * will be lost.
      */
-    if (length > 2 && chars[0] == '(' && chars[length-1] == ')') {
+    if (length > 2 && chars[0] == '(' && chars[length - 1] == ')') {
         JSONParser *jp = js_BeginJSONParse(cx, vp, /* suppressErrors = */true);
-        JSBool ok = jp != NULL;
-        if (ok) {
+        if (jp != NULL) {
             /* Run JSON-parser on string inside ( and ). */
-            ok = js_ConsumeJSONText(cx, jp, chars+1, length-2);
+            JSBool ok = js_ConsumeJSONText(cx, jp, chars + 1, length - 2);
             ok &= js_FinishJSONParse(cx, jp, NullValue());
             if (ok)
                 return true;
         }
     }
 
+    /*
+     * Direct calls to eval are supposed to see the caller's |this|. If we
+     * haven't wrapped that yet, do so now, before we make a copy of it for
+     * the eval code to use.
+     */
+    if (evalType == DIRECT_EVAL && !caller->computeThis(cx))
+        return false;
+
+    JSScript *script = NULL;
     JSScript **bucket = EvalCacheHash(cx, str);
-    if (directCall && caller->isFunctionFrame())
+    if (evalType == DIRECT_EVAL && caller->isFunctionFrame())
         script = EvalCacheLookup(cx, str, caller, staticLevel, principals, scopeobj, bucket);
 
     /*
-     * We can't have a callerFrame (down in js_Execute's terms) if we're in
-     * global code. This includes indirect eval and direct eval called with a
-     * scope object parameter.
+     * We can't have a callerFrame (down in js::Execute's terms) if we're in
+     * global code (or if we're an indirect eval).
      */
     JSStackFrame *callerFrame = (staticLevel != 0) ? caller : NULL;
     if (!script) {
+        uintN lineno;
+        const char *filename = js_ComputeFilename(cx, caller, principals, &lineno);
+
         uint32 tcflags = TCF_COMPILE_N_GO | TCF_NEED_MUTABLE_SCRIPT | TCF_COMPILE_FOR_EVAL;
         script = Compiler::compileScript(cx, scopeobj, callerFrame,
                                          principals, tcflags,
                                          chars, length,
-                                         NULL, file, line, str, staticLevel);
+                                         NULL, filename, lineno, str, staticLevel);
         if (!script)
             return false;
     }
 
     assertSameCompartment(cx, scopeobj, script);
 
     /*
      * Belt-and-braces: check that the lesser of eval's principals and the
@@ -1246,18 +1250,16 @@ eval(JSContext *cx, uintN argc, Value *v
     *bucket = script;
 #ifdef CHECK_SCRIPT_OWNER
     script->owner = NULL;
 #endif
 
     return ok;
 }
 
-namespace js {
-
 bool
 IsBuiltinEvalFunction(JSFunction *fun)
 {
     return fun->maybeNative() == eval;
 }
 
 }
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1712,13 +1712,29 @@ js_Object(JSContext *cx, uintN argc, js:
 namespace js {
 
 extern bool
 SetProto(JSContext *cx, JSObject *obj, JSObject *proto, bool checkForCycles);
 
 extern JSString *
 obj_toStringHelper(JSContext *cx, JSObject *obj);
 
+enum EvalType { INDIRECT_EVAL, DIRECT_EVAL };
+
+/*
+ * Common code implementing direct and indirect eval.
+ *
+ * Evaluate vp[2], if it is a string, in the context of the given calling
+ * frame, with the provided scope chain, with the semantics of either a direct
+ * or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
+ * must be a global object.
+ *
+ * On success, store the completion value in *vp and return true.
+ */
+extern bool
+EvalKernel(JSContext *cx, uintN argc, js::Value *vp, EvalType evalType, JSStackFrame *caller,
+           JSObject *scopeobj);
+
 extern bool
 IsBuiltinEvalFunction(JSFunction *fun);
 
 }
 #endif /* jsobj_h___ */
--- a/js/src/jsprobes.h
+++ b/js/src/jsprobes.h
@@ -211,11 +211,30 @@ inline void Probes::GCEnd(JSCompartment 
 inline void Probes::GCStartMarkPhase(JSCompartment *compartment) {}
 inline void Probes::GCEndMarkPhase(JSCompartment *compartment) {}
 inline void Probes::GCStartSweepPhase(JSCompartment *compartment) {}
 inline void Probes::GCEndSweepPhase(JSCompartment *compartment) {}
 inline JSBool Probes::CustomMark(JSString *string) { return JS_TRUE; }
 inline JSBool Probes::CustomMark(const char *string) { return JS_TRUE; }
 inline JSBool Probes::CustomMark(int marker) { return JS_TRUE; }
 
+struct AutoFunctionCallProbe {
+    JSContext * const cx;
+    JSFunction *fun;
+    js::Value *lval;
+    JS_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+    AutoFunctionCallProbe(JSContext *cx, JSFunction *fun, js::Value *lval = NULL
+                          JS_GUARD_OBJECT_NOTIFIER_PARAM)
+      : cx(cx), fun(fun), lval(lval)
+    {
+        JS_GUARD_OBJECT_NOTIFIER_INIT;
+        Probes::enterJSFun(cx, fun, lval);
+    }
+
+    ~AutoFunctionCallProbe() {
+        Probes::exitJSFun(cx, fun, lval);
+    }
+};
+
 } /* namespace js */
     
 #endif /* _JSPROBES_H */
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1206,17 +1206,21 @@ mjit::Compiler::generateMethod()
             prepareStubCall(Uses(0));
             masm.move(Imm32(fullAtomIndex(PC)), Registers::ArgReg1);
             stubCall(stubs::CallName);
             frame.pushSynced();
             frame.pushSynced();
           END_CASE(JSOP_CALLNAME)
 
           BEGIN_CASE(JSOP_EVAL)
-            jsop_eval();
+          {
+            JaegerSpew(JSpew_Insns, " --- EVAL --- \n");
+            emitEval(GET_ARGC(PC));
+            JaegerSpew(JSpew_Insns, " --- END EVAL --- \n");
+          }
           END_CASE(JSOP_EVAL)
 
           BEGIN_CASE(JSOP_CALL)
           BEGIN_CASE(JSOP_APPLY)
           {
             JaegerSpew(JSpew_Insns, " --- SCRIPTED CALL --- \n");
             inlineCallHelper(GET_ARGC(PC), false);
             JaegerSpew(JSpew_Insns, " --- END SCRIPTED CALL --- \n");
@@ -4323,21 +4327,27 @@ mjit::Compiler::jsop_instanceof()
 
     if (firstSlow.isSet())
         firstSlow.getJump().linkTo(stubcc.masm.label(), &stubcc.masm);
     stubcc.rejoin(Changes(1));
     return true;
 }
 
 void
-mjit::Compiler::jsop_eval()
+mjit::Compiler::emitEval(uint32 argc)
 {
-    JaegerSpew(JSpew_Insns, " --- SCRIPTED CALL --- \n");
-    inlineCallHelper(GET_ARGC(PC), false);
-    JaegerSpew(JSpew_Insns, " --- END SCRIPTED CALL --- \n");
+    /* Check for interrupts on function call */
+    interruptCheckHelper();
+
+    frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2));
+    prepareStubCall(Uses(argc + 2));
+    masm.move(Imm32(argc), Registers::ArgReg1);
+    stubCall(stubs::Eval);
+    frame.popn(argc + 2);
+    frame.pushSynced();
 }
 
 /*
  * Note: This function emits tracer hooks into the OOL path. This means if
  * it is used in the middle of an in-progress slow path, the stream will be
  * hopelessly corrupted. Take care to only call this before linkExits() and
  * after rejoin()s.
  */
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -363,17 +363,17 @@ class Compiler : public BaseCompiler
     bool jsop_callprop_obj(JSAtom *atom);
     bool jsop_callprop_str(JSAtom *atom);
     bool jsop_callprop_generic(JSAtom *atom);
     bool jsop_instanceof();
     void jsop_name(JSAtom *atom);
     bool jsop_xname(JSAtom *atom);
     void enterBlock(JSObject *obj);
     void leaveBlock();
-    void jsop_eval();
+    void emitEval(uint32 argc);
 
     /* Fast arithmetic. */
     void jsop_binary(JSOp op, VoidStub stub);
     void jsop_binary_full(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub);
     void jsop_binary_full_simple(FrameEntry *fe, JSOp op, VoidStub stub);
     void jsop_binary_double(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub);
     void slowLoadConstantDouble(Assembler &masm, FrameEntry *fe,
                                 FPRegisterID fpreg);
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -437,16 +437,40 @@ stubs::UncachedNewHelper(VMFrame &f, uin
 void * JS_FASTCALL
 stubs::UncachedCall(VMFrame &f, uint32 argc)
 {
     UncachedCallResult ucr;
     UncachedCallHelper(f, argc, &ucr);
     return ucr.codeAddr;
 }
 
+void JS_FASTCALL
+stubs::Eval(VMFrame &f, uint32 argc)
+{
+    Value *vp = f.regs.sp - (argc + 2);
+
+    JSObject *callee;
+    JSFunction *fun;
+
+    if (!IsFunctionObject(*vp, &callee) ||
+        !IsBuiltinEvalFunction((fun = callee->getFunctionPrivate())))
+    {
+        if (!ComputeThisFromVpInPlace(f.cx, vp) ||
+            !Invoke(f.cx, InvokeArgsAlreadyOnTheStack(vp, argc), 0))
+        {
+            THROW();
+        }
+        return;
+    }
+
+    JS_ASSERT(f.regs.fp == f.cx->fp());
+    if (!DirectEval(f.cx, fun, argc, vp))
+        THROW();
+}
+
 void
 stubs::UncachedCallHelper(VMFrame &f, uint32 argc, UncachedCallResult *ucr)
 {
     ucr->init();
 
     JSContext *cx = f.cx;
     Value *vp = f.regs.sp - (argc + 2);
 
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -60,16 +60,17 @@ void JS_FASTCALL InitMethod(VMFrame &f, 
 
 void JS_FASTCALL HitStackQuota(VMFrame &f);
 void * JS_FASTCALL FixupArity(VMFrame &f, uint32 argc);
 void * JS_FASTCALL CompileFunction(VMFrame &f, uint32 argc);
 void JS_FASTCALL SlowNew(VMFrame &f, uint32 argc);
 void JS_FASTCALL SlowCall(VMFrame &f, uint32 argc);
 void * JS_FASTCALL UncachedNew(VMFrame &f, uint32 argc);
 void * JS_FASTCALL UncachedCall(VMFrame &f, uint32 argc);
+void JS_FASTCALL Eval(VMFrame &f, uint32 argc);
 void JS_FASTCALL EnterScript(VMFrame &f);
 void JS_FASTCALL LeaveScript(VMFrame &f);
 
 /*
  * Result struct for UncachedXHelper.
  *
  * These functions can have one of two results:
  *
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Global/eval-native-callback-is-indirect.js
@@ -0,0 +1,32 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 604504;
+var summary = "eval called from a native function is indirect";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var originalEval = eval;
+
+var global = this;
+var directCheckCode = "this === global";
+
+function testBound()
+{
+  var global = "psych!";
+  var eval = originalEval.bind(undefined, directCheckCode);
+  assertEq(eval(), true);
+}
+testBound();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("All tests passed!");
--- a/js/src/tests/ecma_5/Global/jstests.list
+++ b/js/src/tests/ecma_5/Global/jstests.list
@@ -1,6 +1,7 @@
 url-prefix ../../jsreftest.html?test=ecma_5/Global/
 script parseInt-01.js
 script eval-01.js
 script eval-02.js
 script eval-inside-with-is-direct.js
 script parenthesized-eval-is-direct.js
+script eval-native-callback-is-indirect.js
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/extensions/eval-native-callback-is-indirect.js
@@ -0,0 +1,43 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 604504;
+var summary = "eval called from a native function is indirect";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var originalEval = eval;
+
+var global = this;
+var directCheckCode = "this === global";
+
+function testArrayGeneric()
+{
+  var global = "psych!";
+  var eval = Array.map;
+
+  var mapped = eval([directCheckCode], originalEval);
+  assertEq(mapped[0], true);
+}
+
+function testStringGeneric()
+{
+  var global = "psych!";
+  var eval = String.replace;
+
+  var newString = eval(directCheckCode, directCheckCode, originalEval);
+  assertEq(newString, "true");
+}
+testStringGeneric();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("All tests passed!");
--- a/js/src/tests/ecma_5/extensions/jstests.list
+++ b/js/src/tests/ecma_5/extensions/jstests.list
@@ -2,9 +2,10 @@ url-prefix ../../jsreftest.html?test=ecm
 script 15.4.4.11.js
 script 8.12.5-01.js
 script Boolean-toSource.js
 script Number-toSource.js
 script String-toSource.js
 script proxy-strict.js
 script regress-bug567606.js
 script string-literal-getter-setter-decompilation.js
+script eval-native-callback-is-indirect.js
 script regress-bug607284.js