This is an archive of the discontinued LLVM Phabricator instance.

[libcxx] Add clang thread safety annotations to mutex and lock_guard
ClosedPublic

Authored by jamesr on Nov 16 2015, 2:23 PM.

Details

Summary

This adds clang thread safety annotations to std::mutex and
std::lock_guard so code using these types can use these types directly
instead of having to wrap the types to provide annotations. These checks
when enabled by -Wthread-safety provide simple but useful static
checking to detect potential race conditions.
See http://clang.llvm.org/docs/ThreadSafetyAnalysis.html for details.

Diff Detail

Event Timeline

jamesr updated this revision to Diff 40343.Nov 16 2015, 2:23 PM
jamesr retitled this revision from to [libcxx] Add clang thread safety annotations to mutex and lock_guard.
jamesr updated this object.
jamesr added a subscriber: cfe-commits.
mclow.lists edited edge metadata.Nov 30 2015, 1:59 PM

In general, this looks fine. There are a few nits.

  1. There needs to be a way to disable these annotations.

The usual way in libc++ is to check to see if _LIBCPP_THREAD_ANNOTATION is already defined, and if so, do not define it. We usually state things in the negative, so the controlling macro should be _LIBCPP_HAS_NO_THREAD_ANNOTATION

  1. We need to support non-ToT versions of clang, so the test needs to be more nuanced than #if defined(__clang__). See L432 of <__config> for an example there.

More as I see them

We usually state things in the negative, so the controlling macro should be _LIBCPP_HAS_NO_THREAD_ANNOTATION

Or actually, _LIBCPP_HAS_NO_THREAD_ANNOTATIONS

Where are the tests?

jamesr updated this revision to Diff 41581.Dec 1 2015, 5:00 PM
jamesr edited edge metadata.

Updates the macros to avoid defining anything if _LIBCPP_THREAD_ANNOTATION is already defined and to define _LIBCPP_THREAD_ANNOTATION to nothing if clang is not set, __has_attribute(acquire_capability) is not set, or _LIBCPP_HAS_NO_THREAD_ANNOTATIONS is set.

It turns out there's not a clean feature guard for the thread safety annotations (i.e. no __has_feature() guard). The annotations were added over time to clang. I'm not sure at which point the feature was considered stable enough for general use. Looking at clang's history the most recently added annotation out of the ones this patch uses is acquire_capability which was added in this commit: https://llvm.org/svn/llvm-project/cfe/trunk@201890, so I'm testing for that annotation to test for support for the feature as a whole. It's quite possible that there are revisions of clang after the annotation support was added that contain bugs that make the annotations not very useful but the fix for any users trying to use such versions of clang is easy - just avoid passing -Wthread-safety to these versions of clang.

jamesr added a comment.Dec 1 2015, 5:12 PM

Where are the tests?

There aren't any yet. I investigated a few avenues for testing but none seem very useful.

The most obvious testing strategy would be to write some code that fails the -Wthread-safety checks such as

std::mutex m; m.lock(); m.lock();

and verify that this generates the expected warning. There's a harness for expected warnings that does this in the clang repo but no such harness exists in libcxx. Depending on the text of warnings from clang in libcxx tests seems like a layering violation, anyway. Another check would be to test that such code fails to compile with -Wthread-safety -Werror set. libcxx's test suite does support negative compilation tests but I can't find any way to specify that particular tests should be compiled with particular flags. I tried enabling -Wthread-safety for all tests, but other tests fail because they do things that the annotations do not understand (deliberately, the annotations are for static checking not dynamic code analysis). I could add suppressions to all of these tests but that seems backwards. This also hits the problem that such annotations would only have meaning for new-enough versions of clang and there's no way for the driver to cleanly tell if the cxx being used supports the annotations or not.

The annotations are (by design) not supposed to have an influence on the program other than generating warnings in non-default configs so there isn't any other testing hook I can think of.

It looks like Aaron Ballman was involved with adding parsing for these attributes in clang - perhaps he has an idea of a better way to detect these than __has_attribute(acquire_capability).

It looks like Aaron Ballman was involved with adding parsing for these attributes in clang - perhaps he has an idea of a better way to detect these than __has_attribute(acquire_capability).

__has_attribute is a reasonable approach. You are correct that there is (unfortunately) no good feature testing macro for these. The good news is, the attributes are relatively stable these days, and have been for a while.

FWIW, the annotations all LGTM.

