The crash occurs because js::ScopeCoordinateName returns nullptr. The
only way it can do that is if |id| is zero at
http://hg.mozilla.org/releases/mozilla-
release/file/7665b8d4d51f/js/src/vm/ScopeObject.cpp#l96.

Unfortunately, it works fine in a debug build.

I added a "MOZ_RELEASE_ASSERT(!JSID_IS_ZERO(id))" after
http://hg.mozilla.org/releases/mozilla-
release/file/7665b8d4d51f/js/src/vm/ScopeObject.cpp#l89 so I could crash
Firefox when this happens and catch it in gdb.

At the point we hit this assert in gdb, |sc| has already been optimised
out. However, by examining the incoming bytecode:

(gdb) p *pc
$2 = 136 '\210'
(gdb) x pc+1
0xb68b2e2:      0x00000000

... we see that this is a JSOP_GETALIASEDVAR instruction and the |slot|
argument is 0x000000. After adding some printf's to
http://hg.mozilla.org/releases/mozilla-
release/file/7665b8d4d51f/js/src/vm/ScopeObject.cpp#l72, I verified that
no properties with slot=0 ever exist.

I added further asserts in
js::frontend::BytecodeEmitter::emitScopeCoordOp() and verified that this
instruction sequence was not being emitted here. After creating a small
helper function to scan the bytecode at various stages in
BytecodeEmitter, I managed to narrow the point at which this instruction
sequence is emitted to js::frontend::BytecodeEmitter::emitNameOp().

Here, at http://hg.mozilla.org/releases/mozilla-
release/file/7665b8d4d51f/js/src/frontend/BytecodeEmitter.cpp#l2272:

        if (!pn->pn_cookie.isFree()) {
             MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ATOM);
             if (!emitVarOp(pn, op))
                 return false;
         } else {
             if (!emitAtomOp(pn, op))
                 return false;
         }


... despite |op| being equal to JSOP_GETALIASEDVAR, this code calls 
emitAtomOp() because the "if (!pn->pn_cookie.isFree()) {" check evaluates 
false. This is wrong - it should be calling emitVarOp().

Changing the MOZ_ASSERT at the top of
js::frontend::BytecodeEmitter::emitAtomOp() to a MOZ_RELEASE_ASSERT
verifies that we hit this. In gdb:

(gdb) f 1
#1  0xf58099bf in emitAtomOp (op=<optimised out>, pn=0x825bc60, 
this=0xffffa408) at 
/home/chr1s/src/firefox/build-area/firefox-39.0+build5/js/src/frontend/BytecodeEmitter.cpp:1071
1071        return emitAtomOp(pn->pn_atom, op);
(gdb) p pn->pn_u.name.cookie
$2 = {level_ = 255, slot_ = 4, static FREE_LEVEL = 255}

So, the correct branch is taken. Which means that |pn| is invalid.

In fact, it goes wrong in
js::frontend::BytecodeEmitter::tryConvertFreeName(), just here at
http://hg.mozilla.org/releases/mozilla-
release/file/7665b8d4d51f/js/src/frontend/BytecodeEmitter.cpp#l1567:

                    JSOp op;
                    switch (pn->getOp()) {
                      case JSOP_GETNAME: op = JSOP_GETALIASEDVAR; break;
                      case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
                      default: return false;
                    }

                    pn->setOp(op);
                    JS_ALWAYS_TRUE(pn->pn_cookie.set(parser->tokenStream, hops, 
slot));
                    return true;

If I add a "MOZ_RELEASE_ASSERT(!pn->pn_cookie.isFree());" just before
the "return true", then we hit this in our broken build. The line above
calls js::frontend::UpvarCookie::set(), which is inlined from
http://hg.mozilla.org/releases/mozilla-
release/file/7665b8d4d51f/js/src/frontend/ParseNode.h#l51:

    bool set(TokenStream& ts, unsigned newLevel, uint32_t newSlot) {
        if (newLevel >= FREE_LEVEL)
            return ts.reportError(JSMSG_TOO_DEEP, js_function_str);

        if (newSlot >= SCOPECOORD_SLOT_LIMIT)
            return ts.reportError(JSMSG_TOO_MANY_LOCALS);

        level_ = newLevel;
        slot_ = newSlot;
        return true;
    }

... which seems simple enough. Note that |level_| and |slot_| both fit
in to 32-bits, with |slot_| taking the least-significant byte and
|level_| taking the other 3 bytes.

If I disassemble this bit of code in
js::frontend::BytecodeEmitter::tryConvertFreeName() in a *good" build,
starting at http://hg.mozilla.org/releases/mozilla-
release/file/7665b8d4d51f/js/src/frontend/BytecodeEmitter.cpp#l1572:

