[INFER] Infer arithmetic on objects as producing ints, bug 642412.

This commit is contained in:
Brian Hackett 2011-03-17 21:34:36 -07:00
parent 236d9c0de4
commit 6b4af544cb
6 changed files with 63 additions and 41 deletions

View File

@ -1128,55 +1128,53 @@ TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
void
TypeConstraintArith::newType(JSContext *cx, TypeSet *source, jstype type)
{
/*
* We only model a subset of the arithmetic behavior that is actually
* possible. The following need to be watched for at runtime:
*
* 1. Operations producing a double where no operand was a double.
* 2. Operations producing a string where no operand was a string (addition only).
* 3. Operations producing a value other than int/double/string.
*/
if (other) {
/*
* Addition operation, consider these cases:
* {int,bool} x {int,bool} -> int
* float x {int,bool,float} -> float
* double x {int,bool,double} -> double
* string x any -> string
*/
switch (type) {
case TYPE_UNDEFINED:
case TYPE_NULL:
case TYPE_INT32:
case TYPE_BOOLEAN:
/* Note: need to account for overflows from, e.g. int + void */
if (other->typeFlags & (TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL |
TYPE_FLAG_INT32 | TYPE_FLAG_BOOLEAN))
target->addType(cx, TYPE_INT32);
if (other->typeFlags & TYPE_FLAG_DOUBLE)
target->addType(cx, TYPE_DOUBLE);
break;
case TYPE_DOUBLE:
if (other->typeFlags & (TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL |
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_BOOLEAN))
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_BOOLEAN) ||
other->objectCount != 0) {
target->addType(cx, TYPE_DOUBLE);
}
break;
case TYPE_STRING:
target->addType(cx, TYPE_STRING);
break;
default:
/*
* Don't try to model arithmetic on objects, this can invoke valueOf,
* operate on XML objects, etc.
*/
case TYPE_UNKNOWN:
target->addType(cx, TYPE_UNKNOWN);
default:
if (other->typeFlags & (TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL |
TYPE_FLAG_INT32 | TYPE_FLAG_BOOLEAN) ||
other->objectCount != 0) {
target->addType(cx, TYPE_INT32);
}
if (other->typeFlags & TYPE_FLAG_DOUBLE)
target->addType(cx, TYPE_DOUBLE);
break;
}
} else {
/* Note: same issues with undefined as addition. */
switch (type) {
case TYPE_UNDEFINED:
case TYPE_NULL:
case TYPE_INT32:
case TYPE_BOOLEAN:
target->addType(cx, TYPE_INT32);
break;
case TYPE_DOUBLE:
target->addType(cx, TYPE_DOUBLE);
break;
default:
case TYPE_UNKNOWN:
target->addType(cx, TYPE_UNKNOWN);
default:
target->addType(cx, TYPE_INT32);
break;
}
}

View File

