This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Add _LIBCPP_ENABLE_NODISCARD and _LIBCPP_NODISCARD_EXT to allow pre-C++2a [[nodiscard]]
ClosedPublic

Authored by lebedev.ri on Apr 2 2018, 12:33 PM.

Details

Summary

The [[nodiscard]] attribute is intended to help users find bugs where
function return values are ignored when they shouldn't be. After C++17 the
C++ standard has started to declared such library functions as [[nodiscard]].
However, this application is limited and applies only to dialects after C++17.
Users who want help diagnosing misuses of STL functions may desire a more
liberal application of [[nodiscard]].

For this reason libc++ provides an extension that does just that! The
extension must be enabled by defining _LIBCPP_ENABLE_NODISCARD. The extended
applications of [[nodiscard]] takes two forms:

  1. Backporting [[nodiscard]] to entities declared as such by the standard in newer dialects, but not in the present one.
  1. Extended applications of [[nodiscard]], at the libraries discretion, applied to entities never declared as such by the standard.

Users may also opt-out of additional applications [[nodiscard]] using
additional macros.

Applications of the first form, which backport [[nodiscard]] from a newer
dialect may be disabled using macros specific to the dialect it was added. For
example _LIBCPP_DISABLE_NODISCARD_AFTER_CXX17.

Applications of the second form, which are pure extensions, may be disabled
by defining _LIBCPP_DISABLE_NODISCARD_EXT.

This patch was originally by Roman Lebedev.

Diff Detail

Repository
rCXX libc++

Event Timeline

lebedev.ri created this revision.Apr 2 2018, 12:33 PM

Actually use [[gnu::nodiscard]] when meant to

It's [[*::warn_unused_result]], not [[*::nodiscard]]. Failed to copy-paste from godbolt.

Also, @thakis notes:

[22:39:11] <thakis_> LebedevRI: also, less combinatorial explosion if you do `#if __has_cpp_attribute(nodiscard) #define _LIBCPP_NODISCARD [[nodiscard]] #elif __has_attribute(warn_unused_result) #define _LIBCPP  __attribute__((warn_unused_result)) #else #define _LIBCPP_NODISCARD` and then  `#if (_LIBCPP_STD_VER > 17 && !_LIBCPP_DISABLE>>) || defined(_LIBCPP_FORCE_NODISCARD) #  define _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_NODISCARD # else #  define 
[22:39:12] <thakis_> _LIBCPP_NODISCARD_AFTER_CXX17 #endif`
lebedev.ri updated this revision to Diff 140679.Apr 2 2018, 1:21 PM

Split _LIBCPP_NODISCARD_AFTER_CXX17 defining into two defines, to avoid combinatorial explosion as per @thakis.

thakis accepted this revision.Apr 2 2018, 1:23 PM

I’d expect _LIBCPP_FORCE_NODISCARD to win over _LIBCPP_DISABLE_NODISCARD_AFTER_CXX17, but /shrug. Thanks!

