Closed Bug 96725 Opened 23 years ago Closed 23 years ago

calling QI on XBL objects causes crash

Categories

(Core :: XBL, defect)

x86
Windows 2000
defect
Not set
critical

Tracking

()

RESOLVED FIXED

People

(Reporter: alex, Assigned: jband_mozilla)

Details

(Keywords: crash)

Attachments

(2 files)

QI'ing an xbl object from js and then calling a method on it leads to a stack 
overflow. The xpc wrapper seems to get messed up somewhat. This is the stack 
trace for the attached testcase:

[...]
XPCWrappedNative::CallMethod(XPCCallContext & {...}, XPCWrappedNative::CallMode 
CALL_METHOD) line 1884 + 42 bytes
XPC_WN_CallMethod(JSContext * 0x01248568, JSObject * 0x0117ad68, unsigned int 
0, long * 0x023f85c4, long * 0x00033adc) line 1252 + 14 bytes
js_Invoke(JSContext * 0x01248568, unsigned int 0, unsigned int 2) line 807 + 23 
bytes
nsXPCWrappedJSClass::CallMethod(nsXPCWrappedJSClass * const 0x02439dc8, 
nsXPCWrappedJS * 0x011990e0, unsigned short 4, const nsXPTMethodInfo * 
0x0247b334, nsXPTCMiniVariant * 0x0003406c) line 1023 + 21 bytes
nsXPCWrappedJS::CallMethod(nsXPCWrappedJS * const 0x011990e0, unsigned short 4, 
const nsXPTMethodInfo * 0x0247b334, nsXPTCMiniVariant * 0x0003406c) line 427
PrepareAndDispatch(nsXPTCStubBase * 0x011990e0, unsigned int 4, unsigned int * 
0x0003411c, unsigned int * 0x0003410c) line 100 + 31 bytes
SharedStub() line 124
XPTC_InvokeByIndex(nsISupports * 0x011990e0, unsigned int 4, unsigned int 0, 
nsXPTCVariant * 0x00034298) line 139
XPCWrappedNative::CallMethod(XPCCallContext & {...}, XPCWrappedNative::CallMode 
CALL_METHOD) line 1884 + 42 bytes
XPC_WN_CallMethod(JSContext * 0x01248568, JSObject * 0x0117ad68, unsigned int 
0, long * 0x023f85bc, long * 0x000344d0) line 1252 + 14 bytes
js_Invoke(JSContext * 0x01248568, unsigned int 0, unsigned int 2) line 807 + 23 
bytes
nsXPCWrappedJSClass::CallMethod(nsXPCWrappedJSClass * const 0x02439dc8, 
nsXPCWrappedJS * 0x011990e0, unsigned short 4, const nsXPTMethodInfo * 
0x0247b334, nsXPTCMiniVariant * 0x00034a60) line 1023 + 21 bytes
nsXPCWrappedJS::CallMethod(nsXPCWrappedJS * const 0x011990e0, unsigned short 4, 
const nsXPTMethodInfo * 0x0247b334, nsXPTCMiniVariant * 0x00034a60) line 427
PrepareAndDispatch(nsXPTCStubBase * 0x011990e0, unsigned int 4, unsigned int * 
0x00034b10, unsigned int * 0x00034b00) line 100 + 31 bytes
SharedStub() line 124
XPTC_InvokeByIndex(nsISupports * 0x011990e0, unsigned int 4, unsigned int 0, 
nsXPTCVariant * 0x00034c8c) line 139
XPCWrappedNative::CallMethod(XPCCallContext & {...}, XPCWrappedNative::CallMode 
CALL_METHOD) line 1884 + 42 bytes
XPC_WN_CallMethod(JSContext * 0x01248568, JSObject * 0x0117ad68, unsigned int 
0, long * 0x023f85b4, long * 0x00034ec4) line 1252 + 14 bytes
[...]
Argh!  People everywhere are trying to use this feature! :)

jband, here's another fun one.
Status: NEW → ASSIGNED
Keywords: crash
OK. This can be fixed from the xpconnect side.

