Closed Bug 363332 Opened 18 years ago Closed 18 years ago

XPConnect's QI can get confused about which object to return

Categories

(Core :: XPConnect, defect)

x86
Windows XP
defect
Not set
normal

Tracking

()

RESOLVED WORKSFORME

People

(Reporter: WeirdAl, Unassigned)

Details

(Keywords: testcase)

Attachments

(2 files)

While working on bug 363290, I ran into a situation where a JS-based component would claim to implement two different interfaces.  The problem arose in that both interfaces (in that case nsIEditor, nsIClassInfo) have an identically named property (flags), which means different things for each interface.

I figured the best solution would be to use QueryInterface and to write the JS component so that its QI method would return a different object (a tearoff, essentially) for nsIClassInfo.  If the tearoff received a QI request for something other than nsIClassInfo, it would refer back to the original object.

As it turns out, I didn't need to do the QI, but I did wonder if it would work.  I wrote a component (nsIScriptError, nsIClassInfo) in JS, and a xpcshell testcase that would try to QI to the nsIClassInfo tearoff.  The testcase failed.

Two-part xpcshell testcase coming up.
Steps to reproduce:
(1) Save the first part of the testcase into dist/bin/components/testFlagsComponent.js
(2) Save the second part of the testcase as a local file.
(3) xpcshell -w -s
(4) Load the second part of the testcase.
(5) js>runTest();

Expected results: (note CI_Tearoff.flags)

Creating ClassInfo tearoff
Returning CI tearoff
isCI: false
typeof testError.flags: number
testError.flags: 1
isCI: true
typeof testError.flags: number
testError.flags: 1
CI_Tearoff: Test script error
CI_Tearoff.flags: 0
isCI: true
typeof testError.flags: number
testError.flags: 1

Actual results:
Creating ClassInfo tearoff
Returning CI tearoff
isCI: false
typeof testError.flags: number
testError.flags: 1
isCI: true
typeof testError.flags: number
testError.flags: 1
CI_Tearoff: Test script error
CI_Tearoff.flags: 1
isCI: true
typeof testError.flags: number
testError.flags: 1
oh, right, so, the reason i stopped caring is that you can use interface flattening's *other* feature to stop caring:

js> SIP=Components.Constructor("@mozilla.org/supports-interface-pointer;1",Components.interfaces.nsISupportsInterfacePointer)
[object nsXPCConstructor @ 0x2e88b60 (native @ 0x2e891d8)]
js> sip=new SIP
[interface pointer]
js> function A(){}
js> A.prototype.QueryInterface=function QueryInterface(iid) {
if (iid.equals(Components.interfaces.nsIObserver) || iid.equals(Components.interfaces.nsISupports)) return this;
if (iid.equals(Components.interfaces.nsIConsoleListener)) {
if (!this.c) this.c=new C(this);
return this.c;
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
function QueryInterface(iid) {
    if (iid.equals(Components.interfaces.nsIObserver) ||
        iid.equals(Components.interfaces.nsISupports)) {
        return this;
    }
    if (iid.equals(Components.interfaces.nsIConsoleListener)) {
        if (!this.c) {
            this.c = new C(this);
        }
        return this.c;
    }
    throw Components.results.NS_ERROR_NO_INTERFACE;
}
js> function C(root) {
this.root = root;
}
js> C.prototype.QueryInterface=function QueryInterface2(iid) {
if (iid.equals(Components.interfaces.nsIConsoleListener)) return this;
return this.root.QueryInterface.apply(this.root, arguments);
}
function QueryInterface2(iid) {
    if (iid.equals(Components.interfaces.nsIConsoleListener)) {
        return this;
    }
    return this.root.QueryInterface.apply(this.root, arguments);
}
js> C.prototype.observe= function Listener_observe() {
print("listener");
}
function Listener_observe() {
    print("listener");
}
js> A.prototype.observe= function Observer_observe() {
print("observer");
}
function Observer_observe() {
    print("observer");
}
js> sip.data=new A
[object Object]
js> sip.data.QueryInterface(Components.interfaces.nsIObserver)
[xpconnect wrapped nsIObserver @ 0x2e83238 (native @ 0x2f5ad38)]
js> sip.data
[xpconnect wrapped nsIObserver @ 0x2e83238 (native @ 0x2f5ad38)]
js> sip.data.observe(null, "", "")
observer
js> sip.data.QueryInterface(Components.interfaces.nsIConsoleListener)
[xpconnect wrapped (nsISupports, nsIObserver, nsIConsoleListener) @ 0x2e83238 (n
ative @ 0x2f5ad38)]
js> sip.data.observe(null)
uncaught exception: [Exception... "Not enough arguments [nsIObserver.observe]"
nsresult: "0x80570001 (NS_ERROR_XPC_NOT_ENOUGH_ARGS)"  location: "JS frame :: ty
pein :: <TOP_LEVEL> :: line 72"  data: no]
js> sip.data.nsIConsoleListener.observe(null)
listener
js> sip.data.nsIObserver.observe(null, "", "")
observer

iow, you don't use explicit QI which only contributes to flattening, you have to use the tearoff system {object.interface):

js> sip.data.nsIConsoleListener
[xpconnect wrapped nsIConsoleListener @ 0x2e83238 (native @ 0x2f5ad38)]
js> sip.data.nsIObserver
[xpconnect wrapped nsIObserver @ 0x2e83238 (native @ 0x2f5ad38)]

It's not confused, it's working as designed.
Status: NEW → RESOLVED
Closed: 18 years ago
Resolution: --- → WORKSFORME
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: