aaron.ballman added inline comments.

================
Comment at: clang/lib/Analysis/ThreadSafety.cpp:1644
     if (!LDat) {
-      Analyzer->Handler.handleMutexNotHeld("", D, POK, Cp.toString(),
-                                           LK_Shared, Loc);
+      Analyzer->Handler.handleMutexNotHeld("negative capability", D, POK,
+                                           Cp.toString(), LK_Shared, Loc);
----------------
aaronpuchert wrote:
> aaron.ballman wrote:
> > aaronpuchert wrote:
> > > aaron.ballman wrote:
> > > > aaronpuchert wrote:
> > > > > aaron.ballman wrote:
> > > > > > 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()`?
> > > > > 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.
> > > > 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).
> > > 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.
> > > 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?
> 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".
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.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D84603/new/

https://reviews.llvm.org/D84603

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to