[PATCH] D52888: Thread safety analysis: Handle conditional expression in getTrylockCallExpr
Aaron Ballman via Phabricator via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 4 12:14:51 PDT 2018
aaron.ballman added inline comments.
================
Comment at: lib/Analysis/ThreadSafety.cpp:1445
+ if (!TCond && FCond) {
+ Negate = !Negate;
+ return getTrylockCallExpr(COP->getCond(), C, Negate);
----------------
aaronpuchert wrote:
> aaron.ballman wrote:
> > Rather than do an assignment here, why not just pass `!Negate` directly below, since you're returning?
> The third parameter of `getTrylockCallExpr` is a (non-const) reference, so I can only pass an lvalue. It's basically a [call-by-reference](https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference) pattern.
Ah, I missed that when looking at the code in Phab, thanks for the explanation!
================
Comment at: test/SemaCXX/warn-thread-safety-analysis.cpp:1879-1880
+ void foo13() {
+ if (mu.TryLock() ? 1 : 0)
+ mu.Unlock();
+ }
----------------
aaronpuchert wrote:
> aaron.ballman wrote:
> > Can you add a test that shows we get it right even if the user does something less than practical, like:
> > ```
> > if (mu.TryLock() ? mu.TryLock() : false); // Warn about double lock
> > if (mu.TryLock() ? mu.Unlock(), 1 : 0)
> > mu.Unlock(); // Warn about unlocking unheld lock
> > if (mu.TryLock() ? 1 : mu.Unlock(), 0)
> > mu.Unlock(); // Warn about unlocking an unheld lock
> > if (mu.TryLock() ? (true ? mu.TryLock() : false) : false); // Warn about double lock
> > ```
> I'm afraid these don't work as expected if we don't branch on `?:` as before. Basically we treat this as if both conditions where evaluated unconditionally.
>
> On calling a try-lock function, no lock is immediately acquired. Only when we branch on the result of that try-lock call is the mutex added to the capability set on the appropriate branch. Now if we branch on `?:` (the situation before this change), we are going to complain about conditionally held locks on the join point in @hokein's use case, and if we don't branch (the behavior after this change), we are not treating your examples correctly. I'm not sure we can treat both as intended.
>
> I'm slightly leaning towards not branching: the C++ standard says that only the [used expression is actually evaluated](https://en.cppreference.com/w/cpp/language/operator_other#Conditional_operator), but I think it's not considered good practice to have side effects in a ternary operator. (I can't actually find it anywhere besides a discussion on [Hacker News](https://news.ycombinator.com/item?id=14810994). Personally I would prefer to use an if-statement if one of the expressions has side-effects.)
>
> Tell me what you think.
I agree that we don't want to encourage complex code in ternary operators, but that complexity does come up often as a result of macro expansion. It feels like there isn't a good answer here because either approach will have unexpected results.
I'll let @delesley be the tie-breaker on whether we follow ternary branches or not, but if we decide to accept this patch and not follow branches, I think we should capture those test cases as expected false negatives with a comment about why they happen and why it's expected behavior.
Related, does this patch then impact the behavior of code like:
```
void f(Mutex &M) {
M.Lock();
}
void g() {
Mutex mu;
(void)(true ? f(mu) : (void)0);
mu.Unlock(); // Ok
}
```
Repository:
rC Clang
https://reviews.llvm.org/D52888
More information about the cfe-commits
mailing list