Bug 1314545 - Fix Ion to handle stores to frozen elements correctly. r=nbp, a=lizzard
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1314545.js
@@ -0,0 +1,31 @@
+function f() {
+ Object.prototype[0] = 10;
+
+ var arr = [];
+ for (var i=3; i<20; i++) {
+ arr[0] = i;
+ Object.freeze(arr);
+ while (!inIon()) {}
+ }
+ assertEq(arr[0], 3);
+}
+f();
+
+function g() {
+ var c = 0;
+ Object.defineProperty(Object.prototype, 18, {set: function() { c++; }});
+
+ var arrays = [];
+ for (var i=0; i<2; i++)
+ arrays.push([1, 2]);
+
+ for (var i=0; i<20; i++) {
+ arrays[0][i] = 1;
+ arrays[1][i] = 2;
+ if (i === 0)
+ Object.freeze(arrays[0]);
+ while (!inIon()) {}
+ }
+ assertEq(c, 2);
+}
+g();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -10361,21 +10361,25 @@ IonBuilder::setElemTryDense(bool* emitte
// Don't generate a fast path if there have been bounds check failures
// and this access might be on a sparse property.
if (ElementAccessHasExtraIndexedProperty(this, object) && failedBoundsCheck_) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
return true;
}
// Emit dense setelem variant.
- if (!jsop_setelem_dense(conversion, object, index, value, unboxedType, writeHole))
- return false;
+ if (!jsop_setelem_dense(conversion, object, index, value, unboxedType, writeHole, emitted))
+ return false;
+
+ if (!*emitted) {
+ trackOptimizationOutcome(TrackedOutcome::NonWritableProperty);
+ return true;
+ }
trackOptimizationSuccess();
- *emitted = true;
return true;
}
bool
IonBuilder::setElemTryArguments(bool* emitted, MDefinition* object,
MDefinition* index, MDefinition* value)
{
MOZ_ASSERT(*emitted == false);
@@ -10447,29 +10451,40 @@ IonBuilder::setElemTryCache(bool* emitte
trackOptimizationSuccess();
*emitted = true;
return true;
}
bool
IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MDefinition* obj, MDefinition* id, MDefinition* value,
- JSValueType unboxedType, bool writeHole)
-{
+ JSValueType unboxedType, bool writeHole, bool* emitted)
+{
+ MOZ_ASSERT(*emitted == false);
+
MIRType elementType = MIRType::None;
if (unboxedType == JSVAL_TYPE_MAGIC)
elementType = DenseNativeElementType(constraints(), obj);
bool packed = ElementAccessIsPacked(constraints(), obj);
// Writes which are on holes in the object do not have to bail out if they
// cannot hit another indexed property on the object or its prototypes.
bool hasNoExtraIndexedProperty = !ElementAccessHasExtraIndexedProperty(this, obj);
bool mayBeFrozen = ElementAccessMightBeFrozen(constraints(), obj);
+ if (mayBeFrozen && !hasNoExtraIndexedProperty) {
+ // FallibleStoreElement does not know how to deal with extra indexed
+ // properties on the prototype. This case should be rare so we fall back
+ // to an IC.
+ return true;
+ }
+
+ *emitted = true;
+
// Ensure id is an integer.
MInstruction* idInt32 = MToInt32::New(alloc(), id);
current->add(idInt32);
id = idInt32;
if (NeedsPostBarrier(value))
current->add(MPostWriteElementBarrier::New(alloc(), obj, value, id));
@@ -10507,25 +10522,28 @@ IonBuilder::jsop_setelem_dense(Temporary
}
// Use MStoreElementHole if this SETELEM has written to out-of-bounds
// indexes in the past. Otherwise, use MStoreElement so that we can hoist
// the initialized length and bounds check.
// If an object may have been frozen, no previous expectation hold and we
// fallback to MFallibleStoreElement.
MInstruction* store;
- MStoreElementCommon *common = nullptr;
+ MStoreElementCommon* common = nullptr;
if (writeHole && hasNoExtraIndexedProperty && !mayBeFrozen) {
MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue, unboxedType);
store = ins;
common = ins;
current->add(ins);
current->push(value);
- } else if (hasNoExtraIndexedProperty && mayBeFrozen) {
+ } else if (mayBeFrozen) {
+ MOZ_ASSERT(hasNoExtraIndexedProperty,
+ "FallibleStoreElement codegen assumes no extra indexed properties");
+
bool strict = IsStrictSetPC(pc);
MFallibleStoreElement* ins = MFallibleStoreElement::New(alloc(), obj, elements, id,
newValue, unboxedType, strict);
store = ins;
common = ins;
current->add(ins);
current->push(value);
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -732,17 +732,17 @@ class IonBuilder
MOZ_MUST_USE bool jsop_getelem_dense(MDefinition* obj, MDefinition* index,
JSValueType unboxedType);
MOZ_MUST_USE bool jsop_getelem_typed(MDefinition* obj, MDefinition* index,
ScalarTypeDescr::Type arrayType);
MOZ_MUST_USE bool jsop_setelem();
MOZ_MUST_USE bool jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MDefinition* object, MDefinition* index,
MDefinition* value, JSValueType unboxedType,
- bool writeHole);
+ bool writeHole, bool* emitted);
MOZ_MUST_USE bool jsop_setelem_typed(ScalarTypeDescr::Type arrayType,
MDefinition* object, MDefinition* index,
MDefinition* value);
MOZ_MUST_USE bool jsop_length();
MOZ_MUST_USE bool jsop_length_fastPath();
MOZ_MUST_USE bool jsop_arguments();
MOZ_MUST_USE bool jsop_arguments_getelem();
MOZ_MUST_USE bool jsop_runonce();