After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 572978 - Glib::ustring allows comparison to 0
Glib::ustring allows comparison to 0
Status: RESOLVED FIXED
Product: glibmm
Classification: Bindings
Component: strings
unspecified
Other Linux
: Normal normal
: ---
Assigned To: gtkmm-forge
gtkmm-forge
Depends on:
Blocks:
 
 
Reported: 2009-02-24 13:56 UTC by Armin Burgmeier
Modified: 2015-12-11 17:22 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Test case (871 bytes, text/plain)
2015-12-10 15:06 UTC, Kjell Ahlstedt
Details

Description Armin Burgmeier 2009-02-24 13:56:36 UTC
The following code compiles fine, but generates a warning at runtime:

Glib::ustring str;
if(str == 0) ...;

However, this does not work with std::string:

std::string str;
if(str == 0) ...;

This produces:

error: no match for 'operator==' in 'str == 0'
Comment 1 Murray Cumming 2009-02-28 11:17:29 UTC
I bet Daniel knows why.
Comment 2 Daniel Elstner 2009-02-28 11:43:01 UTC
I haven't actually tried it yet, but I think it's most likely because we have

    inline bool operator==(const ustring& lhs, const char* rhs)

in ustring.h. Your C++ standard library (i.e. GNU libstdc++) probably doesn't have it, and the string literal therefore goes through

    std::string::string(const string::char_type*)

first. Calling the constructor implicitely _and_ interpreting 0 as a pointer constant would require more than one hop, and therefore doesn't happen.

Any volunteers for actually verifying this hypothesis? ;-)
Comment 3 Armin Burgmeier 2009-02-28 12:46:19 UTC
There is this definition in /usr/lib/gcc/x86_64-pc-linux-gnu/4.3.3/include/g++-v4/bits/basic_string.h:

  template<typename _CharT, typename _Traits, typename _Alloc>
    inline bool
    operator==(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
               const _CharT* __rhs)
    { return __lhs.compare(__rhs) == 0; }

I have verified that this is indeed used for this code:

  std::string s;
  s == "foo";
Comment 4 Daniel Elstner 2009-02-28 13:56:21 UTC
(In reply to comment #3)
> There is this definition in
> /usr/lib/gcc/x86_64-pc-linux-gnu/4.3.3/include/g++-v4/bits/basic_string.h:
> 
>   template<typename _CharT, typename _Traits, typename _Alloc>
>     inline bool
>     operator==(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
>                const _CharT* __rhs)
>     { return __lhs.compare(__rhs) == 0; }

OK. I think this is different because it is a template. See Stroustrup, chapter 13.3.2:  Templates are resolved _before_ overloads or extra type conversions are considered. At that point, the compiler looks for a match to the argument list

    (const std::string&, int)

and the operator==() template as defined above won't match that, and that's it.

std::min() and std::max(), for instance, also exhibit this behavior -- both arguments have to be of the same type if the template type is to be derived implicitly.
Comment 5 Armin Burgmeier 2009-02-28 23:20:10 UTC
So I guess that means there is not much we can do about this, right?
Comment 6 Daniel Elstner 2009-03-03 11:05:18 UTC
(In reply to comment #5)
> So I guess that means there is not much we can do about this, right?

Well, we could remove the shortcut overloads for 'const char*' arguments and rely on the implicit conversion from 'const char*' to Glib::ustring.

However, while this would work, I'm not sure it's actually worth it. Actually, I don't think the problem you hit even qualifies as a bug. You could still pass a nil pointer indirectly. It's just a quirk of the C++ language that it doesn't have a context-independent literal for the nil pointer.

So, personally, I'd prefer to close this as either WONTFIX or NOTABUG. Any objections?
Comment 7 Armin Burgmeier 2009-03-03 11:19:08 UTC
Can we maybe add a g_assert or a g_return_val_if_fail to the comparison operator if the const char* parameter is 0? There is already a warning from g_utf8_collate in that case, but this would make it easier to find out where the warning comes from. It would probably have saved me an hour.

Otherwise, I agree that it's not worth removing the const char* overload, also considering that it's probably inefficient to creating a temporary ustring just for comparison.
Comment 8 Daniel Elstner 2009-03-03 11:47:25 UTC
Definitely not to operator==(), because that's an inline function living in the header file. g_return_if_fail(), g_assert(), g_warning() and the like should never be used in a header file, since the string literals passed to it would no longer live in the library but pollute every single object file which makes use of the inline function in any way.

It could be added to Glib::ustring::compare(const char* rhs), however. But would that actually have helped? I like using g_return_if_fail(), but in this case it seems a bit arbitrary to put it here but not anywhere else. Generally we don't duplicate assertions of the underlying C functions, as a back trace in gdb would immediately reveal the caller anyway.

Would you have been any faster to find the source of the problem if the assertion had been in ustring::compare() instead of g_utf8_collate()?

I'm not totally opposed to the idea of adding a g_return_if_fail(), but I'm not convinced yet that there is much of a point in doing so.
Comment 9 Armin Burgmeier 2009-03-03 12:22:10 UTC
(In reply to comment #8)
> Would you have been any faster to find the source of the problem if the
> assertion had been in ustring::compare() instead of g_utf8_collate()?

I basically ignored the warning from g_utf8_collate since I was looking for a network problem, not for a string issue. Until I realized that I thought the string variable which I compared to 0 was an integer.

It would have let me know that the problem is somewhere in the C++ part of the code. It might have helped, but I'm not sure. Maybe not.

> I'm not totally opposed to the idea of adding a g_return_if_fail(), but I'm not
> convinced yet that there is much of a point in doing so.

Yeah, maybe let's wait. We can still add it later when others hit the same problem (and believe it helped).
Comment 10 Kjell Ahlstedt 2015-12-10 15:06:50 UTC
Created attachment 317124 [details]
Test case

Some "delete" declarations can fix the problem. The test case contains

  bool operator==(const ustring& lhs, int rhs) = delete;
  bool operator==(int lhs, const ustring& rhs) = delete;

We can add such declaractions to ustring.h. Also for operator!=().
"if (string1 == 0)" would generate a compile-time message like

  error: use of deleted function 

while "if (string1 == nullptr)" would pass the compilation and generate a
runtime message as before.

Without these declarations the comparisons 'string1 == 1' and '1 == string2'
behave quite unexpectedly. They call
  bool operator==(const Glib::QueryQuark& a, const Glib::QueryQuark& b)
because QueryQuark has non-explicit constructors that can convert a ustring
and a GQuark (which is guint32) to QueryQuark. No compile-time error message,
no runtime error message! Even worse than 'string1 == 0'!

Shall I add some such "delete" declarations?
Comment 11 Murray Cumming 2015-12-10 20:20:46 UTC
Sounds good to me.