This revision is now accepted and ready to land.Apr 2 2018, 1:23 PM
lebedev.ri retitled this revision from [private][libc++] Add _LIBCPP_FORCE_NODISCARD define to force-enable nodiscard in pre-C++17 to [libc++] Add _LIBCPP_FORCE_NODISCARD define to force-enable nodiscard in pre-C++17.Apr 2 2018, 3:00 PM
lebedev.ri added a subscriber: cfe-commits.
Quuxplusone added inline comments.
test/libcxx/diagnostics/force_nodiscard.fail.cpp
15 ↗(On Diff #140679)

What is the purpose of _LIBCPP_FORCE_NODISCARD?
On one of your other nodiscard-related reviews, @EricWF suggested that we should warn all the time, even e.g. on a discarded std::move(x) in C++11, or a discarded vec.empty() in C++03. And *maybe* we could provide an opt-out mechanism, but honestly *I* don't see why anyone would want to opt out.
_LIBCPP_FORCE_NODISCARD smells like an opt-in mechanism, which I would think is the opposite of what we want.

test/libcxx/diagnostics/force_nodiscard.pass.cpp
16 ↗(On Diff #140679)

What is the purpose of _LIBCPP_DISABLE_NODISCARD_AFTER_CXX17? I guess I could understand a blanket opt-in "please don't warn me about discarded [[nodiscard]] results"; but that should be (and is) spelled -Wno-unused-result, and it has nothing to do with C++17.

I like how this patch defines _LIBCPP_NODISCARD in non-C++17 modes; that's going to be very useful. But I think all these opt-in mechanisms are confusing and not-helpful.

If we must have an opt-in/out mechanism (which I don't believe we do), please consider adding the following two lines to <__config> and removing the rest:

#ifdef _LIBCPP_NODISCARD
    // the user has given us their preferred spelling; use it unconditionally
#elif __has_cpp_attribute(nodiscard) && _LIBCPP_STD_VER > 17
    [... etc etc ...]
lebedev.ri added inline comments.Apr 3 2018, 1:12 PM
test/libcxx/diagnostics/force_nodiscard.fail.cpp
15 ↗(On Diff #140679)

_LIBCPP_FORCE_NODISCARD smells like an opt-in mechanism

Correct.

And *maybe* we could provide an opt-out mechanism, but honestly *I* don't see why anyone would want to opt out.

I agree.

which I would think is the opposite of what we want.

Also correct.

test/libcxx/diagnostics/force_nodiscard.pass.cpp
16 ↗(On Diff #140679)

If we must have an opt-in/out mechanism (which I don't believe we do)

Yes, we do.
Opt-out is pre-existing, and removing it would be an [unacceptable] regression.
Opt-in is an enhancement. Of course, it would be nice to always default it to on,
but as it was disscussed with @mclow.lists, this is simply not going to happen.
This is the best we'll get.

#ifdef _LIBCPP_NODISCARD
    // the user has given us their preferred spelling; use it unconditionally

So you propose to shift the burden of picking which define to use to each and every
libc++ user (who wants to enable nodiscard attribute for pre-C++17/whatever) out there?
I really don't see how that would be better.

If we must have an opt-in/out mechanism (which I don't believe we do)

Yes, we do. Opt-out is pre-existing, and removing it would be an [unacceptable] regression.

Ah, I see now that you weren't the originator of _LIBCPP_DISABLE_NODISCARD_AFTER_CXX17; that existed on the left-hand side of this diff already. Okay, I don't object to keeping it around for historical interest.

Opt-in is an enhancement. Of course, it would be nice to always default it to on, but as it was disscussed with @mclow.lists, this is simply not going to happen. This is the best we'll get.

Is @mclow.lists on record anywhere as saying that, that you could link to? I ask because I haven't seen him commenting on these patches. At the moment, it seems like you're alone in both pushing these patches and simultaneously arguing that any effort is futile. :)

#ifdef _LIBCPP_NODISCARD
   // the user has given us their preferred spelling; use it unconditionally

So you propose to shift the burden of picking which define to use to each and every libc++ user

Not at all! I merely suggested that rather than having three different macros concerned with the spelling of nodiscard, you reduce it to *one* macro: _LIBCPP_NODISCARD. There would be a "sensible default" (namely, whatever spelling Does The Right Thing on the detected compiler), and then users who didn't want that default would be free to override it by passing -D on the command line. The #ifdef I suggested is a clean, concise way of allowing the user's explicit choice to override the libc++-provided "sensible default", no matter whether the user is trying to opt-in on an unsupported compiler (-D_LIBCPP_NODISCARD='[[foobar::nodiscard]]') or opt-out on a supported compiler (-D_LIBCPP_NODISCARD=''). It would be a way to eliminate the soup of ENABLE_ and DISABLE_ macros.

Of course the benefit of this idea decreases if we must preserve _LIBCPP_DISABLE_NODISCARD_AFTER_CXX17 for historical reasons anyway.

Opt-in is an enhancement. Of course, it would be nice to always default it to on, but as it was disscussed with @mclow.lists, this is simply not going to happen. This is the best we'll get.

Is @mclow.lists on record anywhere as saying that, that you could link to?

That particular disscussion was in IRC, so no, i can not link/paste.

I ask because I haven't seen him commenting on these patches.

At the moment, it seems like you're alone in both pushing these patches and simultaneously arguing that any effort is futile. :)

:)

Is Marshall arguing that the standard doesn't allow compilers to warn about failing to use these function results prior to C++17? Because I don't think that's true; warnings are thoroughly non-normative.

If libc++ wants to allow its users to opt out of these warnings just for libc++ functions, that's fine (although unusual), but it seems to me that that ought to be an explicit opt-out that's independent of language version.

Is Marshall arguing that the standard doesn't allow compilers to warn about failing to use these function results prior to C++17?

No. He was simply saying that people are opposed to having nodiscard attribute specified when the standard does not require that yet.
For same reason, this has to be an opt-in (_LIBCPP_FORCE_NODISCARD), it can't be just an opt-out.
But, you might as well just ask him that.

EricWF added a comment.Apr 3 2018, 2:49 PM

I'm also in favor of making this opt-out instead of opt-in, except for a handful of functions like unique_ptr::release which may yield false positives. However, for functions where discarding the return value is always a bug, I don't see an issue in libc++ emitting a warning where it didn't previously.

Yes, if we think that the committee is likely to include questionable functions on the [[nodiscard]] list which we don't want to warn about pre-C++17, then it makes sense to have two internal macros, one for functions like std::move that should be unconditionally warned about and one for the iffy-bu-standard-directed cases.

lebedev.ri updated this revision to Diff 140934.Apr 4 2018, 4:25 AM

Based on IRC disscussion, try to update the patch.

The main problem is that gcc does not silence the warning produced by
these attributes via (void) cast, unless it's [[nodiscard]] attribute :/
https://godbolt.org/g/m3gPKQ

So until they fix that (they will fix that right? :)),
we can only support _LIBCPP_FORCE_NODISCARD for clang compilers.

gcc does not silence the warning produced by
these attributes via (void) cast, unless it's [[nodiscard]] attribute :/
[...] So until they fix that (they will fix that right? :)),
we can only support _LIBCPP_FORCE_NODISCARD for clang compilers.

The GCC warn_unused_result issue is https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 and also https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 . I would certainly love for them to fix it, but the sentiment expressed in those bug reports is "no! never! we love being broken!". :/
I re-suggest my idea that perhaps libc++ should respect the user's choice to pass -D_LIBCPP_NODISCARD=__attribute__((warn_unused_result)) on the GCC command line. This would make your patch only one line longer than it is right now...

I'm waiting for @mclow.lists to have the final say re this differential.

gcc does not silence the warning produced by
these attributes via (void) cast, unless it's [[nodiscard]] attribute :/
[...] So until they fix that (they will fix that right? :)),
we can only support _LIBCPP_FORCE_NODISCARD for clang compilers.

The GCC warn_unused_result issue is https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 and also https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 . I would certainly love for them to fix it, but the sentiment expressed in those bug reports is "no! never! we love being broken!". :/
I re-suggest my idea that perhaps libc++ should respect the user's choice to pass -D_LIBCPP_NODISCARD=__attribute__((warn_unused_result)) on the GCC command line. This would make your patch only one line longer than it is right now...

So roughly:

// NOTE: Do not use [[nodiscard]] in pre-C++17 mode
//       to avoid -Wc++17-extensions warning.
// And we can't use GCC's [[gnu::warn_unused_result]] and
// __attribute__((warn_unused_result)),
// because GCC does not silence them via (void) cast.
#if !defined(_LIBCPP_NODISCARD)
#  if __has_cpp_attribute(nodiscard) && _LIBCPP_STD_VER > 17
#    define _LIBCPP_NODISCARD [[nodiscard]]
#  elif __has_cpp_attribute(clang::warn_unused_result)
#    define _LIBCPP_NODISCARD [[clang::warn_unused_result]]
#  else
#    define _LIBCPP_NODISCARD
#  endif
#endif

?

I'm waiting for @mclow.lists to have the final say re this differential.

Ack. :)

So roughly:

// NOTE: Do not use [[nodiscard]] in pre-C++17 mode
//       to avoid -Wc++17-extensions warning.
// And we can't use GCC's [[gnu::warn_unused_result]] and
// __attribute__((warn_unused_result)),
// because GCC does not silence them via (void) cast.
#if !defined(_LIBCPP_NODISCARD)
#  if __has_cpp_attribute(nodiscard) && _LIBCPP_STD_VER > 17
#    define _LIBCPP_NODISCARD [[nodiscard]]
#  elif __has_cpp_attribute(clang::warn_unused_result)
#    define _LIBCPP_NODISCARD [[clang::warn_unused_result]]
#  else
#    define _LIBCPP_NODISCARD
#  endif
#endif

?

Yes, that correctly expresses my intent. Personally I would write it like this to reduce the nesting and linecount:

// NOTE: Do not use [[nodiscard]] in pre-C++17 mode
//       to avoid -Wc++17-extensions warning.
// And we can't use GCC's [[gnu::warn_unused_result]] and
// __attribute__((warn_unused_result)),
// because GCC does not silence them via (void) cast.
#if defined(_LIBCPP_NODISCARD)
#elif __has_cpp_attribute(nodiscard) && _LIBCPP_STD_VER > 17
#  define _LIBCPP_NODISCARD [[nodiscard]]
#elif __has_cpp_attribute(clang::warn_unused_result)
#  define _LIBCPP_NODISCARD [[clang::warn_unused_result]]
#else
#  define _LIBCPP_NODISCARD
#endif

Is Marshall arguing that the standard doesn't allow compilers to warn about failing to use these function results prior to C++17? Because I don't think that's true; warnings are thoroughly non-normative.

I have proposed to add different forms of "nodiscard" warnings to libc++ in the past, and received strong pushback from people who:

  • Compile with -Werror
  • Don't want to change their code.

I'm not willing to have that argument again.

So you're happy with this opt-in version?

Is Marshall arguing that the standard doesn't allow compilers to warn about failing to use these function results prior to C++17? Because I don't think that's true; warnings are thoroughly non-normative.

I have proposed to add different forms of "nodiscard" warnings to libc++ in the past, and received strong pushback from people who:

  • Compile with -Werror
  • Don't want to change their code.

I'm not willing to have that argument again.

Alright, well, I won't push you on it.

So you're happy with this opt-in version?

I'm happy with an opt-in mechanism, yes.
This one is not quite right yet.

BTW, I expect a large set of calls in the standard library to get marked as [[nodiscard]] in Rapperswil.

include/__config
1057

[[nodiscard]] is a C++17 feature. This test should be >=, not >.

1067

I wouldn't change this; just leave it as [[nodiscard]]

lebedev.ri added inline comments.Apr 24 2018, 10:59 AM
include/__config
1057

Indeed, though the left hand side of the diff already specified _LIBCPP_STD_VER > 17.
Interesting, isn't it.

1067

But that would defy the purpose?

mclow.lists added inline comments.Apr 24 2018, 11:08 AM
include/__config
1057

That was because the flag _LIBCPP_NODISCARD_AFTER_CXX17 was only active post-c++17.

Now we're talking about doing stuff in earlier standards, we should use [[nodiscard]] for C++17

1067

That's a fair point - but I don't want this to be anything but [[nodiscard]] if we're compiling for C++2a or later.

lebedev.ri added inline comments.Apr 24 2018, 11:14 AM
include/__config
1057

Oh, right, sorry!

1067

I don't want this to be anything but [[nodiscard]] if we're compiling for C++2a or later.

Aha, i see, ok.

lebedev.ri marked 8 inline comments as done.

Updated based on @mclow.lists review.

test/libcxx/diagnostics/force_nodiscard.pass.cpp
16 ↗(On Diff #140679)

I tried to do that, but completely failed to come up with testing, so i'm not going to do this here, sorry.

mclow.lists added inline comments.Apr 25 2018, 6:44 AM
test/libcxx/diagnostics/force_nodiscard.fail.cpp
22 ↗(On Diff #143872)

Shouldn't this be just _LIBCPP_NODISCARD ?

lebedev.ri added inline comments.Apr 25 2018, 6:49 AM
test/libcxx/diagnostics/force_nodiscard.fail.cpp
22 ↗(On Diff #143872)

I don't think so?
I thought we are intentionally testing the same macro that libc++ is using internally.

mclow.lists added inline comments.Apr 25 2018, 7:18 AM
test/libcxx/diagnostics/force_nodiscard.fail.cpp
22 ↗(On Diff #143872)

Ok, I see what you're saying.

This test is testing if a function marked _LIBCPP_NODISCARD_AFTER_CXX17 gives an error if _LIBCPP_FORCE_NODISCARD is defined.

But then you need another test, just like this, with _LIBCPP_NODISCARD int foo() { return 6; } to make sure that that gives an error as well. (and a passing test, that shows that if you don't opt-in, you get no error)

BTW, you can gang several failing tests together, and check all the error messages - see libcxx/test/libcxx/containers/unord/unord.set/missing_hash_specialization.fail.cpp for an example.

What's the status here? The MS STL has this feature under _HAS_NODISCARD and it's super useful.

lebedev.ri marked an inline comment as done.

Right, sorry. Let's try to revive this.
Does this test coverage look better?
What did i miss?

EricWF commandeered this revision.Jul 9 2018, 12:40 PM
EricWF edited reviewers, added: lebedev.ri; removed: EricWF.

Hijacking with permission.

EricWF updated this revision to Diff 154724.Jul 9 2018, 4:01 PM
EricWF retitled this revision from [libc++] Add _LIBCPP_FORCE_NODISCARD define to force-enable nodiscard in pre-C++17 to [libc++] Add _LIBCPP_ENABLE_NODISCARD and _LIBCPP_NODISCARD_EXT to allow pre-C++2a [[nodiscard]].
EricWF edited the summary of this revision. (Show Details)

This updates just about everything in this patch. See the updated description for the overall design.

Specific issues addressed by this patch are:

  • Rename _LIBCPP_FORCE_NODISCARD to _LIBCPP_ENABLE_NODISCARD for consistency.
  • Add the _LIBCPP_NODISCARD_EXT macro to allow appling [[nodiscard]] as a pure extension.
  • Document all new user facing macros and how to use them.
  • Document the extension in general. (Adding a section in which libc++ should document all its extensions).
  • Use [[clang::warn_unused_result]] in place of [[nodiscard]] when it's not available. All supported clang versions provide this attribute. This applies to _LIBCPP_NODISCARD_AFTER_CXX17 which may be enabled in before [[nodiscard]] is supported.
  • Add more tests for all combinations.
EricWF added a comment.Jul 9 2018, 4:01 PM

A couple other notes:

  • Add an application of _LIBCPP_NODISCARD_EXT to get_temporary_buffer to demonstrate usage.
thakis added a comment.Sep 6 2018, 7:06 AM

What's the status here? Did this land?

EricWF added a comment.Sep 6 2018, 7:09 AM

I don't think it ever landed. I'll try to get it in this week.

I don't think it ever landed. I'll try to get it in this week.

What does this need?
Is there some changes missing still?
Or this simply needs to be committed (and bots watched?)

I don't think it ever landed. I'll try to get it in this week.

What does this need?
Is there some changes missing still?
Or this simply needs to be committed (and bots watched?)

I think this was good to go. I was just waiting to give time for comments. Obviously I've waited long enough.

I'll work on this today.

I don't think it ever landed. I'll try to get it in this week.

What does this need?
Is there some changes missing still?
Or this simply needs to be committed (and bots watched?)

I think this was good to go. I was just waiting to give time for comments. Obviously I've waited long enough.

Ah, great.

I'll work on this today.

If wanted, i can commandeer this back and land.

EricWF accepted this revision.Sep 22 2018, 10:21 AM

@lebedev.ri Sure, be my guest. It frees me up to do reviews. Thank you.

I just finished running the test suite, so it should be good to go.

lebedev.ri commandeered this revision.Sep 22 2018, 10:25 AM
lebedev.ri removed a reviewer: lebedev.ri.

@lebedev.ri Sure, be my guest. It frees me up to do reviews. Thank you.

Np.

I just finished running the test suite, so it should be good to go.

Great, thank you!

Commandeering back to me, let's land this ^^

This revision was automatically updated to reflect the committed changes.
This revision was automatically updated to reflect the committed changes.
test/libcxx/diagnostics/enable_nodiscard_disable_nodiscard_ext.fail.cpp