[INFER] Monitor CALLPROP calls whose callee is not statically known, bug 660204.

This commit is contained in:
Brian Hackett 2011-05-27 14:21:37 -07:00
parent e1be20257a
commit debff956bd
4 changed files with 150 additions and 69 deletions

View File

@ -0,0 +1,12 @@
eval("try { name(); } catch(e) {}");
function Employee ( name, dept ) {
this.name=name || ""
this.dept
}
function WorkerBee ( name, dept, projs ) {
this.base=Employee
this.base( name, print("WHAT"))
}
new WorkerBee;
WorkerBee();

View File

@ -980,7 +980,7 @@ class ScriptAnalysis
JS_ASSERT_IF(script->code[offset] != JSOP_TRAP,
which < GetDefCount(script, offset) +
(ExtendedDef(script->code + offset) ? 1 : 0));
types::TypeSet *array = (types::TypeSet *) (~0x1 & (size_t) getCode(offset).pushedTypes);
types::TypeSet *array = getCode(offset).pushedTypes;
JS_ASSERT(array);
return array + which;
}
@ -988,6 +988,8 @@ class ScriptAnalysis
return pushedTypes(pc - script->code, which);
}
bool hasPushedTypes(const jsbytecode *pc) { return getCode(pc).pushedTypes != NULL; }
types::TypeBarrier *typeBarriers(uint32 offset) {
if (getCode(offset).typeBarriers)
pruneTypeBarriers(offset);
@ -1083,6 +1085,15 @@ class ScriptAnalysis
}
LoopAnalysis *getLoop(const jsbytecode *pc) { return getLoop(pc - script->code); }
/* For a JSOP_CALL* op, get the pc of the corresponding JSOP_CALL/NEW/etc. */
jsbytecode *getCallPC(jsbytecode *pc)
{
JS_ASSERT(js_CodeSpec[*pc].format & JOF_CALLOP);
SSAUseChain *uses = useChain(SSAValue::PushedValue(pc - script->code, 1));
JS_ASSERT(uses && !uses->next && uses->popped);
return script->code + uses->offset;
}
/* Accessors for local variable information. */
bool localHasUseBeforeDef(uint32 local) {

View File

@ -543,6 +543,46 @@ TypeSet::addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
add(cx, ArenaNew<TypeConstraintProp>(cx->compartment->pool, script, pc, target, id, true));
}
/*
* Constraints for updating the 'this' types of callees on CALLPROP/CALLELEM.
* These are derived from the types on the properties themselves, rather than
* those pushed in the 'this' slot at the call site, which allows us to retain
* correlations between the type of the 'this' object and the associated
* callee scripts at polymorphic call sites.
*/
class TypeConstraintCallProp : public TypeConstraint
{
public:
jsbytecode *callpc;
/* Property being accessed. */
jsid id;
TypeConstraintCallProp(JSScript *script, jsbytecode *callpc, jsid id)
: TypeConstraint("callprop", script), callpc(callpc), id(id)
{
JS_ASSERT(script && callpc);
}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id)
{
/*
* For calls which will go through JSOP_NEW, don't add any constraints to
* modify the 'this' types of callees. The initial 'this' value will be
* outright ignored.
*/
jsbytecode *callpc = script->analysis(cx)->getCallPC(pc);
UntrapOpcode untrap(cx, script, callpc);
if (JSOp(*callpc) == JSOP_NEW)
return;
add(cx, ArenaNew<TypeConstraintCallProp>(cx->compartment->pool, script, callpc, id));
}
/* Constraints for determining the 'this' object at sites invoked using 'new'. */
class TypeConstraintNewObject : public TypeConstraint
{
@ -638,10 +678,11 @@ TypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target)
class TypeConstraintPropagateThis : public TypeConstraint
{
public:
jsbytecode *callpc;
jstype type;
TypeConstraintPropagateThis(JSScript *script, jstype type)
: TypeConstraint("propagatethis", script), type(type)
TypeConstraintPropagateThis(JSScript *script, jsbytecode *callpc, jstype type)
: TypeConstraint("propagatethis", script), callpc(callpc), type(type)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
@ -650,22 +691,13 @@ public:
void
TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type)
{
/*
* If this will definitely be popped by a JSOP_NEW, don't add a constraint
* to modify the 'this' types of callees. The initial 'this' value will be
* outright ignored.
*/
SSAValue calleev = SSAValue::PushedValue(pc - script->code, 0);
SSAUseChain *uses = script->analysis(cx)->useChain(calleev);
/* Don't add constraints when the call will be 'new' (see addCallProperty). */
jsbytecode *callpc = script->analysis(cx)->getCallPC(pc);
UntrapOpcode untrap(cx, script, callpc);
if (JSOp(*callpc) == JSOP_NEW)
return;
if (uses && !uses->next && uses->popped) {
jsbytecode *callpc = script->code + uses->offset;
UntrapOpcode untrap(cx, script, callpc);
if (JSOp(*callpc) == JSOP_NEW)
return;
}
add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool, script, type));
add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool, script, callpc, type));
}
/* Subset constraint which filters out primitive types. */
@ -974,7 +1006,7 @@ TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type)
}
if (type == TYPE_LAZYARGS) {
/* Catch cases which will be accounted for by the followEscapingArguments analysis. */
/* Ignore cases which will be accounted for by the followEscapingArguments analysis. */
if (assign || (id != JSID_VOID && id != id_length(cx)))
return;
@ -986,16 +1018,38 @@ TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type)
}
TypeObject *object = GetPropertyObject(cx, script, type);
if (object) {
if (object)
PropertyAccess(cx, script, pc, object, assign, target, id);
}
if (!object->unknownProperties() &&
(JSOp(*pc) == JSOP_CALLPROP || JSOp(*pc) == JSOP_CALLELEM)) {
JS_ASSERT(!assign);
void
TypeConstraintCallProp::newType(JSContext *cx, TypeSet *source, jstype type)
{
UntrapOpcode untrap(cx, script, callpc);
/*
* For CALLPROP and CALLELEM, we need to update not just the pushed types
* but also the 'this' types of possible callees. If we can't figure out
* that set of callees, monitor the call to make sure discovered callees
* get their 'this' types updated.
*/
if (type == TYPE_UNKNOWN || (!TypeIsObject(type) && !script->global)) {
cx->compartment->types.monitorBytecode(cx, script, callpc - script->code);
return;
}
TypeObject *object = GetPropertyObject(cx, script, type);
if (object) {
if (object->unknownProperties()) {
cx->compartment->types.monitorBytecode(cx, script, callpc - script->code);
} else {
TypeSet *types = object->getProperty(cx, id, false);
if (!types)
return;
types->addPropagateThis(cx, script, pc, type);
/* Bypass addPropagateThis, we already have the callpc. */
types->add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool,
script, callpc, type));
}
}
}
@ -1175,13 +1229,22 @@ TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
void
TypeConstraintPropagateThis::newType(JSContext *cx, TypeSet *source, jstype type)
{
/*
* Ignore callees that are calling natives or where the callee is unknown;
* the latter will be marked as monitored by a TypeConstraintCall.
*/
if (type == TYPE_UNKNOWN || !TypeIsObject(type))
if (type == TYPE_UNKNOWN) {
/*
* The callee is unknown, make sure the call is monitored so we pick up
* possible this/callee correlations. This only comes into play for
* CALLPROP and CALLELEM, for other calls we are past the type barrier
* already and a TypeConstraintCall will also monitor the call.
*/
cx->compartment->types.monitorBytecode(cx, script, callpc - script->code);
return;
}
/* Ignore calls to primitives, these will go through a stub. */
if (!TypeIsObject(type))
return;
/* Ignore calls to natives, these will be handled by TypeConstraintCall. */
TypeObject *object = (TypeObject*) type;
if (object->unknownProperties() || !object->isFunction)
return;
@ -2279,41 +2342,14 @@ TypeCompartment::addPendingRecompile(JSContext *cx, JSScript *script)
}
}
void
TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset)
static inline bool
MonitorResultUnknown(JSOp op)
{
if (script->analysis(cx)->getCode(offset).monitoredTypes)
return;
jsbytecode *pc = script->code + offset;
UntrapOpcode untrap(cx, script, pc);
/*
* Make sure monitoring is limited to property sets and calls where the
* target of the set/call could be statically unknown, and mark the bytecode
* results as unknown.
* Opcodes which can be monitored and whose result should be marked as
* unknown when doing so. :XXX: should use type barriers at calls.
*/
JSOp op = JSOp(*pc);
switch (op) {
case JSOP_SETNAME:
case JSOP_SETGNAME:
case JSOP_SETXMLNAME:
case JSOP_SETCONST:
case JSOP_SETELEM:
case JSOP_SETHOLE:
case JSOP_SETPROP:
case JSOP_SETMETHOD:
case JSOP_INITPROP:
case JSOP_INITMETHOD:
case JSOP_FORPROP:
case JSOP_FORNAME:
case JSOP_FORGNAME:
case JSOP_ENUMELEM:
case JSOP_ENUMCONSTELEM:
case JSOP_DEFFUN:
case JSOP_DEFFUN_FC:
case JSOP_ARRAYPUSH:
break;
case JSOP_INCNAME:
case JSOP_DECNAME:
case JSOP_NAMEINC:
@ -2335,11 +2371,30 @@ TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset)
case JSOP_FUNCALL:
case JSOP_FUNAPPLY:
case JSOP_NEW:
script->analysis(cx)->addPushedType(cx, offset, 0, TYPE_UNKNOWN);
break;
return true;
default:
TypeFailure(cx, "Monitoring unknown bytecode at #%u:%05u", script->id(), offset);
return false;
}
}
void
TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset)
{
ScriptAnalysis *analysis = script->analysis(cx);
if (analysis->getCode(offset).monitoredTypes)
return;
jsbytecode *pc = script->code + offset;
UntrapOpcode untrap(cx, script, pc);
/*
* We may end up monitoring opcodes before even analyzing them, as we can
* peek forward in CALLPROP and CALLELEM ops. Don't add the unknown result
* yet in this case, we will do so when analyzing the opcode.
*/
if (MonitorResultUnknown(JSOp(*pc)) && analysis->hasPushedTypes(pc))
analysis->addPushedType(cx, offset, 0, TYPE_UNKNOWN);
InferSpew(ISpewOps, "addMonitorNeeded: #%u:%05u", script->id(), offset);
@ -3195,6 +3250,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
InferSpew(ISpewOps, "typeSet: T%p pushed%u #%u:%05u", &pushed[i], i, script->id(), offset);
}
/* Add unknown result for opcodes which were monitored before being analyzed. */
if (code.monitoredTypes && MonitorResultUnknown(op))
pushed[0].addType(cx, TYPE_UNKNOWN);
/* Add type constraints for the various opcodes. */
switch (op) {
@ -3563,12 +3622,9 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
jsid id = GetAtomId(cx, script, pc, 0);
TypeSet *seen = script->bytecodeTypes(pc);
/*
* For JSOP_CALLPROP, this will inspect the pc and add PropagateThis
* constraints. Different types for the receiver may be correlated with
* different callee scripts, and we want to retain such correlations.
*/
poppedTypes(pc, 0)->addGetProperty(cx, script, pc, seen, id);
if (op == JSOP_CALLPROP)
poppedTypes(pc, 0)->addCallProperty(cx, script, pc, id);
seen->addSubset(cx, script, &pushed[0]);
if (op == JSOP_CALLPROP)
@ -3596,8 +3652,9 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
case JSOP_CALLELEM: {
TypeSet *seen = script->bytecodeTypes(pc);
/* Ditto the JSOP_CALLPROP case for propagating 'this'. */
poppedTypes(pc, 1)->addGetProperty(cx, script, pc, seen, JSID_VOID);
if (op == JSOP_CALLELEM)
poppedTypes(pc, 1)->addCallProperty(cx, script, pc, JSID_VOID);
seen->addSubset(cx, script, &pushed[0]);
if (op == JSOP_CALLELEM)

View File

@ -366,6 +366,7 @@ class TypeSet
TypeSet *target, jsid id);
void addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
TypeSet *target, jsid id);
void addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id);
void addNewObject(JSContext *cx, JSScript *script, TypeFunction *fun, TypeSet *target);
void addCall(JSContext *cx, TypeCallsite *site);
void addArith(JSContext *cx, JSScript *script,