~Aaron

jamesr added a comment.Dec 8 2015, 2:49 PM

Marshall - friendly ping. If you know a way to test functionality guarded by a compile-time flag in the libcxx test suite I'd be happy to wire in tests for this, but if that doesn't exist (and I think it does not currently) then I think the only way to verify this is by inspection. Let me know what you'd prefer when you get a chance. Thanks!

EricWF added a subscriber: EricWF.Mar 11 2016, 8:53 AM

I tried to do this over the summer. The author of the annotations insisted that it would be a bad idea to apply them to libc++ since it would break existing code. Should these be off by default?

Also at the time the annotations were unstable and somewhat buggy. Has this situtation cleared up?

Also could we add some ".fail.cpp" tests that use Clang verify? I'm happy to give an example if your unfamiliar with how libc++ handles testing.

EricWF edited edge metadata.Mar 11 2016, 9:30 AM

I generated an updated patch with a sample test case (and the LIT changes needed to run it).
https://gist.github.com/EricWF/12f77078e4efc6610572

Thank you for posting the LIT changes - that's the part I was unable to figure out. I have a number of additional failure test cases that I will add.

I don't know what sort of code this could break, although I certainly don't claim to know more about these annotations than the author. Do you have a link to that discussion handy? The most obvious way this could break existing code would be if existing code was compiling with -Wthread-safety and had its own annotations that were inconsistent with these, but as it's a compile error to declare thread-safety relationships with types that aren't already annotated I can't see how any code that would be broken could compile today. Concretely, if some code is written like this today:

struct Foo {

int a __attribute__((guarded_by(m)));
std::mutex m;

};

void Bad(Foo* f) { f->a++; }

will fail to compile without this patch in libc++ with the error:

/tmp/test.cc:4:26: error: 'guarded_by' attribute requires arguments whose type is annotated with 'capability' attribute; type here is 'std::mutex'

  [-Werror,-Wthread-safety-attributes]
int a __attribute__((guarded_by(m))

Unfortunately the discussion was offline so it's not available.

Anybody using std::mutex with std::unique_lock or their own lock guards will probably experience some breakage.

Heres an example of code that will now not compile with "-Werror=thread-safety"

std::mutex m;
m.lock();
{
  std::unique_lock<std::mutex> g(m, std::adopt_lock);
}  // -Wthread-safety thinks `m` is still locked

AFAIK we can't fully annotate std::unique_lock either because there is no way to model the deferred locking behavior.

My suggestion would be to make these annotations OFF by default and allow users to turn them on with a macro.

Ah, that's true. I didn't think of that case. With the design of these annotations the author of that function would have to disable checks in each piece of code that uses these patterns.

What about adding a different guard for these annotations in libc++ that consumers could choose to toggle independently of the -Wthread-safety compiler option? That way consumers could choose to enable the libc++ annotations if their codebase was compatible with them. The alternative is that consumers have to fully wrap the std:: types in their own types to add the annotations which works but makes code reuse very difficult.

My suggestion would be to make these annotations OFF by default and allow users to turn them on with a macro.

Our comments crossed streams but suggested the same thing :). Any suggestions on a naming convention for the guard? Also, would I set this macro unconditionally in LIT or is there a way to have it run tests both with and without the macro to ensure that when it is off code like the snippet you posted continues to build unmodified?

My suggestion would be to make these annotations OFF by default and allow users to turn them on with a macro.

Our comments crossed streams but suggested the same thing :). Any suggestions on a naming convention for the guard? Also, would I set this macro unconditionally in LIT or is there a way to have it run tests both with and without the macro to ensure that when it is off code like the snippet you posted continues to build unmodified?

My suggestion would be _LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS but I'm bad at naming things :-P

I think we leave the macro off in LIT because each test can #define it at the top of the file if it want's it on.

jamesr updated this revision to Diff 50481.Mar 11 2016, 2:55 PM
jamesr edited edge metadata.

This patch guards the new annotations with _LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS and adds a number of tests to check that the annotations produce errors when the annotations are enabled that code violates the thread safety rules, that correct code does not produce errors, and that code that does not enable the annotations does not produce errors even in code that the thread-safety feature in Clang does not understand.

The feature guard is state in the positive as opposed to the negative as the annotations may be incompatible with correct code in some situations.

