Closed Bug 1010756 Opened 11 years ago Closed 10 years ago

Better compile errors if you mix up nsCOMPtr and nsRefPtr

Categories

(Core :: XPCOM, enhancement)

enhancement
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla43
Tracking Status
firefox43 --- fixed

People

(Reporter: ayg, Assigned: ayg)

Details

Attachments

(1 file)

If you use nsCOMPtr for a class like nsRange or Selection or Text, or use nsRefPtr for a class like nsINode, you will get some sort of exciting compiler error that is unlikely to be informative. I recently ran into one when doing InsertElementAt in an nsTArray<nsCOMPtr<Text>> that took me a while to figure out, and I'm sure it would be hopeless for a novice Gecko hacker. Could we improve this? Is there a static_assert we could stick in nsCOMPtr/nsRefPtr constructors that would catch at least some of these cases and fail with a helpful error message? I have no idea why these are failures to begin with, so I wouldn't have any idea what to assert on.
Flags: needinfo?(ehsan)
What's the error that you're getting? I'm sure we can improve things but I don't know enough about the current problem. CCing Neil.
Flags: needinfo?(ehsan)
(In reply to Ehsan Akhgari from comment #1) > What's the error that you're getting? I'm sure we can improve things but I > don't know enough about the current problem. CCing Neil. GCC 4.8.2 spits out the following error: > In instantiation of 'nsCOMPtr<T>::nsCOMPtr(nsQueryInterface) [with T = nsBogusType]' > error: incomplete type 'nsIParentInterface::COMTypeInfo<nsBogusType, void>' used in nested name specifier This means that your nsCOMPtr<nsBogusType> should either be an nsCOMPtr<nsIParentInterface> or an nsRefPtr<nsBogusType>. Or, possibly, you need to complete the type by manually declaring an IID. (In reply to Aryeh Gregor) > I have no idea why these are > failures to begin with, so I wouldn't have any idea what to assert on. nsRefPtr<nsINode> isn't a compile error, although it should be a review error ;-) nsCOMPtr is designed to template over interfaces, that is, classes with a declared IID. In particular interfaces allow you to call QueryInterface to obtain a pointer to a different interface. (Debug builds try to double-check that the pointers actually point to the interface you think they do.) Prior to bug 514280 it used to be all too easy for a concrete class to inherit an IID from an interface however were you to pass in such an IID you would actually get an nsIParentInterface* pointer back rather than the nsBogusType* pointer your code was assuming.
> although it should be a review error No; WebIDL bindings use nsRefPtr for all refcounted types.
(In reply to Boris Zbarsky from comment #3) > > although it should be a review error > > No; WebIDL bindings use nsRefPtr for all refcounted types. Actually I know there are other exceptions to the rule, but I thought that my use of should was sufficiently weak to permit this.
Do you know of any way to write a static_assert that will catch at least some cases where nsCOMPtr<T> will fail to compile? Probably using type_traits?
Flags: needinfo?(neil)
(In reply to Aryeh Gregor from comment #5) > Do you know of any way to write a static_assert that will catch at least > some cases where nsCOMPtr<T> will fail to compile? Probably using > type_traits? Well, it's fairly easy to make it so that nsCOMPtr<T> will fail to compile in release builds with the same error that it already fails to compile in debug builds i.e. error: incomplete type 'I::COMTypeInfo<T, void>' used in nested name specifier. I've come up with a couple of approaches to make nsCOMPtr<T> fail to compile with the error: static assertion failed: nsCOMPtr may only be templated over interfaces template <class T> char (&TestForIID(T*))[sizeof(NS_GET_IID(T)) + 1]; char TestForIID(...); template <class T> class nsCOMPtr { static_assert(1 < sizeof(TestForIID(static_cast<T*>(nullptr)), "nsCOMPtr may only be templated over interfaces"); template <class T, class U = T> struct IsInterface { static const bool value = false; }; template <class T> struct IsInterface<T, typename T::template COMTypeInfo<T, void> > { static const bool value = true; }; template <class T> class nsCOMPtr { static_assert(IsInterface<T>::value, "nsCOMPtr may only be templated over interfaces"); Note that this second version strictly only checks for the COMTypeInfo inner class and not for the IID itself.
Flags: needinfo?(neil)
(In reply to neil@parkwaycc.co.uk from comment #6) > template <class T, class U = T> > struct IsInterface { > static const bool value = false; > }; > template <class T> > struct IsInterface<T, typename T::template COMTypeInfo<T, void> > { > static const bool value = true; > }; > template <class T> > class nsCOMPtr > { > static_assert(IsInterface<T>::value, > "nsCOMPtr may only be templated over interfaces"); I tried this, but got an error that U was being redeclared.
(In reply to neil@parkwaycc.co.uk from comment #6) > template <class T> > char (&TestForIID(T*))[sizeof(NS_GET_IID(T)) + 1]; > char TestForIID(...); > template <class T> > class nsCOMPtr > { > static_assert(1 < sizeof(TestForIID(static_cast<T*>(nullptr)), > "nsCOMPtr may only be templated over interfaces"); And this one fails for all types, including those with IIDs.
Flags: needinfo?(neil)
(In reply to Aryeh Gregor from comment #8) > (In reply to comment #6) > > template <class T> > > char (&TestForIID(T*))[sizeof(NS_GET_IID(T)) + 1]; > > char TestForIID(...); > > template <class T> > > class nsCOMPtr > > { > > static_assert(1 < sizeof(TestForIID(static_cast<T*>(nullptr)), > > "nsCOMPtr may only be templated over interfaces"); > > And this one fails for all types, including those with IIDs. I probably originally compiled with gcc, which may have been more lenient, but MSVC tripped over it in two places. I've tweaked it slightly and MSVC likes this version: template <class T> char (&TestForIID(T*))[sizeof(NS_GET_TEMPLATE_IID(T)) + 1]; char TestForIID(...); template <class T> class nsCOMPtr final { // MSVC seems to need this intermediate value for some reason. static const bool value = 1 < sizeof(TestForIID(static_cast<T*>(nullptr))); static_assert(value, "nsCOMPtr may only be templated over interfaces");
Flags: needinfo?(neil)
Flags: needinfo?(neil)
Did this compile successfully for you? On Clang 3.5, all classes seem to fail the static_assert (including, e.g., nsIContent).
Oh, I see what the problem is - the file I was testing on didn't forward-declare any of the interfaces it used. Let me investigate...
Third time lucky? (xpcom/glue now compiles with this version; if I remember, I'll check back to see whether my build fails.) template <class T> char (&TestForIID(T*))[sizeof(NS_GET_TEMPLATE_IID(T)) + 1]; template <class T> char TestForIID(...); template <class T> class nsCOMPtr final { ... ~nsCOMPtr() { static_assert(1 < sizeof(TestForIID<T>(nullptr)), "nsCOMPtr may only be templated over interfaces"); #ifndef NSCAP_FEATURE_USE_BASE NSCAP_LOG_RELEASE(this, mRawPtr); if (mRawPtr) { NSCAP_RELEASE(this, mRawPtr); } #endif }
Flags: needinfo?(neil)
Now it compiles, but doesn't serve the intended purpose, because if you try to do nsCOMPtr<nsRange> or whatever, it prints an error about ambiguous conversion to nsISupports* and never prints the static_assert. Presumably this is because it's in the destructor instead of the class proper.
Flags: needinfo?(neil)
Whoops, I'd managed to get parts of two different approaches there. Retrying my build with this (currently expecting it to fail in SmsChild.cpp which has an unused nsCOMPtr of a non-interface type): template <class T> char (&TestForIID(T*))[sizeof(NS_GET_TEMPLATE_IID(T)) + 1]; char TestForIID(...); template <class T> class nsCOMPtr final { ... ~nsCOMPtr() { static_assert(1 < sizeof(TestForIID(this)), "nsCOMPtr may only be templated over interfaces"); #ifndef NSCAP_FEATURE_USE_BASE NSCAP_LOG_RELEASE(this, mRawPtr); if (mRawPtr) { NSCAP_RELEASE(this, mRawPtr); } #endif }
Flags: needinfo?(neil)
(In reply to Aryeh Gregor from comment #13) > Now it compiles, but doesn't serve the intended purpose, because if you try > to do nsCOMPtr<nsRange> or whatever, it prints an error about ambiguous > conversion to nsISupports* and never prints the static_assert. Presumably > this is because it's in the destructor instead of the class proper. Maybe that's because you were using a debug build? Putting it in all the constructors is a bit awkward but would work. Putting it in the class doesn't work because the template type might still be incomplete.
No, that version doesn't work either. Sigh...
I have my fingers crossed for this version: // The .m0 works around a bug in MSVC. template<class T> char (&TestForIID(T*))[sizeof(NS_GET_TEMPLATE_IID(T).m0)]; static char TestForIID(...); template <class T> class nsCOMPtr final { ... ~nsCOMPtr() { static_assert(1 < sizeof(TestForIID(get())), "nsCOMPtr may only be templated over interfaces"); #ifndef NSCAP_FEATURE_USE_BASE NSCAP_LOG_RELEASE(this, mRawPtr); if (mRawPtr) { NSCAP_RELEASE(this, mRawPtr); } #endif }
That didn't work either. What wasn't helping is that there are three cases: 1. The class does not inherit from an interface 2. The class inherits from an interface 3. The class is an interface Then I found out I could make the code much simpler using decltype, so here it is: template<class T> char (&TestForIID(decltype(NS_GET_TEMPLATE_IID(T))*))[2]; template<class T> char TestForIID(...); template <class T> class nsCOMPtr final { ... ~nsCOMPtr() { static_assert(1 < sizeof(TestForIID<T>(nullptr)), "nsCOMPtr may only be templated over interfaces"); #ifndef NSCAP_FEATURE_USE_BASE NSCAP_LOG_RELEASE(this, mRawPtr); if (mRawPtr) { NSCAP_RELEASE(this, mRawPtr); } #endif }
This still fails on every time for me (nsITimer, nsIObserver, nsIWidget, etc.). :( Clang 3.5 on Ubuntu 14.04 64-bit.
Flags: needinfo?(neil)
Bah, MSVC 2013 bug - the extra parentheses introduced by NS_GET_IID turn the variable into an lvalue expression. Fortunately the fix is simple: template<class T> char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2]; template<class T> char TestForIID(...); template <class T> class nsCOMPtr final { ... ~nsCOMPtr() { static_assert(1 < sizeof(TestForIID<T>(nullptr)), "nsCOMPtr may only be templated over interfaces"); #ifndef NSCAP_FEATURE_USE_BASE NSCAP_LOG_RELEASE(this, mRawPtr); if (mRawPtr) { NSCAP_RELEASE(this, mRawPtr); } #endif }
Flags: needinfo?(neil)
Attached patch PatchSplinter Review
Assignee: nobody → ayg
Status: NEW → ASSIGNED
Attachment #8646476 - Flags: review?(nfroyd)
Comment on attachment 8646476 [details] [diff] [review] Patch >- nsCOMPtr<SmsMessage> message; >+ nsRefPtr<SmsMessage> message; Actually this variable is completely unused.
Comment on attachment 8646476 [details] [diff] [review] Patch Review of attachment 8646476 [details] [diff] [review]: ----------------------------------------------------------------- r=me; if we can just have a definition of assert_validity, that'd be better than sprinkling assert_validity calls all over the place. ::: xpcom/glue/nsCOMPtr.h @@ +426,5 @@ > > nsCOMPtr() > : NSCAP_CTOR_BASE(0) > { > + assert_validity(); Do we need to "call" assert_validity all over the place, or can we just rely on assert_validity being instantiated (since it's declared inline in the class body) and triggering the static_assert thereby?
Attachment #8646476 - Flags: review?(nfroyd) → review+
(In reply to Nathan Froyd from comment #24) > Do we need to "call" assert_validity all over the place, or can we just rely > on assert_validity being instantiated (since it's declared inline in the > class body) and triggering the static_assert thereby? No, it doesn't get instantiated until you actually use it. Also, you can't put the assertion in the body of the nsCOMPtr template because the definition of T may not be complete yet. Although putting the assertion in the destructor avoids multiple copies, it delays the generation of the assertion until after any usage errors.
(In reply to neil@parkwaycc.co.uk from comment #25) > No, it doesn't get instantiated until you actually use it. Yes, that's what I found.
Status: ASSIGNED → RESOLVED
Closed: 10 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla43
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: