This is an archive of the discontinued LLVM Phabricator instance.

Thread safety analysis: More consistent warning message
ClosedPublic

Authored by aaronpuchert on Jul 26 2020, 11:51 AM.

Details

Summary

Other warning messages for negative capabilities also mention their
kind, and the double space was ugly.

Diff Detail

Event Timeline

aaronpuchert created this revision.Jul 26 2020, 11:51 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 26 2020, 11:51 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
aaron.ballman added inline comments.Jul 27 2020, 10:56 AM
clang/lib/Analysis/ThreadSafety.cpp
1644

It's a bit odd that we aren't using DiagKind as below, I assume that's because this is a negative test and the others are positive tests, but doesn't this introduce a terminology difference where a positive failure may call it a mutex and a negative failure may call it a negative capability? Should this be hooked in to ClassifyDiagnostic() (perhaps we need a ClassifyNegativeDiagnostic()?

aaronpuchert added inline comments.Jul 27 2020, 11:47 AM
clang/lib/Analysis/ThreadSafety.cpp
1644

My thinking was that whatever the positive capability is called, we should only talk about a "negative capability" instead of a "negative mutex" or a "negative role". Also because not holding a capability is in some way its own kind of capability.

clang/test/SemaCXX/warn-thread-safety-negative.cpp
46

Here we're always referring to a "negative capability", I believe it's hardcoded into the message.

@aaron.ballman, can you have a look again? I think this change is consistent with what we're already doing.

aaron.ballman added inline comments.Aug 26 2020, 8:43 AM
clang/lib/Analysis/ThreadSafety.cpp
1644

I may still be confused or thinking of this differently, but I would assume that a negative mutex would be a mutex that's explicitly not held, which you may want to ensure on a function boundary to avoid deadlock. From that, I'd have guessed we would want the diagnostic to read cannot call function 'bar' while mutex 'mu' is held or calling function 'bar' requires mutex 'mu' to not be held because that's more clear than talking about negative capabilities (when the user is thinking in terms of mutexes which are or aren't held).

aaronpuchert added inline comments.Aug 26 2020, 3:02 PM
clang/lib/Analysis/ThreadSafety.cpp
1644

Now I get it. I don't see an issue with that, but we need to distinguish between EXCLUDES(mu) and REQUIRES(!mu). The former will produce "cannot call function 'bar' while mutex 'mu' is held" and we probably want the latter to produce a different warning message.

Now one argument for the existing scheme remains that with -Wthread-safety-negative, if you see a warning like "acquiring mutex 'mu' requires negative capability '!mu'" on a lock operation, you know you can fix that by adding REQUIRES(!mu) to your function.

If we say "warning: acquiring mutex 'mu' requires mutex 'mu' not to be held" it might not be as clear what we want.

aaron.ballman added inline comments.Aug 27 2020, 8:03 AM
clang/lib/Analysis/ThreadSafety.cpp
1644

Now I get it. I don't see an issue with that, but we need to distinguish between EXCLUDES(mu) and REQUIRES(!mu). The former will produce "cannot call function 'bar' while mutex 'mu' is held" and we probably want the latter to produce a different warning message.

Ahhh, that's a good point.

Now one argument for the existing scheme remains that with -Wthread-safety-negative, if you see a warning like "acquiring mutex 'mu' requires negative capability '!mu'" on a lock operation, you know you can fix that by adding REQUIRES(!mu) to your function.

If we say "warning: acquiring mutex 'mu' requires mutex 'mu' not to be held" it might not be as clear what we want.

Hm, that's a good point as well.

Now that I understand the situation a bit better, I will be happy with either route, so I leave the decision in your capable hands. (If we get it wrong, we can always change the diagnostics later.) Do you have a preferred approach?

aaronpuchert added inline comments.Aug 27 2020, 4:32 PM
clang/lib/Analysis/ThreadSafety.cpp
1644

There are basically two warnings related to negative capabilities: one is about acquiring a capability without holding a negative capability, the other about calling a function without holding a negative capability. Negative capabilities can't be acquired or released, nor do they protect anything.

The other warning message also says "negative capability '!mu'", but mentions the original capability kind earlier:

def warn_acquire_requires_negative_cap : Warning<
  "acquiring %0 '%1' requires negative capability '%2'">,

I think that makes sense because there is a relation between the "positive" capability of whatever kind that we want to acquire and the negative capability that we need. The situation here is different: I just have some CallExpr to a function with REQUIRES(!...).

For that we have two different warnings, depending on whether we know the capability to be held or not:

void foo() REQUIRES(!mu);
void bar() REQUIRES(!mu) {
  mu.Lock();
  foo();        // warning: cannot call function 'foo' while mutex 'mu' is held
  mu.Unlock();
}
void baz() {
  foo();        // warning: calling function 'foo' requires holding [negative capability] '!mu'
}

The warning here is about the baz case where the analysis doesn't know whether I hold the capability (e.g. it could be acquired higher in the stack without the function being annotated), but I also don't explicitly hold the negative capability. Since negative capabilities can't be acquired, the only possible fix is to propagate the REQUIRES(!mu) until mu goes out of scope. And for that the capability kind is not really relevant.

What I could change in addition is to drop the "holding" for negative capabilities, because you can't really hold them, they are the absence of holding something. The other warning also says "requires negative capability" instead of "requires holding negative capability".

aaron.ballman added inline comments.Aug 28 2020, 7:28 AM
clang/lib/Analysis/ThreadSafety.cpp
1644

Thank you for the detailed explanation! I think dropping the "holding" so that it says it requires a negative capability makes sense.

Since negative capabilities can't be acquired, the only possible fix is to propagate the REQUIRES(!mu) until mu goes out of scope. And for that the capability kind is not really relevant.

I think that makes sense for the simple case where there's only one REQUIRES clause, but I was thinking about a more complex case where you have something like:

void foo() REQUIRES(!mu) REQUIRES(logging_role);
void baz() {
  foo();
}

and you're mixing capability kinds on the same declaration. However, because we mention the name of the thing we require in the diagnostic, I think that will still be plenty clear even without the capability kind and so we're likely fine.

Remove "holding" and moved to a separate message because the overlap was getting smaller.

Also introduced a separate function because it's more consistent with the other functions and because we can't easily distinguish between negative and positive capabilities otherwise.

aaronpuchert marked 4 inline comments as done.Aug 29 2020, 5:18 PM
aaronpuchert added inline comments.
clang/include/clang/Analysis/Analyses/ThreadSafety.h
205

Should probably be "a negative capability", silly copy-and-paste error.

aaron.ballman accepted this revision.Aug 31 2020, 6:27 AM

LGTM aside from a suggestion for a comment, thank you!

clang/include/clang/Analysis/Analyses/ThreadSafety.h
205

Warn when calling a function and a negative capability is not held.?

This revision is now accepted and ready to land.Aug 31 2020, 6:27 AM
This revision was automatically updated to reflect the committed changes.
aaronpuchert marked an inline comment as done.