Closed Bug 596805 Opened 9 years ago Closed 9 years ago

Crash [@ JSObject::allocSlot] or "Assertion failure: t->freelist < slotSpan" or "Assertion failure: JSVAL_IS_DOUBLE_IMPL(data),"

Categories

(Core :: JavaScript Engine, defect, P1, critical)

defect

Tracking

()

RESOLVED FIXED
mozilla2.0b7
Tracking Status
blocking2.0 --- beta7+

People

(Reporter: jruderman, Assigned: brendan)

References

(Blocks 1 open bug)

Details

(4 keywords, Whiteboard: [ETA: 9/21][sg:critical] fixed-in-tracemonkey)

Attachments

(1 file, 5 obsolete files)

var f = function(){};
for (var y in f);
f.j = 0;
Object.defineProperty(f, "k", ({configurable: true}));
delete f.j;
Object.defineProperty(f, "k", ({get: function() {}}));

Assertion failure: t->freelist < slotSpan, at jsscope.h:364The first bad revision is:
changeset:   53414:b1facf8ba54e
user:        Brendan Eich
date:        Thu Sep 02 14:50:44 2010 -0700
summary:     Eliminate JSObject::freeslot via monotonic lastProp->freeslot (592556, r=jorendorff,dvander).
Assignee: general → brendan
Status: NEW → ASSIGNED
blocking2.0: --- → ?
OS: Mac OS X → All
Priority: -- → P1
Hardware: x86 → All
Target Milestone: --- → mozilla2.0b7
js> var f = function(){};

f has empty shape, slotSpan 3

js> for (var y in f);

f has {prototype, length, arity, name, arguments, caller}, slotSpan 9

js> f.j = 0;

f has {..., j}, slotSpan 10, j has slot 9

js> Object.defineProperty(f, "k", ({configurable: true}));

f has {..., j, k}, slotSpan 11, k has slot 10

js> delete f.j;

f has {..., k}, slotSpan 11, lastProp->table->freelist is 9

NB: lastProp->parent->id is 'caller' and lastProp->parent->slotSpan is 9

js> Object.defineProperty(f, "k", ({get: function() {}}));

f has {..., k}, slotSpan 9, k has no slot, assert setting table

The redefinition of k replaces lastProp, temporarily exposing 'caller' with slotSpan 9. The new shape for k has no slot, so its slotSpan is same as its parent's, 9. But we forgot about freelist, and worse, JSObject::putProperty sets slot 9 to undefined in DEBUG builds, since slot 9 is < numSlots().

This is a putProperty bug. I will fix today, should go into b7.

/be
blocking2.0: ? → beta7+
Worse, and not covered by bug 594899, this shows a slot leak: k-the-first has a slot, k-the-second does not. Fixing the slot leak should help focus the fix for the freelist invariant violation.

/be
blocking2.0: beta7+ → ?
blocking2.0: ? → beta7+
Duplicate of this bug: 596873
for each(let c in [0, 0, 0, 0, 0]) {
  try { (function() {
      this.c = this;
      this.e = arguments;
      Object.defineProperty(this, "d", {
        get: Math.pow,
        configurable: true
      });
      delete this.e;
      delete this.c;
      Object.defineProperty(this, "d", {
        writable: true
      });
      if (Math.tan( - 1)) {
        Object.defineProperty(this, {});
      }
    } (c));
  } catch(e) {}
}