The starting context looks like this:
%eax = |op|
0x1c(%esp) = |hops|
0x24(%esp) = |slot|
0x74(%esp) = |pn|

   0xf4eb978b <+1739>:  mov    0xa0(%ebp),%ecx
   0xf4eb9791 <+1745>:  mov    0x74(%esp),%edi // %edi now points to |pn|
   0xf4eb9795 <+1749>:  add    $0x18,%ecx
   0xf4eb9798 <+1752>:  cmpl   $0xfe,0x1c(%esp) // Compare |hops| with 254 
(FREE_LEVEL - 1)
   0xf4eb97a0 <+1760>:  mov    %al,0x2(%edi) // Calls pn->SetOp(op)
   0xf4eb97a3 <+1763>:  mov    0x24(%esp),%eax // %eax now contains |slot|
// Jump if |hops| > 254
   0xf4eb97a7 <+1767>:  ja     0xf4eb97e9 
<js::frontend::BytecodeEmitter::tryConvertFreeName(js::frontend::ParseNode*)+1833>
   0xf4eb97a9 <+1769>:  cmp    $0xffffff,%eax // Compare |slot| with 0xffffff
// Jump if |slot| > 0xffffff
   0xf4eb97ae <+1774>:  ja     0xf4eb980b 
<js::frontend::BytecodeEmitter::tryConvertFreeName(js::frontend::ParseNode*)+1867>
   0xf4eb97b0 <+1776>:  movzbl 0x1c(%esp),%ecx // %ecx now contains |hops|
   0xf4eb97b5 <+1781>:  mov    0x74(%esp),%edi // %edi now points to |pn|
   0xf4eb97b9 <+1785>:  shl    $0x8,%eax // Left shift new |slot| value by 
8-bits
   0xf4eb97bc <+1788>:  or     %ecx,%eax // %eax now contains the bitwise-OR of 
|hops| and |slot|
   0xf4eb97be <+1790>:  mov    %eax,0x20(%edi) // Save the new values to 
|level_| and |slot_| in pn->pn_u.name.cookie

Now, looking at a broken build:

The starting context looks like this:
%eax = |op|
0x38(%esp) = |hops|
0x44(%esp) = |slot|
0x94(%esp) = |pn|

   0xf5803ba1 <+1729>:  mov    0xa0(%ebp),%edx
   0xf5803ba7 <+1735>:  mov    0x94(%esp),%esi // %esi now points to |pn|
   0xf5803bae <+1742>:  add    $0x18,%edx
   0xf5803bb1 <+1745>:  cmpl   $0xfe,0x38(%esp) // Compare |hops| with 254 
(FREE_LEVEL - 1)
   0xf5803bb9 <+1753>:  mov    %al,0x2(%esi) // Calls pn->SetOp(op)
   0xf5803bbc <+1756>:  mov    0x44(%esp),%eax // %eax now contains |slot|
// Jump if |hops| > 254
   0xf5803bc0 <+1760>:  ja     0xf5803c22 
<js::frontend::BytecodeEmitter::tryConvertFreeName(js::frontend::ParseNode*)+1858>
   0xf5803bc2 <+1762>:  cmp    $0xffffff,%eax // Compare |slot| with 0xffffff
// Jump if |slot| > 0xffffff
   0xf5803bc7 <+1767>:  ja     0xf5803c09 
<js::frontend::BytecodeEmitter::tryConvertFreeName(js::frontend::ParseNode*)+1833>
   0xf5803bc9 <+1769>:  mov    0x94(%esp),%esi // %esi now points to |pn|
   0xf5803bd0 <+1776>:  shl    $0x8,%eax // Left shift new |slot| value by 
8-bits
   0xf5803bd3 <+1779>:  mov    %eax,%edx // ...and copy it in to %edx

// Now, this is where it goes wrong. The next instruction loads the current 
|level_| from pn->pn_u.name.cookie (which is 255) in to %eax
   0xf5803bd5 <+1781>:  movzbl 0x20(%esi),%eax
   0xf5803bd9 <+1785>:  or     %edx,%eax // %eax now contains the bitwise-OR of 
the old |level_| and new |slot| value
   0xf5803bdb <+1787>:  mov    %eax,0x20(%esi) // Save the new |slot_|

So, this appears to be a miscompile in
js::frontend::BytecodeEmitter::tryConvertFreeName - the inlined call to
js::frontend::UpvarCookie::set() correctly sets |slot_| but always
preserves the previous value of |level_|, causing the call to
js::frontend::UpvarCookie::isFree() to return the wrong value in
BytecodeEmitter::emitNameOp().

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/1471949

Title:
  Firefox 39 crashes on startup or within a few seconds on Precise/x86

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/firefox/+bug/1471949/+subscriptions

-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to