This is an archive of the discontinued LLVM Phabricator instance.

handle scoped_lockable objects being returned by value in C++17
ClosedPublic

Authored by rsmith on Jan 10 2018, 6:24 PM.

Details

Summary

In C++17, guaranteed copy elision means that there isn't necessarily a constructor call when a local variable is initialized by a function call that returns a scoped_lockable by value. That causes code such as:

// see test for definition of ReadLockedPtr
ReadLockedPtr<X> get();
void f() {
  auto my_ptr = get();
  my_ptr->do_something_requiring_lock();
}

... to fail for two reasons: the lock my_ptr->mutex is not held in the do_something_requiring_lock() call, and the lock my_ptr is not held at the point of the implicit destructor call at the end of the function.

There isn't really a good way to work around this: we don't have a list of "these are the locks you should expect to be held when an object is returned by value". But we can approximate that by pretending that the local my_ptr variable was actually initialized by running the move constructor of ReadLockedPtr, which is what this patch does.

[Note that this doesn't address the more problematic case where the returned type has no copy or move constructor. If that comes up, it can be hacked around with something like:

struct SCOPED_LOCKABLE MyImmovableButReturnableScopedLock {
  MyImmovableButReturnableScopedLock(MyImmovableButReturnableScopedLock &&) = delete;
  MyImmovableButReturnableScopedLock(volatile MyImmovableButReturnableScopedLock&&) ATTRIBUTES_HERE; // not defined
};

... on the basis that no sane program will ever actually call the other move constructor.]

Ideas for alternative approaches welcome. Ideally I'd like something that allows existing C++<=14 code to continue to work in C++17 mode unchanged, which I think this approach mostly provides.

Diff Detail

Repository
rC Clang

Event Timeline

rsmith created this revision.Jan 10 2018, 6:24 PM

I think this approach is a reasonable approximation, but @delesley has the final word.

lib/Analysis/ThreadSafety.cpp
1994

Can you sprinkle some const correctness around this declaration?

2001

Should we care about access checking here (protected ctor within a reasonable context, or friendship)?

rsmith updated this revision to Diff 129496.Jan 11 2018, 12:05 PM
rsmith added inline comments.
lib/Analysis/ThreadSafety.cpp
1994

I can't make the CXXConstructorDecl const without adding a const_cast somewhere or making very large-scale changes to Clang :) The CXXConstructExpr constructor needs a pointer to a non-const CXXConstructorDecl.

I can make the CXXRecordDecl const. I think that's actually a const-correctness bug -- if const on an AST node means anything, it should mean that the AST is potentially immutable, and you shouldn't be able to get from a const AST node to a non-const one -- but it's probably closer to the ideal than no const, so I'll add a const there.

2001

I'm not completely sure. My thinking was that if someone uses the "private copy constructor with no definition" approach rather than the "deleted copy constructor" approach, we should ignore that copy constructor. But this would only really matter if they do that to their *move* constructor and they have an accessible copy constructor, which seems like a sufficiently bizarre situation to not have a special rule for it. (Well, it'd also matter if the private copy ctor with no definition had thread safety attributes, which also seems like a very strange case.)

Happy to go either way here. On balance I think I agree that it's more consistent with the general language rules to ignore access here.

rsmith marked 4 inline comments as done.Jan 11 2018, 12:32 PM
aaron.ballman accepted this revision.Jan 11 2018, 12:47 PM

LGTM, but you should wait for a bit to see if DeLesley has concerns.

lib/Analysis/ThreadSafety.cpp
1994

Thanks! I agree with you that const on an AST node should mean that the AST is immutable, but I think getting to that point would be a pretty large change to Clang in general, unfortunately.

2001

I think ignoring access is a good first approximation. We can refine later if we find the analysis requires it.

This revision is now accepted and ready to land.Jan 11 2018, 12:47 PM
delesley accepted this revision.Jan 11 2018, 2:00 PM

LGTM. Thanks, Richard!

rsmith closed this revision.Jan 11 2018, 2:15 PM

Committed as r322316.