[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, JS_ASSERT_IF(script->code[offset] != JSOP_TRAP,
which < GetDefCount(script, offset) + which < GetDefCount(script, offset) +
(ExtendedDef(script->code + offset) ? 1 : 0)); (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); JS_ASSERT(array);
return array + which; return array + which;
} }
@ -988,6 +988,8 @@ class ScriptAnalysis
return pushedTypes(pc - script->code, which); return pushedTypes(pc - script->code, which);
} }
bool hasPushedTypes(const jsbytecode *pc) { return getCode(pc).pushedTypes != NULL; }
types::TypeBarrier *typeBarriers(uint32 offset) { types::TypeBarrier *typeBarriers(uint32 offset) {
if (getCode(offset).typeBarriers) if (getCode(offset).typeBarriers)
pruneTypeBarriers(offset); pruneTypeBarriers(offset);
@ -1083,6 +1085,15 @@ class ScriptAnalysis
} }
LoopAnalysis *getLoop(const jsbytecode *pc) { return getLoop(pc - script->code); } 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. */ /* Accessors for local variable information. */
bool localHasUseBeforeDef(uint32 local) { 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)); 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'. */ /* Constraints for determining the 'this' object at sites invoked using 'new'. */
class TypeConstraintNewObject : public TypeConstraint class TypeConstraintNewObject : public TypeConstraint
{ {
@ -638,10 +678,11 @@ TypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target)
class TypeConstraintPropagateThis : public TypeConstraint class TypeConstraintPropagateThis : public TypeConstraint
{ {
public: public:
jsbytecode *callpc;
jstype type; jstype type;
TypeConstraintPropagateThis(JSScript *script, jstype type) TypeConstraintPropagateThis(JSScript *script, jsbytecode *callpc, jstype type)
: TypeConstraint("propagatethis", script), type(type) : TypeConstraint("propagatethis", script), callpc(callpc), type(type)
{} {}
void newType(JSContext *cx, TypeSet *source, jstype type); void newType(JSContext *cx, TypeSet *source, jstype type);
@ -650,22 +691,13 @@ public:
void void
TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type) TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type)
{ {
/* /* Don't add constraints when the call will be 'new' (see addCallProperty). */
* If this will definitely be popped by a JSOP_NEW, don't add a constraint jsbytecode *callpc = script->analysis(cx)->getCallPC(pc);
* to modify the 'this' types of callees. The initial 'this' value will be UntrapOpcode untrap(cx, script, callpc);
* outright ignored. if (JSOp(*callpc) == JSOP_NEW)
*/ return;
SSAValue calleev = SSAValue::PushedValue(pc - script->code, 0);
SSAUseChain *uses = script->analysis(cx)->useChain(calleev);
if (uses && !uses->next && uses->popped) { add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool, script, callpc, type));
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));
} }
/* Subset constraint which filters out primitive types. */ /* Subset constraint which filters out primitive types. */
@ -974,7 +1006,7 @@ TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type)
} }
if (type == TYPE_LAZYARGS) { 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))) if (assign || (id != JSID_VOID && id != id_length(cx)))
return; return;
@ -986,16 +1018,38 @@ TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type)
} }
TypeObject *object = GetPropertyObject(cx, script, type); TypeObject *object = GetPropertyObject(cx, script, type);
if (object) { if (object)
PropertyAccess(cx, script, pc, object, assign, target, id); PropertyAccess(cx, script, pc, object, assign, target, id);
}
if (!object->unknownProperties() && void
(JSOp(*pc) == JSOP_CALLPROP || JSOp(*pc) == JSOP_CALLELEM)) { TypeConstraintCallProp::newType(JSContext *cx, TypeSet *source, jstype type)
JS_ASSERT(!assign); {
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); TypeSet *types = object->getProperty(cx, id, false);
if (!types) if (!types)
return; 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 void
TypeConstraintPropagateThis::newType(JSContext *cx, TypeSet *source, jstype type) TypeConstraintPropagateThis::newType(JSContext *cx, TypeSet *source, jstype type)
{ {
/* if (type == TYPE_UNKNOWN) {
* Ignore callees that are calling natives or where the callee is unknown; /*
* the latter will be marked as monitored by a TypeConstraintCall. * The callee is unknown, make sure the call is monitored so we pick up
*/ * possible this/callee correlations. This only comes into play for
if (type == TYPE_UNKNOWN || !TypeIsObject(type)) * 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; return;
/* Ignore calls to natives, these will be handled by TypeConstraintCall. */
TypeObject *object = (TypeObject*) type; TypeObject *object = (TypeObject*) type;
if (object->unknownProperties() || !object->isFunction) if (object->unknownProperties() || !object->isFunction)
return; return;
@ -2279,41 +2342,14 @@ TypeCompartment::addPendingRecompile(JSContext *cx, JSScript *script)
} }
} }
void static inline bool
TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset) 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 * Opcodes which can be monitored and whose result should be marked as
* target of the set/call could be statically unknown, and mark the bytecode * unknown when doing so. :XXX: should use type barriers at calls.
* results as unknown.
*/ */
JSOp op = JSOp(*pc);
switch (op) { 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_INCNAME:
case JSOP_DECNAME: case JSOP_DECNAME:
case JSOP_NAMEINC: case JSOP_NAMEINC:
@ -2335,11 +2371,30 @@ TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset)
case JSOP_FUNCALL: case JSOP_FUNCALL:
case JSOP_FUNAPPLY: case JSOP_FUNAPPLY:
case JSOP_NEW: case JSOP_NEW:
script->analysis(cx)->addPushedType(cx, offset, 0, TYPE_UNKNOWN); return true;
break;
default: 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); 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); 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. */ /* Add type constraints for the various opcodes. */
switch (op) { switch (op) {
@ -3563,12 +3622,9 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
jsid id = GetAtomId(cx, script, pc, 0); jsid id = GetAtomId(cx, script, pc, 0);
TypeSet *seen = script->bytecodeTypes(pc); 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); 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]); seen->addSubset(cx, script, &pushed[0]);
if (op == JSOP_CALLPROP) if (op == JSOP_CALLPROP)
@ -3596,8 +3652,9 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
case JSOP_CALLELEM: { case JSOP_CALLELEM: {
TypeSet *seen = script->bytecodeTypes(pc); TypeSet *seen = script->bytecodeTypes(pc);
/* Ditto the JSOP_CALLPROP case for propagating 'this'. */
poppedTypes(pc, 1)->addGetProperty(cx, script, pc, seen, JSID_VOID); 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]); seen->addSubset(cx, script, &pushed[0]);
if (op == JSOP_CALLELEM) if (op == JSOP_CALLELEM)

View File

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