EricWF added inline comments.Mar 15 2016, 9:15 AM
test/libcxx/thread/thread.mutex/thread_safety_access_guarded_without_lock.fail.cpp
1 ↗(On Diff #50481)

This test doesn't really test any libc++ behavior. It would fail in the same way before and after applying your patch. I don't think we need it.

test/libcxx/thread/thread.mutex/thread_safety_annotations_not_enabled.cpp
1 ↗(On Diff #50481)

'.pass.cpp'

test/libcxx/thread/thread.mutex/thread_safety_call_requires_capability_without_having.fail.cpp
1 ↗(On Diff #50481)

This test doesn't really test any libc++ behavior. It would fail in the same way before and after applying your patch. I don't think we need it.

test/libcxx/thread/thread.mutex/thread_safety_lock_guard.cpp
1 ↗(On Diff #50481)

'.pass.cpp' and main().

test/libcxx/thread/thread.mutex/thread_safety_lock_unlock.cpp
1 ↗(On Diff #50481)

'.pass.cpp' and add another 'main()' definition.

test/libcxx/thread/thread.mutex/thread_safety_requires_capability.cpp
1 ↗(On Diff #50481)

Passing tests need to have the suffix ".pass.cpp" in order for the test suite to pick them up.
Also this function will still need a definition for main in order to pass with GCC and other configurations.

jamesr marked 5 inline comments as done.Mar 15 2016, 6:33 PM

Thank you for the comments, new patch coming.. (is there a way to post comments and upload a new patch at the same time?)

jamesr updated this revision to Diff 50788.Mar 15 2016, 6:34 PM

So I fixed up LIT so that it also adds "-Werror=thread-safety" for both ".pass.cpp" and ".fail.cpp" tests. Could you apply it to your patch?
https://gist.github.com/EricWF/8a0bfb6ff02f8c9f9940

include/__mutex_base
37

Does capability have a alternative keyword that's a reserved name? ie __capability__? If so we should use that instead.

So I fixed up LIT so that it also adds "-Werror=thread-safety" for both ".pass.cpp" and ".fail.cpp" tests. Could you apply it to your patch?
https://gist.github.com/EricWF/8a0bfb6ff02f8c9f9940

Cool! Applied and confirmed that it's set for both.

include/__mutex_base
37

If I'm reading https://github.com/llvm-mirror/clang/blob/master/include/clang/Basic/Attr.td#L1657 correctly:

def Capability : InheritableAttr {

let Spellings = [GNU<"capability">, CXX11<"clang", "capability">,
                 GNU<"shared_capability">,
                 CXX11<"clang", "shared_capability">];

then the answer is unfortunately "no"

jamesr updated this revision to Diff 50790.Mar 15 2016, 6:58 PM

So this LGTM except for one last change (I'm sorry). LIT now knows when "-Werror=thread-safety" is being passed. Could you change the tests guarded by "#if !defined(clang) || !__has_attribute(aquire_capability)` to say "// REQUIRES: thread-safety" instead? I prefer using LIT to manage when tests run because it can report un-run tests where simply using macros cant.

After that LGTM.

include/__mutex_base
37

I appreciate the super thorough answer!

So this LGTM except for one last change (I'm sorry). LIT now knows when "-Werror=thread-safety" is being passed. Could you change the tests guarded by "#if !defined(clang) || !__has_attribute(aquire_capability)` to say "// REQUIRES: thread-safety" instead? I prefer using LIT to manage when tests run because it can report un-run tests where simply using macros cant.

After that LGTM.

If I do that, does that mean I can omit the extra main()s, etc, and the LIT harness will avoid attempting the test at all when the feature bit is set?

Yes exactly. // REQUIRES: <feature> tells LIT to skip the test (and report it as UNSUPPORTED) whenever the feature is not present.

jamesr updated this revision to Diff 50791.Mar 15 2016, 7:23 PM

Use // REQUIRES: thread-safety instead of macros to feature guard tests

EricWF accepted this revision.Mar 15 2016, 7:26 PM
EricWF edited edge metadata.

Oh fudge. One last change. The tests need license headers. Just copy it from an existing test. LGTM after that.

Thanks again.

This revision is now accepted and ready to land.Mar 15 2016, 7:26 PM
jamesr updated this revision to Diff 50793.Mar 15 2016, 7:33 PM
jamesr edited edge metadata.

Add LLVM license headers to new test files

EricWF closed this revision.Mar 15 2016, 7:35 PM

Committed in r263611.