crashes js opt shell on TM changeset dfcf5611ce02 at JSObject::allocSlot when passed in as a CLI argument, and asserts at Assertion failure: JSVAL_IS_DOUBLE_IMPL(data), (this assertion was duped here in comment #3)

Turning bug s-s because this seems to be accessing a scary memory address.

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x098139f0
0x000939b8 in JSObject::allocSlot ()
(gdb) bt
#0  0x000939b8 in JSObject::allocSlot ()
#1  0x000f9aa3 in JSObject::getChildProperty ()
Previous frame inner to this frame (gdb could not unwind past this frame)
(gdb) x/i $eip
0x939b8 <_ZN8JSObject9allocSlotEP9JSContextPj+72>:      mov    (%ebx),%eax
(gdb) x/b $ebx
0x98139f0:      Cannot access memory at address 0x98139f0
Summary: "Assertion failure: t->freelist < slotSpan" → Crash [@ JSObject::allocSlot] or "Assertion failure: t->freelist < slotSpan" or "Assertion failure: JSVAL_IS_DOUBLE_IMPL(data),"
Group: core-security
Attached patch proposed fix (obsolete) — Splinter Review
The jsscope.cpp code can be simplified quite a bit based on ideas in bug 593129, but I need to spot-fix this bug.

/be
Attachment #476708 - Flags: review?(jorendorff)
>-        if (lastProp->slotSpan < numSlots())
>+        if (lastProp->slotSpan < numSlots() && !(table && table->freelist == lastProp->slotSpan))
>             getSlotRef(lastProp->slotSpan).setUndefined();

I think a comment like

  /*
   * At this point, we may have just added a slot to table->freelist which
   * is not covered by lastProp->slotSpan. This temporary breaking of the
   * slotSpan invariant is fixed below.
   */

is warranted.

>+            if (shape->slotSpan == table->freelist)
>+                ++shape->slotSpan;

OK, but we could alternatively remove the slot from the freelist, right?

Note that this is not done if getChildProperty() fails. I think that is a bug in the unlikely case that we run out of memory here and survive.

Long-term, can we move the responsibility for slot allocation into these methods instead of having the caller do it and pass a slot parameter?
Comment on attachment 476708 [details] [diff] [review]
proposed fix

What I mean by that comment about adding a comment is that what's happening here is, that an invariant is temporarily relaxed within this function, and must be fixed up before we return. The comment in the patch

+            /*
+             * If shape, the new lastProp in this dictionary-mode object, has a
+             * slotSpan equal to the table freelist, bump shape->slotSpan so it
+             * covers the free slot.
+             */

tells what, but left me wondering why.

r=me with comments addressed.
Attachment #476708 - Flags: review?(jorendorff) → review+
(In reply to comment #6)
> >-        if (lastProp->slotSpan < numSlots())
> >+        if (lastProp->slotSpan < numSlots() && !(table && table->freelist == lastProp->slotSpan))
> >             getSlotRef(lastProp->slotSpan).setUndefined();
> 
> I think a comment like
> 
>   /*
>    * At this point, we may have just added a slot to table->freelist which
>    * is not covered by lastProp->slotSpan. This temporary breaking of the
>    * slotSpan invariant is fixed below.
>    */
> 
> is warranted.

For sure.

> >+            if (shape->slotSpan == table->freelist)
> >+                ++shape->slotSpan;
> 
> OK, but we could alternatively remove the slot from the freelist, right?

Yeah but strictly more work and more Object/Shape concern mixing AKA layering violation -- see below.

> Note that this is not done if getChildProperty() fails. I think that is a bug
> in the unlikely case that we run out of memory here and survive.

It is a bug, again see below.

> Long-term, can we move the responsibility for slot allocation into these
> methods instead of having the caller do it and pass a slot parameter?

Not into these methods -- slot allocation should move back to jsobj.cpp and JSObject::* methods, these jsscope.cpp (jsshape.cpp, Shape.cpp) methods should be Shape::* methods, which do not allocate slots from objects, or free slots back to objects.

Then we can have Object-centric code do something like:

    if (Shape *shape = parent->getChild(...)) {
        if (shape->hasSlot())
            allocSlot(shape->slot);
    }

(sketching, ignoring errors), and:

    freeSlot(shape->slot);
    removeShape(shape);

(latter only in js_DeleteProperty, which should be JSObject::deleteProperty with a bit of work). See bug 593129.

/be
Depends on: 597945
Whiteboard: [ETA: 9/21]
Whiteboard: [ETA: 9/21] → [ETA: 9/21][sg:critical]
Attached patch proposed fix (obsolete) — Splinter Review
Jason, the assertion we discussed on IRC,

    JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen());

at the top of putProperty smoked out the (in hindsight obvious) offenders who can extend Call objects, what with their shared frozen dictionary shapes (created for fun->u.i.names if non-empty-pattern destructuring params or overlong parameter lists are used). The same assertion in changeProperty has yet to botch and should not or something is up.

So now putProperty compensates using newDictionaryList and addPropertyInternal (note new name and comment in jsobj.h) asserts.

The three-way structure of putProperty is much better. Mutable dictionary shapes FTW!

Only thing left is bug 593129 cleanup (a shallow Shape class hierarchy, moving slot alloc/free upstairs to jsobj.cpp, Shape not JSObject methods, etc.).

/be
Attachment #476708 - Attachment is obsolete: true
Attachment #477347 - Flags: review?(jorendorff)
Preliminary comments. I have an appointment this morning, so I can't finish the
review right now.

I like the new structure of putProperty a lot better.

In jsscope.cpp, JSObject::putProperty:
>+            setOwnShape(shape->shape);

Doesn't this conflict with what it says in checkShapeConsistency?

    if (hasOwnShape())
        JS_ASSERT(objShape != lastProp->shape);
    else
        JS_ASSERT(objShape == lastProp->shape);

>+    /*
>+     * Second case: reordering shapes by overwriting a non-last shape. Let's
>+     * use removeShape and addPropertyInternal to keep things simple and avoid
>+     * temporarily violating invariants.
>+     */
>+    if (shape != lastProp) {
>+        if (!removeShape(cx, hadSlot, shape, spp))
>+            return NULL;
>[...]
>+        spp = nativeSearch(id, true);
>+        return addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp);
>+    }

