Bug 494235: wrap escaping optimized closures for the debugger API (r=igor/mrbkap).

This commit is contained in:
Brendan Eich 2009-06-04 18:58:47 -07:00
Родитель 6e088c83e0
Коммит 3442a335eb
15 изменённых файлов: 526 добавлений и 59 удалений

Просмотреть файл

@ -949,6 +949,11 @@ uint8 js_opcode2extra[JSOP_LIMIT] = {
0, /* JSOP_LAMBDA_FC */
0, /* JSOP_OBJTOP */
0, /* JSOP_LOOP */
0, /* JSOP_GETUPVAR_DBG */
0, /* JSOP_CALLUPVAR_DBG */
0, /* JSOP_DEFFUN_DBGFC */
0, /* JSOP_DEFLOCALFUN_DBGFC */
0, /* JSOP_LAMBDA_DBGFC */
};
#define JSOP_IS_IMACOP(x) (0 \
|| x == JSOP_BITOR \

Просмотреть файл

@ -192,7 +192,7 @@ MSG_DEF(JSMSG_SEMI_BEFORE_STMNT, 109, 0, JSEXN_SYNTAXERR, "missing ; before
MSG_DEF(JSMSG_NO_RETURN_VALUE, 110, 1, JSEXN_TYPEERR, "function {0} does not always return a value")
MSG_DEF(JSMSG_DUPLICATE_FORMAL, 111, 1, JSEXN_TYPEERR, "duplicate formal argument {0}")
MSG_DEF(JSMSG_EQUAL_AS_ASSIGN, 112, 1, JSEXN_SYNTAXERR, "test for equality (==) mistyped as assignment (=)?{0}")
MSG_DEF(JSMSG_UNUSED113, 113, 0, JSEXN_NONE, "unused113")
MSG_DEF(JSMSG_OPTIMIZED_CLOSURE_LEAK, 113, 0, JSEXN_INTERNALERR, "cannot access optimized closure")
MSG_DEF(JSMSG_TOO_MANY_DEFAULTS, 114, 0, JSEXN_SYNTAXERR, "more than one switch default")
MSG_DEF(JSMSG_TOO_MANY_CASES, 115, 0, JSEXN_INTERNALERR, "too many switch cases")
MSG_DEF(JSMSG_BAD_SWITCH, 116, 0, JSEXN_SYNTAXERR, "invalid switch statement")
@ -309,6 +309,4 @@ MSG_DEF(JSMSG_EVAL_ARITY, 226, 0, JSEXN_TYPEERR, "eval accepts only
MSG_DEF(JSMSG_MISSING_FUN_ARG, 227, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
MSG_DEF(JSMSG_JSON_BAD_PARSE, 228, 0, JSEXN_SYNTAXERR, "JSON.parse")
MSG_DEF(JSMSG_JSON_BAD_STRINGIFY, 229, 0, JSEXN_ERR, "JSON.stringify")
MSG_DEF(JSMSG_XDR_CLOSURE_WRAPPER, 230, 1, JSEXN_INTERNALERR, "can't XDR closure wrapper for function {0}")

Просмотреть файл

@ -646,11 +646,7 @@ JS_TypeOfValue(JSContext *cx, jsval v)
type = JSTYPE_OBJECT; /* XXXbe JSTYPE_NULL for JS2 */
obj = JSVAL_TO_OBJECT(v);
if (obj) {
JSObject *wrapped;
wrapped = js_GetWrappedObject(cx, obj);
if (wrapped)
obj = wrapped;
obj = js_GetWrappedObject(cx, obj);
ops = obj->map->ops;
#if JS_HAS_XML_SUPPORT
@ -1362,11 +1358,11 @@ JS_InitStandardClasses(JSContext *cx, JSObject *obj)
}
#define CLASP(name) (&js_##name##Class)
#define EXT_CLASP(name) (&js_##name##Class.base)
#define XCLASP(name) (&js_##name##Class.base)
#define EAGER_ATOM(name) ATOM_OFFSET(name), NULL
#define EAGER_CLASS_ATOM(name) CLASS_ATOM_OFFSET(name), NULL
#define EAGER_ATOM_AND_CLASP(name) EAGER_CLASS_ATOM(name), CLASP(name)
#define EAGER_ATOM_AND_EXT_CLASP(name) EAGER_CLASS_ATOM(name), EXT_CLASP(name)
#define EAGER_ATOM_AND_XCLASP(name) EAGER_CLASS_ATOM(name), XCLASP(name)
#define LAZY_ATOM(name) ATOM_OFFSET(lazy.name), js_##name##_str
typedef struct JSStdName {
@ -1415,8 +1411,8 @@ static JSStdName standard_class_atoms[] = {
#endif
#if JS_HAS_XML_SUPPORT
{js_InitXMLClass, EAGER_ATOM_AND_CLASP(XML)},
{js_InitNamespaceClass, EAGER_ATOM_AND_EXT_CLASP(Namespace)},
{js_InitQNameClass, EAGER_ATOM_AND_EXT_CLASP(QName)},
{js_InitNamespaceClass, EAGER_ATOM_AND_XCLASP(Namespace)},
{js_InitQNameClass, EAGER_ATOM_AND_XCLASP(QName)},
#endif
#if JS_HAS_FILE_OBJECT
{js_InitFileClass, EAGER_ATOM_AND_CLASP(File)},

Просмотреть файл

@ -684,7 +684,6 @@ js_watch_set_wrapper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval userid;
funobj = JSVAL_TO_OBJECT(argv[-2]);
JS_ASSERT(OBJ_GET_CLASS(cx, funobj) == &js_FunctionClass);
wrapper = GET_FUNCTION_PRIVATE(cx, funobj);
userid = ATOM_KEY(wrapper->atom);
*rval = argv[0];
@ -1156,7 +1155,7 @@ JS_GetFrameFunctionObject(JSContext *cx, JSStackFrame *fp)
if (!fp->fun)
return NULL;
JS_ASSERT(OBJ_GET_CLASS(cx, fp->callee) == &js_FunctionClass);
JS_ASSERT(HAS_FUNCTION_CLASS(fp->callee));
JS_ASSERT(OBJ_GET_PRIVATE(cx, fp->callee) == fp->fun);
return fp->callee;
}
@ -1261,11 +1260,28 @@ JS_EvaluateUCInStackFrame(JSContext *cx, JSStackFrame *fp,
TCF_PUT_STATIC_LEVEL(JS_DISPLAY_SIZE),
chars, length, NULL,
filename, lineno);
if (!script)
return JS_FALSE;
JSStackFrame *displayCopy[JS_DISPLAY_SIZE];
if (cx->fp != fp) {
memcpy(displayCopy, cx->display, sizeof displayCopy);
/* This API requires an active fp on cx, so fp2 can't go null here. */
for (JSStackFrame *fp2 = cx->fp; fp2 != fp; fp2 = fp2->down) {
if (fp2->displaySave) {
JS_ASSERT(fp2->script->staticLevel < JS_DISPLAY_SIZE);
cx->display[fp2->script->staticLevel] = fp2->displaySave;
}
}
}
ok = js_Execute(cx, scobj, script, fp, JSFRAME_DEBUGGER | JSFRAME_EVAL,
rval);
if (cx->fp != fp)
memcpy(cx->display, displayCopy, sizeof cx->display);
js_DestroyScript(cx, script);
return ok;
}

Просмотреть файл

@ -2127,6 +2127,7 @@ BindNameToSlot(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
JS_ASSERT(cg->flags & TCF_IN_FUNCTION);
JS_ASSERT(cg->lexdeps.lookup(atom));
JS_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
JS_ASSERT(cg->fun->u.i.skipmin <= skip);
/*
* If op is a mutating opcode, this upvar's static level is too big to

Просмотреть файл

@ -245,7 +245,19 @@ js_GetArgsObject(JSContext *cx, JSStackFrame *fp)
* We must be in a function activation; the function must be lightweight
* or else fp must have a variable object.
*/
JS_ASSERT(fp->fun && (!(fp->fun->flags & JSFUN_HEAVYWEIGHT) || fp->varobj));
JSFunction *fun = fp->fun;
JS_ASSERT(fun && (!(fun->flags & JSFUN_HEAVYWEIGHT) || fp->varobj));
/*
* Unlike FUN_ESCAPE_HAZARD(fun), we test here only for null closures, not
* flat closures -- flat ones are inherently escaping, so arguments.callee
* references are fine.
*/
if (FUN_NULL_CLOSURE(fun) && fun->u.i.skipmin != 0) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_OPTIMIZED_CLOSURE_LEAK);
return JS_FALSE;
}
/* Skip eval and debugger frames. */
while (fp->flags & JSFRAME_SPECIAL)
@ -365,6 +377,175 @@ args_delProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
return JS_TRUE;
}
static JS_REQUIRES_STACK JSObject *
WrapEscapingClosure(JSContext *cx, JSStackFrame *fp, JSObject *funobj, JSFunction *fun)
{
JS_ASSERT(GET_FUNCTION_PRIVATE(cx, funobj) == fun);
JS_ASSERT(FUN_OPTIMIZED_CLOSURE(fun));
JS_ASSERT(!fun->u.i.wrapper);
/*
* We do not attempt to reify Call and Block objects on demand for outer
* scopes. This could be done (see the "v8" patch in bug 494235) but it is
* fragile in the face of ongoing compile-time optimization. Instead, the
* _DBG* opcodes used by wrappers created here must cope with unresolved
* upvars and throw them as reference errors. Caveat debuggers!
*/
JSObject *scopeChain = js_GetScopeChain(cx, fp);
if (!scopeChain)
return NULL;
JSObject *wfunobj = js_NewObjectWithGivenProto(cx, &js_FunctionClass,
funobj, scopeChain, 0);
if (!wfunobj)
return NULL;
JSAutoTempValueRooter tvr(cx, wfunobj);
JSFunction *wfun = (JSFunction *) wfunobj;
wfunobj->fslots[JSSLOT_PRIVATE] = PRIVATE_TO_JSVAL(wfun);
wfun->nargs = 0;
wfun->flags = fun->flags | JSFUN_HEAVYWEIGHT;
wfun->u.i.nvars = 0;
wfun->u.i.nupvars = 0;
wfun->u.i.skipmin = fun->u.i.skipmin;
wfun->u.i.wrapper = true;
wfun->u.i.script = NULL;
wfun->u.i.names.taggedAtom = NULL;
wfun->atom = fun->atom;
if (fun->hasLocalNames()) {
void *mark = JS_ARENA_MARK(&cx->tempPool);
jsuword *names = js_GetLocalNameArray(cx, fun, &cx->tempPool);
if (!names)
return NULL;
JSBool ok = true;
for (uintN i = 0, n = fun->countLocalNames(); i != n; i++) {
jsuword name = names[i];
JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(name);
JSLocalKind localKind = (i < fun->nargs)
? JSLOCAL_ARG
: (i < fun->countArgsAndVars())
? (JS_LOCAL_NAME_IS_CONST(name)
? JSLOCAL_CONST
: JSLOCAL_VAR)
: JSLOCAL_UPVAR;
ok = js_AddLocal(cx, wfun, atom, localKind);
if (!ok)
break;
}
JS_ARENA_RELEASE(&cx->tempPool, mark);
if (!ok)
return NULL;
JS_ASSERT(wfun->nargs == fun->nargs);
JS_ASSERT(wfun->u.i.nvars == fun->u.i.nvars);
JS_ASSERT(wfun->u.i.nupvars == fun->u.i.nupvars);
js_FreezeLocalNames(cx, wfun);
}
JSScript *script = fun->u.i.script;
jssrcnote *snbase = SCRIPT_NOTES(script);
jssrcnote *sn = snbase;
while (!SN_IS_TERMINATOR(sn))
sn = SN_NEXT(sn);
uintN nsrcnotes = (sn - snbase) + 1;
/* NB: GC must not occur before wscript is homed in wfun->u.i.script. */
JSScript *wscript = js_NewScript(cx, script->length, nsrcnotes,
script->atomMap.length,
(script->objectsOffset != 0)
? JS_SCRIPT_OBJECTS(script)->length
: 0,
fun->u.i.nupvars,
(script->regexpsOffset != 0)
? JS_SCRIPT_REGEXPS(script)->length
: 0,
(script->trynotesOffset != 0)
? JS_SCRIPT_TRYNOTES(script)->length
: 0);
if (!wscript)
return NULL;
memcpy(wscript->code, script->code, script->length);
wscript->main = wscript->code + (script->main - script->code);
memcpy(SCRIPT_NOTES(wscript), snbase, nsrcnotes);
memcpy(wscript->atomMap.vector, script->atomMap.vector,
wscript->atomMap.length * sizeof(JSAtom *));
if (script->objectsOffset != 0) {
memcpy(JS_SCRIPT_OBJECTS(wscript)->vector, JS_SCRIPT_OBJECTS(script)->vector,
JS_SCRIPT_OBJECTS(wscript)->length * sizeof(JSObject *));
}
if (script->regexpsOffset != 0) {
memcpy(JS_SCRIPT_REGEXPS(wscript)->vector, JS_SCRIPT_REGEXPS(script)->vector,
JS_SCRIPT_REGEXPS(wscript)->length * sizeof(JSObject *));
}
if (script->trynotesOffset != 0) {
memcpy(JS_SCRIPT_TRYNOTES(wscript)->vector, JS_SCRIPT_TRYNOTES(script)->vector,
JS_SCRIPT_TRYNOTES(wscript)->length * sizeof(JSTryNote));
}
if (wfun->u.i.nupvars != 0) {
JS_ASSERT(wfun->u.i.nupvars == JS_SCRIPT_UPVARS(wscript)->length);
memcpy(JS_SCRIPT_UPVARS(wscript)->vector, JS_SCRIPT_UPVARS(script)->vector,
wfun->u.i.nupvars * sizeof(uint32));
}
jsbytecode *pc = wscript->code;
while (*pc != JSOP_STOP) {
/* XYZZYbe should copy JSOP_TRAP? */
JSOp op = js_GetOpcode(cx, wscript, pc);
const JSCodeSpec *cs = &js_CodeSpec[op];
ptrdiff_t oplen = cs->length;
if (oplen < 0)
oplen = js_GetVariableBytecodeLength(pc);
/*
* Rewrite JSOP_{GET,CALL}DSLOT as JSOP_{GET,CALL}UPVAR_DBG for the
* case where fun is an escaping flat closure. This works because the
* UPVAR and DSLOT ops by design have the same format: an upvar index
* immediate operand.
*/
switch (op) {
case JSOP_GETUPVAR: *pc = JSOP_GETUPVAR_DBG; break;
case JSOP_CALLUPVAR: *pc = JSOP_CALLUPVAR_DBG; break;
case JSOP_GETDSLOT: *pc = JSOP_GETUPVAR_DBG; break;
case JSOP_CALLDSLOT: *pc = JSOP_CALLUPVAR_DBG; break;
case JSOP_DEFFUN_FC: *pc = JSOP_DEFFUN_DBGFC; break;
case JSOP_DEFLOCALFUN_FC: *pc = JSOP_DEFLOCALFUN_DBGFC; break;
case JSOP_LAMBDA_FC: *pc = JSOP_LAMBDA_DBGFC; break;
default:;
}
pc += oplen;
}
/*
* Flag the wrapper's script as hazardous, because it could host debugger
* or indirect eval calls that leak closures. Wrappers thus transitively
* wrap all contained closures that escape.
*/
wscript->flags = script->flags | JSSF_ESCAPE_HAZARD;
wscript->version = script->version;
wscript->nfixed = script->nfixed;
wscript->filename = script->filename;
wscript->lineno = script->lineno;
wscript->nslots = script->nslots;
wscript->staticLevel = script->staticLevel;
wscript->principals = script->principals;
if (wscript->principals)
JSPRINCIPALS_HOLD(cx, wscript->principals);
#ifdef CHECK_SCRIPT_OWNER
wscript->owner = script->owner;
#endif
/* Deoptimize wfun from FUN_{FLAT,NULL}_CLOSURE to FUN_INTERPRETED. */
FUN_SET_KIND(wfun, JSFUN_INTERPRETED);
wfun->u.i.script = wscript;
return wfunobj;
}
static JSBool
args_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
@ -379,6 +560,13 @@ args_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
return JS_TRUE;
JS_ASSERT(fp->argsobj);
/*
* If this function or one in it needs upvars that reach above it in the
* scope chain, it must not be a null closure (it could be a flat closure,
* or an unoptimized closure -- the latter not necessarily heavyweight).
*/
JS_ASSERT_IF(fp->fun->u.i.skipmin != 0, !FUN_NULL_CLOSURE(fp->fun));
slot = JSVAL_TO_INT(id);
switch (slot) {
case ARGS_CALLEE:
@ -590,14 +778,62 @@ JSClass js_ArgumentsClass = {
#define JSSLOT_CALL_ARGUMENTS (JSSLOT_PRIVATE + 2)
#define CALL_CLASS_FIXED_RESERVED_SLOTS 2
/*
* A Declarative Environment object stores its active JSStackFrame pointer in
* its private slot, just as Call and Arguments objects do.
*/
JSClass js_DeclEnvClass = {
js_Object_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};
static JS_REQUIRES_STACK JSBool
CheckForEscapingClosure(JSContext *cx, JSObject *obj, jsval *vp)
{
JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_CallClass ||
STOBJ_GET_CLASS(obj) == &js_DeclEnvClass);
if (cx->fp && cx->fp->script && (cx->fp->script->flags & JSSF_ESCAPE_HAZARD)) {
jsval v = *vp;
if (VALUE_IS_FUNCTION(cx, v)) {
JSObject *funobj = JSVAL_TO_OBJECT(v);
JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
/*
* Any escaping null or flat closure that reaches above itself or
* contains nested functions that reach above it must be wrapped.
* We can wrap only when this Call or Declarative Environment obj
* still has an active stack frame associated with it.
*/
if (FUN_ESCAPE_HAZARD(fun)) {
JSStackFrame *fp = (JSStackFrame *) JS_GetPrivate(cx, obj);
if (fp) {
JSObject *wrapper = WrapEscapingClosure(cx, fp, funobj, fun);
if (!wrapper)
return false;
*vp = OBJECT_TO_JSVAL(wrapper);
return true;
}
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_OPTIMIZED_CLOSURE_LEAK);
return false;
}
}
}
return true;
}
static JS_REQUIRES_STACK JSBool
CalleeGetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
return CheckForEscapingClosure(cx, obj, vp);
}
JSObject *
js_GetCallObject(JSContext *cx, JSStackFrame *fp)
{
@ -628,11 +864,13 @@ js_GetCallObject(JSContext *cx, JSStackFrame *fp)
fp->scopeChain, 0);
if (!env)
return NULL;
env->fslots[JSSLOT_PRIVATE] = PRIVATE_TO_JSVAL(fp);
/* Root env before js_DefineNativeProperty (-> JSClass.addProperty). */
fp->scopeChain = env;
if (!js_DefineNativeProperty(cx, fp->scopeChain, ATOM_TO_JSID(lambdaName),
OBJECT_TO_JSVAL(fp->callee), NULL, NULL,
OBJECT_TO_JSVAL(fp->callee),
CalleeGetter, NULL,
JSPROP_PERMANENT | JSPROP_READONLY,
0, 0, NULL)) {
return NULL;
@ -725,10 +963,18 @@ js_PutCallObject(JSContext *cx, JSStackFrame *fp)
}
/*
* Clear the private pointer to fp, which is about to go away (js_Invoke).
* Clear private pointers to fp, which is about to go away (js_Invoke).
* Do this last because js_GetProperty calls above need to follow the
* private slot to find fp.
* call object's private slot to find fp.
*/
if ((fun->flags & JSFUN_LAMBDA) && fun->atom) {
JSObject *env = STOBJ_GET_PARENT(callobj);
JS_ASSERT(STOBJ_GET_CLASS(env) == &js_DeclEnvClass);
JS_ASSERT(STOBJ_GET_PRIVATE(env) == fp);
JS_SetPrivate(cx, env, NULL);
}
JS_SetPrivate(cx, callobj, NULL);
fp->callobj = NULL;
return ok;
@ -888,7 +1134,10 @@ SetCallArg(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
JSBool
js_GetCallVar(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
return CallPropertyOp(cx, obj, id, vp, JSCPK_VAR, JS_FALSE);
if (!CallPropertyOp(cx, obj, id, vp, JSCPK_VAR, JS_FALSE))
return JS_FALSE;
return CheckForEscapingClosure(cx, obj, vp);
}
static JSBool
@ -1099,10 +1348,20 @@ fun_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
break;
case FUN_CALLER:
if (fp && fp->down && fp->down->fun)
if (fp && fp->down && fp->down->fun) {
/*
* See the equivalent assertion in args_getProperty. Here we assert
* against a closure leak that goes through foo.caller. Wrapping an
* escaping optimized closure guarantees that its function cannot
* match the unwrapped stack frame's function (fp->fun) in the loop
* commented "Find fun's top-most activation record" above.
*/
JS_ASSERT_IF(fp->down->fun->u.i.skipmin != 0,
!FUN_NULL_CLOSURE(fp->down->fun));
*vp = OBJECT_TO_JSVAL(fp->down->callee);
else
} else {
*vp = JSVAL_NULL;
}
if (!JSVAL_IS_PRIMITIVE(*vp)) {
callbacks = JS_GetSecurityCallbacks(cx);
if (callbacks && callbacks->checkObjectAccess) {
@ -1272,10 +1531,12 @@ js_XDRFunctionObject(JSXDRState *xdr, JSObject **objp)
{
JSContext *cx;
JSFunction *fun;
uint32 nullAtom; /* flag to indicate if fun->atom is NULL */
uint32 firstword; /* flag telling whether fun->atom is non-null,
plus for fun->u.i.skipmin, fun->u.i.wrapper,
and 14 bits reserved for future use */
uintN nargs, nvars, nupvars, n;
uint32 localsword; /* word to xdr argument and variable counts */
uint32 flagsword; /* word to xdr upvars count and fun->flags */
uint32 localsword; /* word for argument and variable counts */
uint32 flagsword; /* word for fun->u.i.nupvars and fun->flags */
JSTempValueRooter tvr;
JSBool ok;
@ -1288,7 +1549,14 @@ js_XDRFunctionObject(JSXDRState *xdr, JSObject **objp)
JS_GetFunctionName(fun));
return JS_FALSE;
}
nullAtom = !fun->atom;
if (fun->u.i.wrapper) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_XDR_CLOSURE_WRAPPER,
JS_GetFunctionName(fun));
return JS_FALSE;
}
JS_ASSERT((fun->u.i.wrapper & ~1U) == 0);
firstword = (fun->u.i.skipmin << 2) | (fun->u.i.wrapper << 1) | !!fun->atom;
nargs = fun->nargs;
nvars = fun->u.i.nvars;
nupvars = fun->u.i.nupvars;
@ -1310,9 +1578,9 @@ js_XDRFunctionObject(JSXDRState *xdr, JSObject **objp)
JS_PUSH_TEMP_ROOT_OBJECT(cx, FUN_OBJECT(fun), &tvr);
ok = JS_TRUE;
if (!JS_XDRUint32(xdr, &nullAtom))
if (!JS_XDRUint32(xdr, &firstword))
goto bad;
if (!nullAtom && !js_XDRStringAtom(xdr, &fun->atom))
if ((firstword & 1U) && !js_XDRStringAtom(xdr, &fun->atom))
goto bad;
if (!JS_XDRUint32(xdr, &localsword) ||
!JS_XDRUint32(xdr, &flagsword)) {
@ -1325,6 +1593,8 @@ js_XDRFunctionObject(JSXDRState *xdr, JSObject **objp)
JS_ASSERT((flagsword & JSFUN_KINDMASK) >= JSFUN_INTERPRETED);
nupvars = flagsword >> 16;
fun->flags = uint16(flagsword);
fun->u.i.skipmin = uint16(firstword >> 2);
fun->u.i.wrapper = (firstword >> 1) & 1;
}
/* do arguments and local vars */
@ -2134,6 +2404,8 @@ js_NewFunction(JSContext *cx, JSObject *funobj, JSNative native, uintN nargs,
JS_ASSERT(nargs == 0);
fun->u.i.nvars = 0;
fun->u.i.nupvars = 0;
fun->u.i.skipmin = 0;
fun->u.i.wrapper = false;
fun->u.i.script = NULL;
#ifdef DEBUG
fun->u.i.names.taggedAtom = 0;
@ -2210,6 +2482,8 @@ JSObject *
js_NewFlatClosure(JSContext *cx, JSFunction *fun)
{
JSObject *closure = js_AllocFlatClosure(cx, fun, cx->fp->scopeChain);
if (!closure)
return NULL;
JSUpvarArray *uva = JS_SCRIPT_UPVARS(fun->u.i.script);
JS_ASSERT(uva->length <= size_t(closure->dslots[-1]));
@ -2221,6 +2495,16 @@ js_NewFlatClosure(JSContext *cx, JSFunction *fun)
return closure;
}
JSObject *
js_NewDebuggableFlatClosure(JSContext *cx, JSFunction *fun)
{
JS_ASSERT(cx->fp->script->flags & JSSF_ESCAPE_HAZARD);
JS_ASSERT(cx->fp->fun->flags & JSFUN_HEAVYWEIGHT);
JS_ASSERT(!FUN_OPTIMIZED_CLOSURE(cx->fp->fun));
return WrapEscapingClosure(cx, cx->fp, FUN_OBJECT(fun), fun);
}
JSFunction *
js_DefineFunction(JSContext *cx, JSObject *obj, JSAtom *atom, JSNative native,
uintN nargs, uintN attrs)

Просмотреть файл

@ -113,6 +113,8 @@ typedef union JSLocalNames {
#define FUN_INTERPRETED(fun) (FUN_KIND(fun) >= JSFUN_INTERPRETED)
#define FUN_FLAT_CLOSURE(fun)(FUN_KIND(fun) == JSFUN_FLAT_CLOSURE)
#define FUN_NULL_CLOSURE(fun)(FUN_KIND(fun) == JSFUN_NULL_CLOSURE)
#define FUN_OPTIMIZED_CLOSURE(fun) (FUN_KIND(fun) > JSFUN_INTERPRETED)
#define FUN_ESCAPE_HAZARD(fun) (FUN_OPTIMIZED_CLOSURE(fun) && (fun)->u.i.skipmin != 0)
#define FUN_SLOW_NATIVE(fun) (!FUN_INTERPRETED(fun) && !((fun)->flags & JSFUN_FAST_NATIVE))
#define FUN_SCRIPT(fun) (FUN_INTERPRETED(fun) ? (fun)->u.i.script : NULL)
#define FUN_NATIVE(fun) (FUN_SLOW_NATIVE(fun) ? (fun)->u.n.native : NULL)
@ -147,6 +149,15 @@ struct JSFunction {
uint16 nvars; /* number of local variables */
uint16 nupvars; /* number of upvars (computable from script
but here for faster access) */
uint16 skipmin; /* net skip amount up (toward zero) from
script->staticLevel to nearest upvar,
including upvars in nested functions */
JSPackedBool wrapper; /* true if this function is a wrapper that
rewrites bytecode optimized for a function
judged non-escaping by the compiler, which
then escaped via the debugger or a rogue
indirect eval; if true, then this function
object's proto is the wrapped object */
JSScript *script; /* interpreted bytecode descriptor or null */
JSLocalNames names; /* argument and variable names */
} i;
@ -231,6 +242,9 @@ js_CloneFunctionObject(JSContext *cx, JSFunction *fun, JSObject *parent);
extern JS_REQUIRES_STACK JSObject *
js_NewFlatClosure(JSContext *cx, JSFunction *fun);
extern JS_REQUIRES_STACK JSObject *
js_NewDebuggableFlatClosure(JSContext *cx, JSFunction *fun);
extern JSFunction *
js_DefineFunction(JSContext *cx, JSObject *obj, JSAtom *atom, JSNative native,
uintN nargs, uintN flags);
@ -265,7 +279,7 @@ js_PutCallObject(JSContext *cx, JSStackFrame *fp);
extern JSBool
js_GetCallArg(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
extern JSBool
extern JS_REQUIRES_STACK JSBool
js_GetCallVar(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
extern JSBool

Просмотреть файл

@ -2589,11 +2589,19 @@ AssertValidPropertyCacheHit(JSContext *cx, JSScript *script, JSFrameRegs& regs,
JS_STATIC_ASSERT(JSOP_NAME_LENGTH == JSOP_CALLNAME_LENGTH);
JS_STATIC_ASSERT(JSOP_GETGVAR_LENGTH == JSOP_CALLGVAR_LENGTH);
JS_STATIC_ASSERT(JSOP_GETUPVAR_LENGTH == JSOP_CALLUPVAR_LENGTH);
JS_STATIC_ASSERT(JSOP_GETUPVAR_DBG_LENGTH == JSOP_CALLUPVAR_DBG_LENGTH);
JS_STATIC_ASSERT(JSOP_GETUPVAR_DBG_LENGTH == JSOP_GETUPVAR_LENGTH);
JS_STATIC_ASSERT(JSOP_GETDSLOT_LENGTH == JSOP_CALLDSLOT_LENGTH);
JS_STATIC_ASSERT(JSOP_GETARG_LENGTH == JSOP_CALLARG_LENGTH);
JS_STATIC_ASSERT(JSOP_GETLOCAL_LENGTH == JSOP_CALLLOCAL_LENGTH);
JS_STATIC_ASSERT(JSOP_XMLNAME_LENGTH == JSOP_CALLXMLNAME_LENGTH);
/*
* Same for debuggable flat closures defined at top level in another function
* or program fragment.
*/
JS_STATIC_ASSERT(JSOP_DEFFUN_FC_LENGTH == JSOP_DEFFUN_DBGFC_LENGTH);
/*
* Same for JSOP_SETNAME and JSOP_SETPROP, which differ only slightly but
* remain distinct for the decompiler.
@ -5733,6 +5741,43 @@ js_Interpret(JSContext *cx)
}
END_CASE(JSOP_GETUPVAR)
BEGIN_CASE(JSOP_GETUPVAR_DBG)
BEGIN_CASE(JSOP_CALLUPVAR_DBG)
fun = fp->fun;
JS_ASSERT(FUN_KIND(fun) == JSFUN_INTERPRETED);
JS_ASSERT(fun->u.i.wrapper);
/* Scope for tempPool mark and local names allocation in it. */
{
void *mark = JS_ARENA_MARK(&cx->tempPool);
jsuword *names = js_GetLocalNameArray(cx, fun, &cx->tempPool);
if (!names)
goto error;
index = fun->countArgsAndVars() + GET_UINT16(regs.pc);
atom = JS_LOCAL_NAME_TO_ATOM(names[index]);
id = ATOM_TO_JSID(atom);
ok = js_FindProperty(cx, id, &obj, &obj2, &prop);
JS_ARENA_RELEASE(&cx->tempPool, mark);
if (!ok)
goto error;
}
if (!prop)
goto atom_not_defined;
/* Minimize footprint with generic code instead of NATIVE_GET. */
OBJ_DROP_PROPERTY(cx, obj2, prop);
vp = regs.sp;
PUSH_OPND(JSVAL_NULL);
if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
goto error;
if (op == JSOP_CALLUPVAR_DBG)
PUSH_OPND(JSVAL_NULL);
END_CASE(JSOP_GETUPVAR_DBG)
BEGIN_CASE(JSOP_GETDSLOT)
BEGIN_CASE(JSOP_CALLDSLOT)
obj = fp->callee;
@ -6018,9 +6063,12 @@ js_Interpret(JSContext *cx)
END_CASE(JSOP_DEFFUN)
BEGIN_CASE(JSOP_DEFFUN_FC)
BEGIN_CASE(JSOP_DEFFUN_DBGFC)
LOAD_FUNCTION(0);
obj = js_NewFlatClosure(cx, fun);
obj = (op == JSOP_DEFFUN_FC)
? js_NewFlatClosure(cx, fun)
: js_NewDebuggableFlatClosure(cx, fun);
if (!obj)
goto error;
rval = OBJECT_TO_JSVAL(obj);
@ -6117,6 +6165,17 @@ js_Interpret(JSContext *cx)
fp->slots[slot] = OBJECT_TO_JSVAL(obj);
END_CASE(JSOP_DEFLOCALFUN_FC)
BEGIN_CASE(JSOP_DEFLOCALFUN_DBGFC)
LOAD_FUNCTION(SLOTNO_LEN);
obj = js_NewDebuggableFlatClosure(cx, fun);
if (!obj)
goto error;
slot = GET_SLOTNO(regs.pc);
fp->slots[slot] = OBJECT_TO_JSVAL(obj);
END_CASE(JSOP_DEFLOCALFUN_DBGFC)
BEGIN_CASE(JSOP_LAMBDA)
/* Load the specified function object literal. */
LOAD_FUNCTION(0);
@ -6152,6 +6211,16 @@ js_Interpret(JSContext *cx)
PUSH_OPND(OBJECT_TO_JSVAL(obj));
END_CASE(JSOP_LAMBDA_FC)
BEGIN_CASE(JSOP_LAMBDA_DBGFC)
LOAD_FUNCTION(0);
obj = js_NewDebuggableFlatClosure(cx, fun);
if (!obj)
goto error;
PUSH_OPND(OBJECT_TO_JSVAL(obj));
END_CASE(JSOP_LAMBDA_DBGFC)
BEGIN_CASE(JSOP_CALLEE)
PUSH_OPND(OBJECT_TO_JSVAL(fp->callee));
END_CASE(JSOP_CALLEE)

Просмотреть файл

@ -2804,6 +2804,8 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
case JSOP_GETUPVAR:
case JSOP_CALLUPVAR:
case JSOP_GETUPVAR_DBG:
case JSOP_CALLUPVAR_DBG:
case JSOP_GETDSLOT:
case JSOP_CALLDSLOT:
{
@ -3974,6 +3976,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
case JSOP_LAMBDA:
case JSOP_LAMBDA_FC:
case JSOP_LAMBDA_DBGFC:
#if JS_HAS_GENERATOR_EXPRS
sn = js_GetSrcNote(jp->script, pc);
if (sn && SN_TYPE(sn) == SRC_GENEXP) {
@ -4339,6 +4342,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
case JSOP_DEFFUN:
case JSOP_DEFFUN_FC:
case JSOP_DEFFUN_DBGFC:
LOAD_FUNCTION(0);
todo = -2;
goto do_function;

Просмотреть файл

@ -580,3 +580,12 @@ OPDEF(JSOP_LAMBDA_FC, 226,"lambda_fc", NULL, 3, 0, 1, 19, JOF_OBJECT
OPDEF(JSOP_OBJTOP, 227,"objtop", NULL, 3, 0, 0, 0, JOF_UINT16)
OPDEF(JSOP_LOOP, 228, "loop", NULL, 1, 0, 0, 0, JOF_BYTE)
/*
* Debugger versions of JSOP_{GET,CALL}UPVAR.
*/
OPDEF(JSOP_GETUPVAR_DBG, 229,"getupvar_dbg", NULL, 3, 0, 1, 19, JOF_UINT16|JOF_NAME)
OPDEF(JSOP_CALLUPVAR_DBG, 230,"callupvar_dbg", NULL, 3, 0, 2, 19, JOF_UINT16|JOF_NAME|JOF_CALLOP)
OPDEF(JSOP_DEFFUN_DBGFC, 231,"deffun_dbgfc", NULL, 3, 0, 0, 0, JOF_OBJECT|JOF_DECLARING)
OPDEF(JSOP_DEFLOCALFUN_DBGFC,232,"deflocalfun_dbgfc",NULL, 5, 0, 0, 0, JOF_SLOTOBJECT|JOF_DECLARING)
OPDEF(JSOP_LAMBDA_DBGFC, 233,"lambda_dbgfc", NULL, 3, 0, 1, 19, JOF_OBJECT)

Просмотреть файл

@ -819,7 +819,7 @@ JSCompiler::compileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *cal
/* Null script early in case of error, to reduce our code footprint. */
script = NULL;
cg.flags |= (uint16) tcflags;
cg.flags |= uint16(tcflags);
cg.scopeChain = scopeChain;
if (!SetStaticLevel(&cg, TCF_GET_STATIC_LEVEL(tcflags)))
goto out;
@ -1688,12 +1688,21 @@ JSCompiler::analyzeFunctions(JSFunctionBox *funbox, uint16& tcflags)
* process is potentially exponential in the number of functions, but generally
* not so complex. But it can't be done during a single recursive traversal of
* the funbox tree, so we must use a work queue.
*
* Return the minimal "skipmin" for funbox and its siblings. This is the delta
* between the static level of the bodies of funbox and its peers (which must
* be funbox->level + 1), and the static level of the nearest upvar among all
* the upvars contained by funbox and its peers. If there are no upvars, return
* FREE_STATIC_LEVEL. Thus this function never returns 0.
*/
static void
static uintN
FindFunArgs(JSFunctionBox *funbox, int level, JSFunctionBoxQueue *queue)
{
uintN allskipmin = FREE_STATIC_LEVEL;
do {
JSParseNode *fn = funbox->node;
JSFunction *fun = (JSFunction *) funbox->object;
int fnlevel = level;
/*
@ -1709,35 +1718,78 @@ FindFunArgs(JSFunctionBox *funbox, int level, JSFunctionBoxQueue *queue)
kid->node->setFunArg();
}
if (fn->isFunArg()) {
queue->push(funbox);
fnlevel = int(funbox->level);
} else {
JSParseNode *pn = fn->pn_body;
/*
* Compute in skipmin the least distance from fun's static level up to
* an upvar, whether used directly by fun, or indirectly by a function
* nested in fun.
*/
uintN skipmin = FREE_STATIC_LEVEL;
JSParseNode *pn = fn->pn_body;
if (pn->pn_type == TOK_UPVARS) {
JSAtomList upvars(pn->pn_names);
JS_ASSERT(upvars.count != 0);
if (pn->pn_type == TOK_UPVARS) {
JSAtomList upvars(pn->pn_names);
JS_ASSERT(upvars.count != 0);
JSAtomListIterator iter(&upvars);
JSAtomListElement *ale;
JSAtomListIterator iter(&upvars);
JSAtomListElement *ale;
while ((ale = iter()) != NULL) {
JSDefinition *lexdep = ALE_DEFN(ale)->resolve();
while ((ale = iter()) != NULL) {
JSDefinition *lexdep = ALE_DEFN(ale)->resolve();
if (!lexdep->isFreeVar() && int(lexdep->frameLevel()) <= fnlevel) {
if (!lexdep->isFreeVar()) {
uintN upvarLevel = lexdep->frameLevel();
if (int(upvarLevel) <= fnlevel)
fn->setFunArg();
queue->push(funbox);
fnlevel = int(funbox->level);
break;
}
uintN skip = (funbox->level + 1) - upvarLevel;
if (skip < skipmin)
skipmin = skip;
}
}
}
if (funbox->kids)
FindFunArgs(funbox->kids, fnlevel, queue);
/*
* If this function escapes, whether directly (the parser detects such
* escapes) or indirectly (because this non-escaping function uses an
* upvar that reaches across an outer function boundary where the outer
* function escapes), enqueue it for further analysis, and bump fnlevel
* to trap any non-escaping children.
*/
if (fn->isFunArg()) {
queue->push(funbox);
fnlevel = int(funbox->level);
}
/*
* Now process the current function's children, and recalibrate their
* cumulative skipmin to be relative to the current static level.
*/
if (funbox->kids) {
uintN kidskipmin = FindFunArgs(funbox->kids, fnlevel, queue);
JS_ASSERT(kidskipmin != 0);
if (kidskipmin != FREE_STATIC_LEVEL) {
--kidskipmin;
if (kidskipmin != 0 && kidskipmin < skipmin)
skipmin = kidskipmin;
}
}
/*
* Finally, after we've traversed all of the current function's kids,
* minimize fun's skipmin against our accumulated skipmin. Do likewise
* with allskipmin, but minimize across funbox and all of its siblings,
* to compute our return value.
*/
if (skipmin != FREE_STATIC_LEVEL) {
fun->u.i.skipmin = skipmin;
if (skipmin < allskipmin)
allskipmin = skipmin;
}
} while ((funbox = funbox->siblings) != NULL);
return allskipmin;
}
bool
@ -1767,13 +1819,14 @@ JSCompiler::markFunArgs(JSFunctionBox *funbox, uintN tcflags)
!lexdep->isFunArg() &&
lexdep->kind() == JSDefinition::FUNCTION) {
/*
* Mark this formerly-Algol-like function as a funarg,
* since it is referenced from a funarg and can no longer
* use JSOP_{GET,CALL}UPVAR to access upvars.
* Mark this formerly-Algol-like function as an escaping
* function (i.e., as a funarg), because it is used from a
* funarg and therefore can not use JSOP_{GET,CALL}UPVAR to
* access upvars.
*
* Progress is guaranteed since we set PND_FUNARG here,
* which suppresses revisiting this function (namely the
* !lexdep->isFunArg() test just above).
* Progress is guaranteed because we set the funarg flag
* here, which suppresses revisiting this function (thanks
* to the !lexdep->isFunArg() test just above).
*/
lexdep->setFunArg();

Просмотреть файл

@ -1530,6 +1530,8 @@ js_NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg)
cg->regexpList.finish(JS_SCRIPT_REGEXPS(script));
if (cg->flags & TCF_NO_SCRIPT_RVAL)
script->flags |= JSSF_NO_SCRIPT_RVAL;
if ((cg->flags & TCF_COMPILE_N_GO) && cg->compiler->callerFrame)
script->flags |= JSSF_ESCAPE_HAZARD;
if (cg->upvarList.count != 0) {
JS_ASSERT(cg->upvarList.count <= cg->upvarMap.length);

Просмотреть файл

@ -135,6 +135,9 @@ struct JSScript {
#define JSSF_NO_SCRIPT_RVAL 0x01 /* no need for result value of last
expression statement */
#define JSSF_SAVED_CALLER_FUN 0x02 /* object 0 is caller function */
#define JSSF_ESCAPE_HAZARD 0x04 /* script (including functions) was
created by dynamically scoped eval
or debugger eval-in-frame API */
static JS_INLINE uintN
StackDepth(JSScript *script)

Просмотреть файл

@ -10797,6 +10797,19 @@ TraceRecorder::record_JSOP_LOOP()
return JSRS_CONTINUE;
}
#define DBG_STUB(OP) \
JS_REQUIRES_STACK JSRecordingStatus \
TraceRecorder::record_##OP() \
{ \
ABORT_TRACE("can't trace " #OP); \
}
DBG_STUB(JSOP_GETUPVAR_DBG)
DBG_STUB(JSOP_CALLUPVAR_DBG)
DBG_STUB(JSOP_DEFFUN_DBGFC)
DBG_STUB(JSOP_DEFLOCALFUN_DBGFC)
DBG_STUB(JSOP_LAMBDA_DBGFC)
#ifdef JS_JIT_SPEW
/* Prints information about entry typemaps and unstable exits for all peers at a PC */
void

Просмотреть файл

@ -204,7 +204,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32 id);
* before deserialization of bytecode. If the saved version does not match
* the current version, abort deserialization and invalidate the file.
*/
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 48)
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 49)
/*
* Library-private functions.