@ -555,6 +555,12 @@ JSScript::typeMonitorUndefined(JSContext *cx, const jsbytecode *pc)
return typeMonitorResult(cx, pc, js::types::TYPE_UNDEFINED);
}
inline bool
JSScript::typeMonitorString(JSContext *cx, const jsbytecode *pc)
{
return typeMonitorResult(cx, pc, js::types::TYPE_STRING);
}
inline bool
JSScript::typeMonitorUnknown(JSContext *cx, const jsbytecode *pc)
{

View File

@ -3755,12 +3755,15 @@ BEGIN_CASE(JSOP_ADD)
goto error;
regs.sp--;
regs.sp[-1] = rval;
if (!script->typeMonitorUnknown(cx, regs.pc))
goto error;
} else
#endif
{
if (lval.isObject())
bool lIsObject, rIsObject;
if ((lIsObject = lval.isObject()))
DEFAULT_VALUE(cx, -2, JSTYPE_VOID, lval);
if (rval.isObject())
if ((rIsObject = rval.isObject()))
DEFAULT_VALUE(cx, -1, JSTYPE_VOID, rval);
bool lIsString, rIsString;
if ((lIsString = lval.isString()) | (rIsString = rval.isString())) {
@ -3784,6 +3787,8 @@ BEGIN_CASE(JSOP_ADD)
JSString *str = js_ConcatStrings(cx, lstr, rstr);
if (!str)
goto error;
if ((lIsObject || rIsObject) && !script->typeMonitorString(cx, regs.pc))
goto error;
regs.sp--;
regs.sp[-1].setString(str);
} else {
@ -3793,7 +3798,7 @@ BEGIN_CASE(JSOP_ADD)
l += r;
regs.sp--;
if (!regs.sp[-1].setNumber(l) &&
!(lval.isDouble() || rval.isDouble()) &&
(lIsObject || rIsObject || (!lval.isDouble() && !rval.isDouble())) &&
!script->typeMonitorOverflow(cx, regs.pc)) {
goto error;
}

View File

@ -395,7 +395,9 @@ js_math_max(JSContext *cx, uintN argc, Value *vp)
return cx->markTypeCallerOverflow();
}
argv = vp + 2;
bool expectDouble = false;
for (i = 0; i < argc; i++) {
expectDouble |= argv[i].isDouble();
if (!ValueToNumber(cx, argv[i], &x))
return JS_FALSE;
if (JSDOUBLE_IS_NaN(x)) {
@ -409,7 +411,8 @@ js_math_max(JSContext *cx, uintN argc, Value *vp)
z = (x > z) ? x : z;
}
}
vp->setNumber(z);
if (!vp->setNumber(z) && !expectDouble)
return cx->markTypeCallerOverflow();
return JS_TRUE;
}
@ -425,7 +428,9 @@ js_math_min(JSContext *cx, uintN argc, Value *vp)
return cx->markTypeCallerOverflow();
}
argv = vp + 2;
bool expectDouble = false;
for (i = 0; i < argc; i++) {
expectDouble |= argv[i].isDouble();
if (!ValueToNumber(cx, argv[i], &x))
return JS_FALSE;
if (JSDOUBLE_IS_NaN(x)) {
@ -439,7 +444,8 @@ js_math_min(JSContext *cx, uintN argc, Value *vp)
z = (x < z) ? x : z;
}
}
vp->setNumber(z);
if (!vp->setNumber(z) && !expectDouble)
return cx->markTypeCallerOverflow();
return JS_TRUE;
}
@ -480,6 +486,7 @@ math_pow(JSContext *cx, uintN argc, Value *vp)
vp->setDouble(js_NaN);
return cx->markTypeCallerOverflow();
}
bool expectDouble = vp[2].isDouble() || vp[3].isDouble();
if (!ValueToNumber(cx, vp[2], &x))
return JS_FALSE;
if (!ValueToNumber(cx, vp[3], &y))
@ -491,11 +498,11 @@ math_pow(JSContext *cx, uintN argc, Value *vp)
if (JSDOUBLE_IS_FINITE(x) && x != 0.0) {
if (y == 0.5) {
vp->setNumber(sqrt(x));
return JS_TRUE;
return expectDouble || cx->markTypeCallerOverflow();
}
if (y == -0.5) {
vp->setNumber(1.0/sqrt(x));
return JS_TRUE;
return expectDouble || cx->markTypeCallerOverflow();
}
}
/*
@ -504,7 +511,7 @@ math_pow(JSContext *cx, uintN argc, Value *vp)
*/
if (!JSDOUBLE_IS_FINITE(y) && (x == 1.0 || x == -1.0)) {
vp->setDouble(js_NaN);
return vp[3].isDouble() || cx->markTypeCallerOverflow();
return expectDouble || cx->markTypeCallerOverflow();
}
/* pow(x, +-0) is always 1, even for x = NaN. */
if (y == 0) {
@ -517,10 +524,8 @@ math_pow(JSContext *cx, uintN argc, Value *vp)
else
z = pow(x, y);
vp->setNumber(z);
if (vp->isDouble() && !(vp[2].isDouble() || vp[3].isDouble()) && !cx->markTypeCallerOverflow())
return false;
if (!vp->setNumber(z) && !expectDouble)
return cx->markTypeCallerOverflow();
return JS_TRUE;
}

View File

@ -522,6 +522,7 @@ struct JSScript {
inline bool typeMonitorResult(JSContext *cx, const jsbytecode *pc, const js::Value &val);
inline bool typeMonitorUndefined(JSContext *cx, const jsbytecode *pc);
inline bool typeMonitorOverflow(JSContext *cx, const jsbytecode *pc);
inline bool typeMonitorString(JSContext *cx, const jsbytecode *pc);
inline bool typeMonitorUnknown(JSContext *cx, const jsbytecode *pc);
/* Add a type for a variable in this script. */

View File

@ -1102,13 +1102,16 @@ stubs::Add(VMFrame &f)
THROW();
regs.sp--;
regs.sp[-1] = rval;
if (!f.script()->typeMonitorUnknown(cx, regs.pc))
THROW();
} else
#endif
{
/* These can convert lval/rval to strings. */
if (lval.isObject() && !DefaultValue(f, JSTYPE_VOID, lval, -2))
bool lIsObject, rIsObject;
if ((lIsObject = lval.isObject()) && !DefaultValue(f, JSTYPE_VOID, lval, -2))
THROW();
if (rval.isObject() && !DefaultValue(f, JSTYPE_VOID, rval, -1))
if ((rIsObject = rval.isObject()) && !DefaultValue(f, JSTYPE_VOID, rval, -1))
THROW();
if ((lIsString = lval.isString()) || (rIsString = rval.isString())) {
if (lIsString) {
@ -1127,6 +1130,8 @@ stubs::Add(VMFrame &f)
THROW();
regs.sp[-1].setString(rstr);
}
if ((lIsObject || rIsObject) && !f.script()->typeMonitorString(cx, regs.pc))
THROW();
goto string_concat;
} else {
@ -1134,7 +1139,9 @@ stubs::Add(VMFrame &f)
if (!ValueToNumber(cx, lval, &l) || !ValueToNumber(cx, rval, &r))
THROW();
l += r;
if (!regs.sp[-2].setNumber(l) && !MonitorArithmeticOverflow(f, regs.sp[-2]))
if (!regs.sp[-2].setNumber(l) &&
(lIsObject || rIsObject || (!lval.isDouble() && !rval.isDouble())) &&
!MonitorArithmeticOverflow(f, regs.sp[-2]))
THROW();
}
}