On OOM, this would have the effect of removing a property, even a
non-configurable one (being changed from writeable to non-writeable, which
Object.defineProperty at least can do).

The same could happen in the final case.

> bool
>-JSObject::removeProperty(JSContext *cx, jsid id)
>+JSObject::removeShape(JSContext *cx, bool hadSlot, Shape *shape, Shape **spp)

hadSlot is only used for an assertion, and it could be recalculated there.

In JSObject::removeProperty:
>+    /* First, if shape is unshared and not cleared, free its slot number. */

The word "First" in this comment was meant to go with the word "Next" in the
following comment, but now they have been factored into separate methods.

What does "cleared" mean here?

>+    bool hadSlot = !shape->isAlias() && containsSlot(shape->slot);
>+    if (hadSlot) {

How is containsSlot(shape->slot) different from shape->hasSlot() here?
(In reply to comment #10)
> In jsscope.cpp, JSObject::putProperty:
> >+            setOwnShape(shape->shape);
> 
> Doesn't this conflict with what it says in checkShapeConsistency?
> 
>     if (hasOwnShape())
>         JS_ASSERT(objShape != lastProp->shape);
>     else
>         JS_ASSERT(objShape == lastProp->shape);

Yes, indeed -- no need for OWN_SHAPE here. Fixed.

> On OOM, this would have the effect of removing a property, ...
> 
> The same could happen in the final case.

The old jsscope.cpp code was careful to restore on failed overwrite, at most reordering for-in enumeration order. Fixed, and no more for-in enumeration order change on OOM.

> >+JSObject::removeShape(JSContext *cx, bool hadSlot, Shape *shape, Shape **spp)
> 
> hadSlot is only used for an assertion, and it could be recalculated there.

removeShape is gone in the next patch I'll attach.

> In JSObject::removeProperty:
> >+    /* First, if shape is unshared and not cleared, free its slot number. */
. . .
> What does "cleared" mean here?

This dates from when JS_ClearScope could zap slots still mapped by properties in use (js_NativeGet calling a getter, e.g., or a setter including a watchpoint setter-wrapper). That should not be possible any longer thanks to the runtime's propertyRemovals generation number. So I changed all containsSlot(shape->slot) calls to shape->hasSlot().

Thanks for the comments, very helpful. On a plane, will test and attach a v2 patch after test victory.

/be
Attached patch proposed fix, v2 (obsolete) — Splinter Review
Attachment #477347 - Attachment is obsolete: true
Attachment #477522 - Flags: review?(jorendorff)
Attachment #477347 - Flags: review?(jorendorff)
Plane lacks power for everyone, I ran my batt down without noticing, so pushed to try and attached here to optimize. Back later (or sooner if power returns).

/be
Attachment #477522 - Attachment is obsolete: true
Attachment #477530 - Flags: review?(jorendorff)
Attachment #477522 - Flags: review?(jorendorff)
Comment on attachment 477530 [details] [diff] [review]
proposed fix, v2 (comment fixes and tweaks)

I can't find anything wrong with this one.
Attachment #477530 - Flags: review?(jorendorff) → review+
Attachment #477530 - Attachment is obsolete: true
Attachment #477970 - Flags: review?(jorendorff)
Also, "cannot" had 4 hits in js.msg to many more for "can't", so I normalized. Also^2, no messages save two new ones put single quotes around decompiled property expressions, which just adds noise and potential confusion, so I got rid of those.

/be
Attachment #477970 - Attachment is obsolete: true
Attachment #477990 - Flags: review?(jorendorff)
Attachment #477970 - Flags: review?(jorendorff)
Comment on attachment 477990 [details] [diff] [review]
consolidate and improve error reporting, with test mods to match

The CANT_APPEND message starts with a capital letter, unlike all the others. Fix that while you're in js.msg?

Please keep the quotes in JSMSG_CANT_REDEFINE_PROP. At least in some cases (maybe all), the argument isn't a property expression but just an id:

  js> var obj = Object.create(null, {name: {value: 'k'}});
  js> Object.defineProperty(obj, "name", {value: 'j'});
  typein:4: TypeError: can't redefine non-configurable property 'name'

Without quotes this would be really confusing.

However removing the quotes in JSMSG_CANT_DELETE is the right thing.

Other than that, this looks good. "Cannot" is computerese. Good riddance.
Attachment #477990 - Flags: review?(jorendorff) → review+
http://hg.mozilla.org/tracemonkey/rev/7ef107ab081e

/be
Whiteboard: [ETA: 9/21][sg:critical] → [ETA: 9/21][sg:critical] fixed-in-tracemonkey
http://hg.mozilla.org/mozilla-central/rev/7ef107ab081e
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
Depends on: 600137
Depends on: 605015
Depends on: 610088
Depends on: 619529
Depends on: 624417
Depends on: 643847
Depends on: 643839
Keywords: crash
Group: core-security
You need to log in before you can comment on or make changes to this bug.