This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Fix PR20855 -- libc++ incorrectly diagnoses illegal reference binding in std::tuple.
ClosedPublic

Authored by EricWF on Jan 11 2018, 7:33 PM.

Details

Summary

See https://bugs.llvm.org/show_bug.cgi?id=20855

Libc++ goes out of it's way to diagnose std::tuple constructions which are UB due to lifetime bugs caused by reference creation. For example:

// The 'const std::string&' is created *inside* the tuple constructor, and its lifetime is over before the end of the constructor call.
std::tuple<int, const std::string&> t(std::make_tuple(42, "abc"));

However, we are over-aggressive and we incorrectly diagnose cases such as:

void foo(std::tuple<int const&, int const&> const&);
foo(std::make_tuple(42, 42));

This patch fixes the incorrectly diagnosed cases, as well as converting the diagnostic to use the newly added Clang trait __reference_binds_to_temporary. The new trait allows us to diagnose cases we previously couldn't such as:

std::tuple<int, const std::string&> t(42, "abc");

Diff Detail

Repository
rCXX libc++

Event Timeline

EricWF created this revision.Jan 11 2018, 7:33 PM
rsmith added inline comments.Jan 12 2018, 4:19 PM
include/tuple
176–178

This line looks wrong to me. This disallows an rvalue reference tuple member from binding to an rvalue reference argument, no? Can you

return !is_reference<_Hp>::value || (is_reference<_Tp>::value && is_convertible<_RawTp*, _RawHp*>::value) || (reference wrapper special case);

instead? That should at least only reject valid code in cases where _RawTp is a class type that converts to a reference type.

177–178

It would be reasonable to consider is_base_of here too.

This will still diagnose valid and reasonable programs, such as:

struct ConvertToRef { operator int&(); };
std::tuple<int&> t = {ConvertToRef()};

... on compilers that don't provide the trait. You could maybe try to work around that by checking to see if the type has a member .operator int&(). But perhaps it's better to remove the non-conforming check entirely, at least in the case where you can't reasonably get it right.

This will still diagnose valid and reasonable programs, such as:

struct ConvertToRef { operator int&(); };
std::tuple<int&> t = {ConvertToRef()};

... on compilers that don't provide the trait. You could maybe try to work around that by checking to see if the type has a member .operator int&(). But perhaps it's better to remove the non-conforming check entirely, at least in the case where you can't reasonably get it right.

I agree with both your comments. I'll remove the non-conforming check.

EricWF updated this revision to Diff 129744.Jan 12 2018, 6:45 PM
  • Address @rsmith's comments by removing the fallback implementation of the diagnostics.
rsmith accepted this revision.Jan 22 2018, 10:41 PM

Looks good, thanks!

Maybe the assertion message could be clearer about why this is a problem though? ("binds to a temporary whose lifetime has ended" maybe?)

This revision is now accepted and ready to land.Jan 22 2018, 10:41 PM
EricWF updated this revision to Diff 131348.Jan 24 2018, 2:14 PM
  • Improve diagnostic as per @rsmiths suggestion.
This revision was automatically updated to reflect the committed changes.

__can_bind_reference() doesn't return anything when __reference_binds_to_temporary doesn't exist. This causes builds break with old compilers.

Should it just return true if __reference_binds_to_temporary doesn't exist?

Created D42510 for discussion. I'm not sure if it's a good idea, though.

include/tuple