mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 832329 - Improve analysis of definite properties for 'new' scripts, r=jandem, a=nonlibxul.
This commit is contained in:
parent
893e6d1c91
commit
5d34c466fc
24
js/src/jit-test/tests/basic/new-read-before-write.js
Normal file
24
js/src/jit-test/tests/basic/new-read-before-write.js
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
function Foo() {
|
||||
var x = this.property;
|
||||
this.property = 5;
|
||||
glob = x;
|
||||
}
|
||||
Foo.prototype.property = 10;
|
||||
for (var i = 0; i < 10; i++) {
|
||||
new Foo();
|
||||
assertEq(glob, 10);
|
||||
}
|
||||
|
||||
function Bar() {
|
||||
this.property;
|
||||
this.other = 5;
|
||||
}
|
||||
Bar.prototype.other = 10;
|
||||
Object.defineProperty(Bar.prototype, "property", {
|
||||
get: function() { glob = this.other; }
|
||||
});
|
||||
for (var i = 0; i < 10; i++) {
|
||||
new Bar();
|
||||
assertEq(glob, 10);
|
||||
}
|
@ -4677,18 +4677,18 @@ ScriptAnalysis::integerOperation(jsbytecode *pc)
|
||||
|
||||
/*
|
||||
* Persistent constraint clearing out newScript and definite properties from
|
||||
* an object should a property on another object get a setter.
|
||||
* an object should a property on another object get a getter or setter.
|
||||
*/
|
||||
class TypeConstraintClearDefiniteSetter : public TypeConstraint
|
||||
class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint
|
||||
{
|
||||
public:
|
||||
TypeObject *object;
|
||||
|
||||
TypeConstraintClearDefiniteSetter(TypeObject *object)
|
||||
TypeConstraintClearDefiniteGetterSetter(TypeObject *object)
|
||||
: object(object)
|
||||
{}
|
||||
|
||||
const char *kind() { return "clearDefiniteSetter"; }
|
||||
const char *kind() { return "clearDefiniteGetterSetter"; }
|
||||
|
||||
void newPropertyState(JSContext *cx, TypeSet *source)
|
||||
{
|
||||
@ -4707,6 +4707,28 @@ class TypeConstraintClearDefiniteSetter : public TypeConstraint
|
||||
void newType(JSContext *cx, TypeSet *source, Type type) {}
|
||||
};
|
||||
|
||||
static bool
|
||||
AddClearDefiniteGetterSetterForPrototypeChain(JSContext *cx, TypeObject *type, jsid id)
|
||||
{
|
||||
/*
|
||||
* Ensure that if the properties named here could have a getter, setter or
|
||||
* a permanent property in any transitive prototype, the definite
|
||||
* properties get cleared from the shape.
|
||||
*/
|
||||
RootedObject parent(cx, type->proto);
|
||||
while (parent) {
|
||||
TypeObject *parentObject = parent->getType(cx);
|
||||
if (parentObject->unknownProperties())
|
||||
return false;
|
||||
HeapTypeSet *parentTypes = parentObject->getProperty(cx, id, false);
|
||||
if (!parentTypes || parentTypes->ownProperty(true))
|
||||
return false;
|
||||
parentTypes->add(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteGetterSetter>(type));
|
||||
parent = parent->getProto();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Constraint which clears definite properties on an object should a type set
|
||||
* contain any types other than a single object.
|
||||
@ -4731,15 +4753,25 @@ class TypeConstraintClearDefiniteSingle : public TypeConstraint
|
||||
}
|
||||
};
|
||||
|
||||
static bool
|
||||
AnalyzePoppedThis(JSContext *cx, Vector<SSAUseChain *> *pendingPoppedThis,
|
||||
TypeObject *type, HandleFunction fun, MutableHandleObject pbaseobj,
|
||||
Vector<TypeNewScript::Initializer> *initializerList);
|
||||
struct NewScriptPropertiesState
|
||||
{
|
||||
RootedFunction thisFunction;
|
||||
RootedObject baseobj;
|
||||
Vector<TypeNewScript::Initializer> initializerList;
|
||||
Vector<jsid> accessedProperties;
|
||||
|
||||
NewScriptPropertiesState(JSContext *cx)
|
||||
: thisFunction(cx), baseobj(cx), initializerList(cx), accessedProperties(cx)
|
||||
{}
|
||||
};
|
||||
|
||||
static bool
|
||||
AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
MutableHandleObject pbaseobj,
|
||||
Vector<TypeNewScript::Initializer> *initializerList)
|
||||
AnalyzePoppedThis(JSContext *cx, Vector<SSAUseChain *> *pendingPoppedThis,
|
||||
TypeObject *type, JSFunction *fun, NewScriptPropertiesState &state);
|
||||
|
||||
static bool
|
||||
AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun,
|
||||
NewScriptPropertiesState &state)
|
||||
{
|
||||
AssertCanGC();
|
||||
|
||||
@ -4753,7 +4785,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
* have been cleared).
|
||||
*/
|
||||
|
||||
if (initializerList->length() > 50) {
|
||||
if (state.initializerList.length() > 50) {
|
||||
/*
|
||||
* Bail out on really long initializer lists (far longer than maximum
|
||||
* number of properties we can track), we may be recursing.
|
||||
@ -4763,7 +4795,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
|
||||
RootedScript script(cx, fun->nonLazyScript());
|
||||
if (!JSScript::ensureRanAnalysis(cx, script) || !JSScript::ensureRanInference(cx, script)) {
|
||||
pbaseobj.set(NULL);
|
||||
state.baseobj = NULL;
|
||||
cx->compartment->types.setPendingNukeTypes(cx);
|
||||
return false;
|
||||
}
|
||||
@ -4808,7 +4840,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
*/
|
||||
if (op == JSOP_RETURN || op == JSOP_STOP || op == JSOP_RETRVAL) {
|
||||
if (offset < lastThisPopped) {
|
||||
pbaseobj.set(NULL);
|
||||
state.baseobj = NULL;
|
||||
entirelyAnalyzed = false;
|
||||
break;
|
||||
}
|
||||
@ -4820,7 +4852,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
/* 'this' can escape through a call to eval. */
|
||||
if (op == JSOP_EVAL) {
|
||||
if (offset < lastThisPopped)
|
||||
pbaseobj.set(NULL);
|
||||
state.baseobj = NULL;
|
||||
entirelyAnalyzed = false;
|
||||
break;
|
||||
}
|
||||
@ -4857,10 +4889,8 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
if (!pendingPoppedThis.empty() &&
|
||||
offset >= pendingPoppedThis.back()->offset) {
|
||||
lastThisPopped = pendingPoppedThis[0]->offset;
|
||||
if (!AnalyzePoppedThis(cx, &pendingPoppedThis, type, fun, pbaseobj,
|
||||
initializerList)) {
|
||||
if (!AnalyzePoppedThis(cx, &pendingPoppedThis, type, fun, state))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pendingPoppedThis.append(uses)) {
|
||||
@ -4885,8 +4915,8 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
*/
|
||||
if (entirelyAnalyzed &&
|
||||
!pendingPoppedThis.empty() &&
|
||||
!AnalyzePoppedThis(cx, &pendingPoppedThis, type, fun, pbaseobj,
|
||||
initializerList)) {
|
||||
!AnalyzePoppedThis(cx, &pendingPoppedThis, type, fun, state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4896,8 +4926,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
|
||||
|
||||
static bool
|
||||
AnalyzePoppedThis(JSContext *cx, Vector<SSAUseChain *> *pendingPoppedThis,
|
||||
TypeObject *type, HandleFunction fun, MutableHandleObject pbaseobj,
|
||||
Vector<TypeNewScript::Initializer> *initializerList)
|
||||
TypeObject *type, JSFunction *fun, NewScriptPropertiesState &state)
|
||||
{
|
||||
RootedScript script(cx, fun->nonLazyScript());
|
||||
ScriptAnalysis *analysis = script->analysis();
|
||||
@ -4922,53 +4951,83 @@ AnalyzePoppedThis(JSContext *cx, Vector<SSAUseChain *> *pendingPoppedThis,
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Ensure that if the properties named here could have a setter or
|
||||
* a permanent property in any transitive prototype, the definite
|
||||
* properties get cleared from the shape.
|
||||
* Don't add definite properties for properties that were already
|
||||
* read in the constructor.
|
||||
*/
|
||||
RootedObject parent(cx, type->proto);
|
||||
while (parent) {
|
||||
TypeObject *parentObject = parent->getType(cx);
|
||||
if (parentObject->unknownProperties())
|
||||
for (size_t i = 0; i < state.accessedProperties.length(); i++) {
|
||||
if (state.accessedProperties[i] == id)
|
||||
return false;
|
||||
HeapTypeSet *parentTypes = parentObject->getProperty(cx, id, false);
|
||||
if (!parentTypes || parentTypes->ownProperty(true))
|
||||
return false;
|
||||
parentTypes->add(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteSetter>(type));
|
||||
parent = parent->getProto();
|
||||
}
|
||||
|
||||
unsigned slotSpan = pbaseobj->slotSpan();
|
||||
if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, type, id))
|
||||
return false;
|
||||
|
||||
unsigned slotSpan = state.baseobj->slotSpan();
|
||||
RootedValue value(cx, UndefinedValue());
|
||||
if (!DefineNativeProperty(cx, pbaseobj, id, value, NULL, NULL,
|
||||
if (!DefineNativeProperty(cx, state.baseobj, id, value, NULL, NULL,
|
||||
JSPROP_ENUMERATE, 0, 0, DNP_SKIP_TYPE)) {
|
||||
cx->compartment->types.setPendingNukeTypes(cx);
|
||||
pbaseobj.set(NULL);
|
||||
state.baseobj = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pbaseobj->inDictionaryMode()) {
|
||||
pbaseobj.set(NULL);
|
||||
if (state.baseobj->inDictionaryMode()) {
|
||||
state.baseobj = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pbaseobj->slotSpan() == slotSpan) {
|
||||
if (state.baseobj->slotSpan() == slotSpan) {
|
||||
/* Set a duplicate property. */
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeNewScript::Initializer setprop(TypeNewScript::Initializer::SETPROP, uses->offset);
|
||||
if (!initializerList->append(setprop)) {
|
||||
if (!state.initializerList.append(setprop)) {
|
||||
cx->compartment->types.setPendingNukeTypes(cx);
|
||||
pbaseobj.set(NULL);
|
||||
state.baseobj = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pbaseobj->slotSpan() >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
|
||||
if (state.baseobj->slotSpan() >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
|
||||
/* Maximum number of definite properties added. */
|
||||
return false;
|
||||
}
|
||||
} else if (op == JSOP_FUNCALL && uses->u.which == GET_ARGC(pc) - 1) {
|
||||
} else if (op == JSOP_GETPROP && uses->u.which == 0) {
|
||||
/*
|
||||
* Properties can be read from the 'this' object if the following hold:
|
||||
*
|
||||
* - The read is not on a getter along the prototype chain, which
|
||||
* could cause 'this' to escape.
|
||||
*
|
||||
* - The accessed property is either already a definite property or
|
||||
* is not later added as one. Since the definite properties are
|
||||
* added to the object at the point of its creation, reading a
|
||||
* definite property before it is assigned could incorrectly hit.
|
||||
*/
|
||||
RootedId id(cx, NameToId(script->getName(GET_UINT32_INDEX(pc))));
|
||||
if (IdToTypeId(id) != id)
|
||||
return false;
|
||||
if (!state.baseobj->nativeLookup(cx, id) && !state.accessedProperties.append(id.get())) {
|
||||
cx->compartment->types.setPendingNukeTypes(cx);
|
||||
state.baseobj = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, type, id))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Populate the read with any value from the type's proto, if
|
||||
* this is being used in a function call and we need to analyze the
|
||||
* callee's behavior.
|
||||
*/
|
||||
Shape *shape = type->proto ? type->proto->nativeLookup(cx, id) : NULL;
|
||||
if (shape && shape->hasSlot()) {
|
||||
Value protov = type->proto->getSlot(shape->slot());
|
||||
TypeSet *types = script->analysis()->bytecodeTypes(pc);
|
||||
types->addType(cx, GetValueType(cx, protov));
|
||||
}
|
||||
} else if ((op == JSOP_FUNCALL || op == JSOP_FUNAPPLY) && uses->u.which == GET_ARGC(pc) - 1) {
|
||||
/*
|
||||
* Passed as the first parameter to Function.call. Follow control
|
||||
* into the callee, and add any definite properties it assigns to
|
||||
@ -4999,15 +5058,20 @@ AnalyzePoppedThis(JSContext *cx, Vector<SSAUseChain *> *pendingPoppedThis,
|
||||
StackTypeSet *funcallTypes = analysis->poppedTypes(pc, GET_ARGC(pc) + 1);
|
||||
StackTypeSet *scriptTypes = analysis->poppedTypes(pc, GET_ARGC(pc));
|
||||
|
||||
/* Need to definitely be calling Function.call on a specific script. */
|
||||
RootedFunction function(cx);
|
||||
/* Need to definitely be calling Function.call/apply on a specific script. */
|
||||
JSFunction *function;
|
||||
{
|
||||
RawObject funcallObj = funcallTypes->getSingleton();
|
||||
RawObject scriptObj = scriptTypes->getSingleton();
|
||||
if (!funcallObj || !scriptObj || !scriptObj->isFunction() ||
|
||||
if (!funcallObj || !funcallObj->isFunction() ||
|
||||
funcallObj->toFunction()->isInterpreted() ||
|
||||
!scriptObj || !scriptObj->isFunction() ||
|
||||
!scriptObj->toFunction()->isInterpreted()) {
|
||||
return false;
|
||||
}
|
||||
Native native = funcallObj->toFunction()->native();
|
||||
if (native != js_fun_call && native != js_fun_apply)
|
||||
return false;
|
||||
function = scriptObj->toFunction();
|
||||
}
|
||||
|
||||
@ -5021,21 +5085,19 @@ AnalyzePoppedThis(JSContext *cx, Vector<SSAUseChain *> *pendingPoppedThis,
|
||||
cx->analysisLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type));
|
||||
|
||||
TypeNewScript::Initializer pushframe(TypeNewScript::Initializer::FRAME_PUSH, uses->offset);
|
||||
if (!initializerList->append(pushframe)) {
|
||||
if (!state.initializerList.append(pushframe)) {
|
||||
cx->compartment->types.setPendingNukeTypes(cx);
|
||||
pbaseobj.set(NULL);
|
||||
state.baseobj = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AnalyzeNewScriptProperties(cx, type, function,
|
||||
pbaseobj, initializerList)) {
|
||||
if (!AnalyzeNewScriptProperties(cx, type, function, state))
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeNewScript::Initializer popframe(TypeNewScript::Initializer::FRAME_POP, 0);
|
||||
if (!initializerList->append(popframe)) {
|
||||
if (!state.initializerList.append(popframe)) {
|
||||
cx->compartment->types.setPendingNukeTypes(cx);
|
||||
pbaseobj.set(NULL);
|
||||
state.baseobj = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -5063,17 +5125,22 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
if (type->unknownProperties())
|
||||
return;
|
||||
|
||||
NewScriptPropertiesState state(cx);
|
||||
state.thisFunction = fun;
|
||||
|
||||
/* Strawman object to add properties to and watch for duplicates. */
|
||||
RootedObject baseobj(cx, NewBuiltinClassInstance(cx, &ObjectClass, gc::FINALIZE_OBJECT16));
|
||||
if (!baseobj) {
|
||||
state.baseobj = NewBuiltinClassInstance(cx, &ObjectClass, gc::FINALIZE_OBJECT16);
|
||||
if (!state.baseobj) {
|
||||
if (type->newScript)
|
||||
type->clearNewScript(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<TypeNewScript::Initializer> initializerList(cx);
|
||||
AnalyzeNewScriptProperties(cx, type, fun, &baseobj, &initializerList);
|
||||
if (!baseobj || baseobj->slotSpan() == 0 || !!(type->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED)) {
|
||||
AnalyzeNewScriptProperties(cx, type, fun, state);
|
||||
if (!state.baseobj ||
|
||||
state.baseobj->slotSpan() == 0 ||
|
||||
!!(type->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED))
|
||||
{
|
||||
if (type->newScript)
|
||||
type->clearNewScript(cx);
|
||||
return;
|
||||
@ -5085,15 +5152,15 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
* the properties added to baseobj match the type's definite properties.
|
||||
*/
|
||||
if (type->newScript) {
|
||||
if (!type->matchDefiniteProperties(baseobj))
|
||||
if (!type->matchDefiniteProperties(state.baseobj))
|
||||
type->clearNewScript(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
gc::AllocKind kind = gc::GetGCObjectKind(baseobj->slotSpan());
|
||||
gc::AllocKind kind = gc::GetGCObjectKind(state.baseobj->slotSpan());
|
||||
|
||||
/* We should not have overflowed the maximum number of fixed slots for an object. */
|
||||
JS_ASSERT(gc::GetGCKindSlots(kind) >= baseobj->slotSpan());
|
||||
JS_ASSERT(gc::GetGCKindSlots(kind) >= state.baseobj->slotSpan());
|
||||
|
||||
TypeNewScript::Initializer done(TypeNewScript::Initializer::DONE, 0);
|
||||
|
||||
@ -5102,17 +5169,17 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
* than we will use for subsequent new objects. Generate an object with the
|
||||
* appropriate final shape.
|
||||
*/
|
||||
RootedShape shape(cx, baseobj->lastProperty());
|
||||
baseobj = NewReshapedObject(cx, type, baseobj->getParent(), kind, shape);
|
||||
if (!baseobj ||
|
||||
!type->addDefiniteProperties(cx, baseobj) ||
|
||||
!initializerList.append(done)) {
|
||||
RootedShape shape(cx, state.baseobj->lastProperty());
|
||||
state.baseobj = NewReshapedObject(cx, type, state.baseobj->getParent(), kind, shape);
|
||||
if (!state.baseobj ||
|
||||
!type->addDefiniteProperties(cx, state.baseobj) ||
|
||||
!state.initializerList.append(done)) {
|
||||
cx->compartment->types.setPendingNukeTypes(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t numBytes = sizeof(TypeNewScript)
|
||||
+ (initializerList.length() * sizeof(TypeNewScript::Initializer));
|
||||
+ (state.initializerList.length() * sizeof(TypeNewScript::Initializer));
|
||||
#ifdef JSGC_ROOT_ANALYSIS
|
||||
// calloc can legitimately return a pointer that appears to be poisoned.
|
||||
void *p;
|
||||
@ -5133,11 +5200,13 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
|
||||
type->newScript->fun = fun;
|
||||
type->newScript->allocKind = kind;
|
||||
type->newScript->shape = baseobj->lastProperty();
|
||||
type->newScript->shape = state.baseobj->lastProperty();
|
||||
|
||||
type->newScript->initializerList = (TypeNewScript::Initializer *)
|
||||
((char *) type->newScript.get() + sizeof(TypeNewScript));
|
||||
PodCopy(type->newScript->initializerList, initializerList.begin(), initializerList.length());
|
||||
PodCopy(type->newScript->initializerList,
|
||||
state.initializerList.begin(),
|
||||
state.initializerList.length());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Reference in New Issue
Block a user