Oh boy, ascii art time!


 DOM Node             JSObject of WrappedNativeProto
    ^                        ^ 
    |                        |              <- JSObject proto chain
    |                  XBL proto JSObject
    |                        ^
    |                        | 
  WrappedNative <--- JSObject of WrappedNative  <- JS callers see this object
                        ^
                        |
                     WrappedJS built by XBL and QI aggregated to DOM node
                            

In the normal xpconnect case the wrappedNative methods are found on the 
(potentially) shared WrappedNativeProto's JSObject. This object is in the 
JS proto chain of the WrappedNative's own JSObject. This allows for reasonable 
factoring and also allows for shadowing those methods by inserting a new 
JSObject into the JS proto chain (as XBL does).

The WrappedNativeProto represents all the interfaces that all the objects of a 
given 'class' claim to implement. The 'class' is defined as those object that 
return the same nsIClassInfo object when asked. XPConnect gets the list of 
expected interface iids from the nsIClassInfo and builds a WrappedNativeProto to 
represent those interfaces. If you QI a wrapper for an iid that is a member of 
that interface set, then xpconnect will verify that the actual native object 
actually does implement that interface by calling QueryInterface on that object. 
But nothing really special happens.

However, if JS code QIs the wrapper for an interface that is not a member of 
that shared set, then xpconnect will QI the underlying native and if that 
succeeds then xpconnect will build a mutated interface set that is based on the 
set of its WrappedNativeProto, but adds the additional interface. This mutated 
set will be associated with that particular wrapper instance. XPConnect will 
*not* mutate the *shared* set simply because one instance was QI'd for an 
interface.

So, with a mutated set on the instance any method lookup for an interface that 
is not also in the shared set will have to return a property on the instance's 
JSObject - and not on the shared JSObject. And *that* method would then shadow 
any method in the xbl proto object.

The thing that goes wrong in this test case is caused by the fact that the QI 
must return the wrappedJS that was created by XBL and aggregated to the target 
DOM node. Nothing bad happens at that point. But when the method is actually 
called the flow goes from the WrappedNative to the WrappedJS which then does a 
lookup for the method on the same target JSObject and calls that method... This 
recurs until the stack overflows.

The fix is to detect at QI time that the interface pointer that the object 
wrapped by the wrappedNative (the DOM node in this case) returns in the QI call 
is in fact a wrappedJS and that *its* JSObject is the very same JSObject that 
belongs to our WrappedNative. When xpconnect sees this case, instead of mutating 
the wrapper instance's interface set, xpconnect just says "sure this wrapper 
implements that interface". Then later, when JS code tries to make a call and 
the engine does a property lookup for the method in question, xpconnect will 
*not* reflect a method on the target JSObject (because that method is not a part 
of its interface set). Instead, the property lookup will proceed up the JS 
proto chain to the XBL prototype as the XBL system expects it. This then gives 
the same behavior that would happen if QI had not been called from JS.

I have a patch for review. I'll attach it (and take this bug).
Assignee: hyatt → jband
Status: ASSIGNED → NEW
Attached patch proposed fixSplinter Review
My fix could *certainly* use additional testing to make sure it does not trip up 
any other legitimate double-wrapping sorts of cases.
r/sr=hyatt.  Nice ASCII art. :)
psuedo diff:
        if(!mTearOff || mTearOff->GetInterface() != mInterface)
+       {
+            mTearOff = 0;
             return NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED;
+       }

I'm wondering if this would be appropriate, since this would be the state in a
normal failure of CanCallNow.
That would be: mTearOff = nsnull;
Sure. I can make that change. I don't think it much matters in the current code 
since if CanCallNow fails no one is going to do much with mTearOff. But, I've 
added that change to my tree. Anything else?
yes, nsnull.

r=dbradley
s/implmenting/implementing/ in the comment.

I think we can take this.  It makes things better by not crashing, and I don't
see it making things worse in as severe a way as causing a crash.

/be
fix checked in
Status: NEW → RESOLVED
Closed: 23 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: