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

RESOLVED WORKSFORME

Status

()

RESOLVED WORKSFORME
12 years ago
12 years ago

People

(Reporter: WeirdAl, Unassigned)

Tracking

({testcase})

Trunk
x86
Windows XP
testcase
Points:
---

Firefox Tracking Flags

(Not tracked)

Details

Attachments

(2 attachments)

(Reporter)

Description

12 years ago
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.
(Reporter)

Comment 1

12 years ago
Created attachment 248141 [details]
testcase, part 1 of 2 (JS-based component, nsIScriptError, nsIClassInfo)
(Reporter)

Comment 2

12 years ago
Created attachment 248142 [details]
testcase, part 2 of 2 (xpcshell script)

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

Comment 3

12 years ago
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
Last Resolved: 12 years ago
Resolution: --- → WORKSFORME
You need to log in before you can comment on or make changes to this bug.