Closed Bug 418128 Opened 14 years ago Closed 13 years ago

Yet another GC hazard with ++/-- in js_Interpret

Categories

(Core :: JavaScript Engine, defect, P2)

defect

Tracking

()

VERIFIED FIXED
mozilla1.9

People

(Reporter: igor, Assigned: igor)

Details

(Keywords: verified1.8.1.15, Whiteboard: [sg:critical?] trunk fixed in bug 418641)

Attachments

(3 files, 7 obsolete files)

The current implementation of ++ and -- operators in js_Interpret to increase/decrease indexed property of an object does not root the id of the property. This leads to a GC hazard when code tries to save the result back into object as the following example demonstrates:

~/m/ff/mozilla/js/src $ cat ~/m/y.js
var obj = {};
var id = { toString: function() { return ""+Math.pow(2, 0.1); } }
obj[id] = { valueOf: unrooter };
print(obj[id]++);
gc();
print(uneval(obj));

function unrooter()
{
    delete obj[id];
    gc();
    return 10;
}
~/m/ff/mozilla/js/src $ ~/m/ff/mozilla/js/src/Linux_All_OPT.OBJ/js ~/m/y.js
before 16384, after 16384, break 09ffc000
10
before 16384, after 16384, break 09ffc000
segmentation fault
There is another hazard in the ++/-- implementation. The code in js_Interpret that stores the result contains:

            ok = OBJ_SET_PROPERTY(cx, obj, id, &rval);
...
            PUSH_OPND(rtmp);

Here for pre "++" and "--" operations 

   rval == rtmp == incremenetd_or_decremented_value. 

When rval does not fit the int range, it will be a newborn double representing the result. So this code assumes that after the property setter returns, rval still will be alive GC thing that can be stored back to the stack. But this is not true in general. Consider array_length_setter from jsarray.c. It contains the following code:

    if (!ValueIsLength(cx, *vp, &newlen))
        return JS_FALSE;
    if (!js_GetLengthProperty(cx, obj, &oldlen))
        return JS_FALSE;
    if (oldlen > newlen) {
        if (oldlen - newlen < (1 << 24)) {
            do {
                --oldlen;
                if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
                    !DeleteArrayElement(cx, obj, oldlen)) {
                    return JS_FALSE;
                }
            } while (oldlen != newlen);
...
    }
    if (!IndexToValue(cx, newlen, vp))
        return JS_FALSE;
    STOBJ_SET_SLOT(obj, JSSLOT_ARRAY_LENGTH, *vp);
 
Here vp is &rval. When oldlen > newlen, the code can call operational callback. That in turn can call the full GC collecting the context of vp. 
In such situation when the setter returns, rtemp, the original value of rval, will refer to the collected value and the code will push it back into JS stack. I.e., if the operation callback runs the GC, then the following example will store the dead value into x:

var a = Array(1 << 30 + 1);
var x = --a.length;

Note that there is no hazard in the array code as vp is never read again after a potential GC call. It is precisely the ++/-- implementation that introduces the hazard when it accesses weakly rooted value after calling a setter.
Flags: blocking1.9?
Flags: blocking1.8.1.13?
Flags: blocking1.9? → blocking1.9+
Target Milestone: --- → mozilla1.9beta4
Attached patch v1 (obsolete) — Splinter Review
Untested patch. To fix the first hazard, the patch always roots the result of FETCH_ELEMT_ID. To fix the second hazard the patch makes sure preincrements also includes JOF_TMPSLOT.

Besides the fix the patch also moves non-int increment and decrement to separated functions to share and simplify the code for this infrequently taken path. This shrinks the size of stripped jsinterp.o on Linux by 2K.

The patch is not tested.
Blocks: 418641
I filed bug 418641 as a cover bug for this.
No longer blocks: 418641
Flags: wanted1.8.1.x+
Whiteboard: [sg:critical?]
The new patch from bug 418641 comment 4 fixes this. It never passes pointers to unrooted values to getters/setters. This may seems excessive since the rest of code still passes &rval/&rtmp to the native functions. But given the history of hazards, I prefer not to rely on weak roots at all. 

Plus in fact &rval/&rtmp are expensive code wise since they force the compiler to assume that rval/rtmp can be modified by arbitrary native functions. 

On the other hand &sp is not exposed so sp lives most of the time in registers. Thus passing &rval and &sp[-1] and using rval or sp[-1] generates the same amount of code. In the former case the compiler will use native_stack_register[offset_for_rval] and in the latter case  register_storing_sp[-1].
Patch is kinda big and scary... will this land on the trunk anytime soon so we can check for regressions?
(In reply to comment #5)
> Patch is kinda big and scary... will this land on the trunk anytime soon so we
> can check for regressions?

Note that the branch would require rather different patch as there rooting id is not trivial. So I will go there for a minimal patch that uses JS_KEEP_ATOMS for rooting the id and will apply JSOF_TEMPSLOT for index and property operations. But I do plan to replace the main macro implementing ++/-- for non-integer cases by a function to follow the trunk.

Moving bugs that aren't beta 4 blockers to target final release.
Target Milestone: mozilla1.9beta4 → mozilla1.9
Flags: tracking1.9+ → blocking1.9+
Priority: -- → P2
Since the trunk patch hasn't been reviewed or landed on trunk we'll likely have to wait for next branch release to catch this one. Minusing for '13
Flags: blocking1.8.1.14?
Flags: blocking1.8.1.13?
Flags: blocking1.8.1.13-
This is fixed on the trunk since landing bug 418641.
Status: NEW → RESOLVED
Closed: 13 years ago
Resolution: --- → FIXED
Attached file js1_5/GC/regress-418128.js (obsolete) —
Flags: in-testsuite+
Flags: in-litmus-
v 1.9.0
Status: RESOLVED → VERIFIED
(In reply to comment #9)
> This is fixed on the trunk since landing bug 418641.

Does that mean the patch in this bug is obsolete and we should look at bug 418641 for the 1.8 branch, or that this patch was checked in when that one was?
Whiteboard: [sg:critical?] → [sg:critical?] fixed by bug 418641 ?
(In reply to comment #12)
> (In reply to comment #9)
> > This is fixed on the trunk since landing bug 418641.
> 
> Does that mean the patch in this bug is obsolete and we should look at bug
> 418641 for the 1.8 branch, or that this patch was checked in when that one was?

The branch requires a new separated patch. The patch in this bug or in the bug  418641 can serve only as a guideline.
Attachment #304256 - Attachment is obsolete: true
Flags: blocking1.8.1.15? → blocking1.8.1.15+
Whiteboard: [sg:critical?] fixed by bug 418641 ? → [sg:critical?] fixed in bug 418641
Attached patch fix for 1.8 branch (obsolete) — Splinter Review
This is what I am going to test on 2008-06-04 late evening PST. Still I am asking for an early review to make sure that I have not missed anything in the patch.

To solve the problem with unrooted id the patch uses JS_KEEP_ATOMS. For the problem of the setter unrooting the pre-increment result the patch makes sure that an extra temporary slot is allocated both for post- and pre-increment.
Attachment #323761 - Flags: review?(brendan)
Comment on attachment 323761 [details] [diff] [review]
fix for 1.8 branch

Looks good. Assuming it all tests well too. r=me.

/be
Attachment #323761 - Flags: review?(brendan)
Attachment #323761 - Flags: review+
Attachment #323761 - Flags: approval1.8.1.15?
Comment on attachment 323761 [details] [diff] [review]
fix for 1.8 branch

Approved for 1.8.1.15, a=dveditz for release-drivers
Attachment #323761 - Flags: approval1.8.1.15? → approval1.8.1.15+
Comment on attachment 323761 [details] [diff] [review]
fix for 1.8 branch

The patch regressed the js shell test suite.
Attachment #323761 - Attachment is obsolete: true
Attachment #323761 - Flags: approval1.8.1.15+
Attached patch fix for 1.8 branch (obsolete) — Splinter Review
Now the patch passes shell tests without regressions: in the previous one I "optimized" ++/-- for var and args too much.

To Bob Clary: is there a slow test list for 1.8.1 branch? When I run the tests on sm-valgrind01 with -L slow-n.js it takes 3 hours or so.
Attachment #312529 - Attachment is obsolete: true
Attachment #324028 - Flags: review?(brendan)
The previous attachment has a wrong patch
Attachment #324028 - Attachment is obsolete: true
Attachment #324029 - Flags: review?(brendan)
Attachment #324028 - Flags: review?(brendan)
Attachment #324029 - Attachment is patch: true
Attachment #324029 - Attachment mime type: application/octet-stream → text/plain
(In reply to comment #18)

> 
> To Bob Clary: is there a slow test list for 1.8.1 branch? When I run the tests
> on sm-valgrind01 with -L slow-n.js it takes 3 hours or so.

No, but I can create branch specific versions. Are you using jsDriver with the default timeout? It is rather long at 3600 seconds. -T 480 or -T 120 will cut it down to more manageable period.
Whiteboard: [sg:critical?] fixed in bug 418641 → [sg:critical?] need branch r=brendan; trunk fixed in bug 418641
Comment on attachment 324029 [details] [diff] [review]
fix for 1.8 branch v2 for real

Sorry I missed that!

/be
Attachment #324029 - Flags: review?(brendan)
Attachment #324029 - Flags: review+
Attachment #324029 - Flags: approval1.8.1.15?
Whiteboard: [sg:critical?] need branch r=brendan; trunk fixed in bug 418641 → [sg:critical?] trunk fixed in bug 418641
Comment on attachment 324029 [details] [diff] [review]
fix for 1.8 branch v2 for real

Approved for 1.8.1.15, a=dveditz for release-drivers
Attachment #324029 - Flags: approval1.8.1.15? → approval1.8.1.15+
I checked in the patch from the comment 19 to the MOZILLA_1_8_BRANCH:

Checking in jsemit.c;
/cvsroot/mozilla/js/src/jsemit.c,v  <--  jsemit.c
new revision: 3.128.2.77; previous revision: 3.128.2.76
done
Checking in jsinterp.c;
/cvsroot/mozilla/js/src/jsinterp.c,v  <--  jsinterp.c
new revision: 3.181.2.101; previous revision: 3.181.2.100
done
Keywords: fixed1.8.1.15
verified fixed 1.8.1.15 linux/mac/win
Attached patch 1.8.0 attempt (obsolete) — Splinter Review
Comment on attachment 325247 [details] [diff] [review]
1.8.0 attempt

the other option would have been to pull in bug 352272 - however, thats 1.7 from what i can tell; thus this backport.

Anyway, it fixes the crash for me too.
Attachment #325247 - Flags: review?(igor)
Attached patch 1.8 to 1.8.0 plaindiff (obsolete) — Splinter Review
Without patch:
js> before 7157, after 6409, break 0070e000
Segmentation fault
Flags: blocking1.8.0.15+
Comment on attachment 325247 [details] [diff] [review]
1.8.0 attempt

we found a glitch, will attach updated one
Attachment #325247 - Flags: review?(igor) → review-
Attached patch 1.8.0 attempt 2 (obsolete) — Splinter Review
2nd attempt
Attachment #325247 - Attachment is obsolete: true
Attachment #325248 - Attachment is obsolete: true
Attachment #325249 - Flags: review?(igor)
Comment on attachment 325249 [details] [diff] [review]
 1.8.0 attempt 2

>Index: jsemit.c

>         if (pn2->pn_type != TOK_NAME &&
>-            (js_CodeSpec[op].format & JOF_POST) &&
>+            (js_CodeSpec[op].format & (JOF_INC | JOF_DEC)) &&
>             (uintN)++cg->stackDepth > cg->maxStackDepth) {
>             cg->maxStackDepth = cg->stackDepth;

...
> 
>-        if (pn2->pn_type != TOK_NAME && (js_CodeSpec[op].format & JOF_POST))
>+        if (pn2->pn_type != TOK_NAME && (js_CodeSpec[op].format & (JOF_INC | JOF_DEC)))
>             --cg->stackDepth;

Right, on 1.8 there was only one place to patch as there the first "if" affected only cg->maxStackDepth without touching cg->stackDepth. On 1.8.0 both places has to be patched.

>Index: jsinterp.c
>             /* The operand must contain a number. */
>+            /* Preload for use in the if/else immediately below. */
>+            cs = &js_CodeSpec[op];
>+

On 1.8.0 branch cs is always initialized before interpreter's switch. There is no need to assign it again and these 3 added lines can be removed. r+ with this fixed.
Attachment #325249 - Flags: review?(igor)
Attachment #325249 - Attachment is obsolete: true
Attachment #325262 - Flags: review+
Group: security
/cvsroot/mozilla/js/tests/js1_5/GC/regress-418128.js,v  <--  regress-418128.js
initial revision: 1.1

changeset:   15651:33dac11fb9ed
You need to log in before you can comment on or make changes to this bug.