This changes add a new warning named -Wcompare-op-parentheses that's
part of the -Wparentheses diagnostic group. This diagnostic produces a
warning when a pattern like 'x<=y<=z' is found. When this pattern is not
qualified by parentheses, it's equivalent to '(x<=y ? 1 : 0) <= z',
which is a different interpretation from that of ordinary mathematical
notation.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
This is an implementation in the CFE, after submitting and getting comments on https://reviews.llvm.org/D84898.
clang/docs/DiagnosticsReference.rst | ||
---|---|---|
2853 | s/-no-/-op-/ | |
9885 | s/-no-/-op-/ | |
clang/include/clang/Basic/DiagnosticSemaKinds.td | ||
6135 | Why is this x<=y<=z instead of the simpler x<y<z or even x<=y<z (the "half-open range" common case)? | |
clang/test/Sema/warn-compare-op-parentheses.c | ||
2 | These are some great test cases, but I think they would benefit from being exhaustive. int tests(int p1, int p2, int p3) { bool b; b = (p1 < p2 < p3); // expected-warning{{comparisons like 'x<y<z' are interpreted}} expected-note{{place parentheses}} b = (p1 < p2 <= p3); // expected-warning{{comparisons like 'x<y<z' are interpreted}} expected-note{{place parentheses}} b = (p1 < p2 > p3); // expected-warning{{comparisons like 'x<y<z' are interpreted}} expected-note{{place parentheses}} b = (p1 < p2 >= p3); // expected-warning{{comparisons like 'x<y<z' are interpreted}} expected-note{{place parentheses}} b = (p1 <= p2 < p3); // expected-warning{{comparisons like 'x<y<z' are interpreted}} expected-note{{place parentheses}} b = (p1 <= p2 <= p3); // expected-warning{{comparisons like 'x<y<z' are interpreted}} expected-note{{place parentheses}} etc. etc. | |
130 | I would like to see explicit (and preferably exhaustive or at least systematic) test cases for the "no warning intended" case: if ((p1 < p2) < p3) if (p1 < (p2 < p3)) if (0 <= (p1 < p2)) // this should already trigger a constant-comparison-result warning, yes? I would like to see explicit (and preferably exhaustive or at least systematic) test cases for mixed relational and equality comparisons: if (p1 == p2 < p3) // maybe intentional, but definitely deserving of a warning if (p1 == p2 == p3) // definitely buggy and deserving of a warning if (p1 != p2 != p3) // definitely buggy and deserving of a warning if (p1 < p2 == p3 < p4) // maybe intentional, but definitely deserving of a warning if (p1 == p2 < p3 == p4) // definitely buggy and deserving of a warning |
Thank you! I strongly prefer this path forward.
clang/lib/Sema/SemaExpr.cpp | ||
---|---|---|
14010 | Should we also suggest the fix to rewrite into what user likely intended? |
Thank you for the comments @lebedev.ri and @Quuxplusone. I'll abandon the tidy approach (https://reviews.llvm.org/D84898) and work towards satisfying these review comments (and any others), driving towards acceptance. Best!
Address simpler issues brought up during review so far.
Tests to be refactored, and a fixit added.
Thanks for the comments. I posted an update for the simpler issues, working on refactoring the test cases and creating a fixit. Thanks for the good and actionable review comments!
clang/docs/DiagnosticsReference.rst | ||
---|---|---|
2853 | fixed, thank you! | |
9885 | missed that, thank you! | |
clang/include/clang/Basic/DiagnosticSemaKinds.td | ||
6135 | Thanks Arthur. I modeled the warning message after gcc's warning message. We found internally that while gcc detected this, clang did not. See https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#Warning-Options ... Also warn if a comparison like x<=y<=z appears; this is equivalent to (x<=y ? 1 : 0) <= z, which is a different interpretation from that of ordinary mathematical notation. | |
clang/lib/Sema/SemaExpr.cpp | ||
14010 | I'll work on this, post in a next update. Thank you! | |
clang/test/Sema/warn-compare-op-parentheses.c | ||
2 | I'll address in a future update. Thank you! | |
130 | Great suggestions, I'll address in a future update. Thank you! |
clang/test/Misc/warning-wall.c | ||
---|---|---|
100 | oh my, I didn't intend for this to happen. I'll address. |
clang/lib/Sema/SemaExpr.cpp | ||
---|---|---|
14010 | Hi @lebedev.ri , the warning emits a note that says "place parentheses around the '<op>' expression to silence this warning" (see the test cases below). Is this sufficient, or are you looking for something else? |
I believe I've addressed all comments so far. Looks like Arthur suggested some particular cases that are not currently covered, and are not covered by this change since I think addressing those issues are our of scope of my original intent. If this patch is otherwise acceptable, would the reviewers be ok accepting this patch on the condition of creating a bugzilla report to track those issues?
clang/lib/Sema/SemaExpr.cpp | ||
---|---|---|
14010 | https://godbolt.org/z/d1dG1o <source>:3:16: warning: & has lower precedence than ==; == will be evaluated first [-Wparentheses] int y = (x & 1 == 0); ^~~~~~~~ <source>:3:16: note: place parentheses around the '==' expression to silence this warning int y = (x & 1 == 0); ^ ( ) <source>:3:16: note: place parentheses around the & expression to evaluate it first int y = (x & 1 == 0); ^ ( ) For our situation here it would be something like <source>:3:16: warning: chained comparisons like 'x<=y<=z' are interpreted as '(x<=y ? 1 : 0) <= z' [-Wcompare-op-parentheses] int w = (x < y < z); ^~~~~~~~ <source>:3:16: note: place parentheses around the first '<' expression to silence this warning int w = (x < y < z); ^ ( ) <source>:3:16: note: separate the expression into two clauses to give it the mathematical meaning int y = (x < y < z); ^ ( ) && (y ) Watch out that you don't suggest a wrong fixit for (0 <= sideeffect() < 10), though. I seem to recall another warning that recently had a lot of trouble phrasing its fixit in a way that wouldn't invoke UB or change the behavior of the code in corner cases, but I forget the details. |
clang/lib/Sema/SemaExpr.cpp | ||
---|---|---|
14113 | Same precedence as what? |
added prototype fixits for review.
added additional RUN test case.
filed https://bugs.llvm.org/show_bug.cgi?id=47010 for other
warnings improvement post landing of this patch, after lgtm
I added prototype fixits per request by Roman, updated the LIT test, and added an additional RUN line (one for -Wparentheses and one for -Wcompare-op-parentheses). Also filed https://bugs.llvm.org/show_bug.cgi?id=47010 to follow up on the FIXME cases at the bottom of the LIT since they are out of scope for this change. Thanks for the feedback and comments so far, I look forward to driving this change to completion.
Also with a name like compare op parenthesis. It sounds like this would consider == and !=
clang/lib/Sema/SemaExpr.cpp | ||
---|---|---|
14034 | You don't want to insert y but the source code for y |
clang/lib/Sema/SemaExpr.cpp | ||
---|---|---|
14034 | Gotcha, I'll address. |
clang/include/clang/Basic/DiagnosticSemaKinds.td | ||
---|---|---|
6139 | Both in the string and in the identifier, s/seperate/separate/. | |
clang/lib/Sema/SemaExpr.cpp | ||
14010 | My understanding is that option (2) (x <= (y <= z)) is never suggested. It's reasonable not to suggest it, because it is neither "what the compiler sees, elucidated for the human reader" nor "what the programmer meant, corrected for the compiler." However, as it's not suggested, it shouldn't be documented in this comment. | |
14034 | Please make sure to add at least one regression test to catch this (i.e. some test that tests the wording of the fixit and uses a name for the middle term that is not y). I don't know how to test a fixit, but there must be a way. | |
14261 | FWIW, I would s/loosely/such as/. | |
clang/test/Misc/warning-wall.c | ||
86 | My impression is that you (Vincent) are tending toward just adding x == y == z and x <= y == z to this same -Wcompare-op-parentheses. That's also my preference (except that I wish it could be done all together in one big pull request instead of being split up and perhaps deferred). Have we conclusively established that everyone's on board for this? An alternative (but IMHO a poor alternative) would be to have two or three more granular warnings, e.g. -Wrelational-op-parentheses and -Wequality-op-parentheses (and -Wmixed-relational-equality-op-parentheses?!). If we were to foresee that happening, then -Wcompare-op-parentheses would be the wrong name for this warning option today. |
The diagnostic we get for the case of three or more comparison operators here doesn't seem ideal. Perhaps we could do this check as part of the SemaChecking pass over the completed expression rather than doing it as we form the individual comparisons? That way we'll have the contextual information necessary to find the complete chained comparison; we'd also be able to detect the special case where the operator sequence is the operand of an &/|/^ so that we need parentheses in the fixit.
clang/docs/DiagnosticsReference.rst | ||
---|---|---|
1–5 | Please note this :) You can revert the changes to this file; it's auto-generated. (We only check in a version because the clang website infrastructure isn't yet able to generate it on-demand.) | |
clang/include/clang/Basic/DiagnosticSemaKinds.td | ||
6135 | Per our diagnostic best practices (http://clang.llvm.org/docs/InternalsManual.html#the-format-string), don't repeat the code in the diagnostic (or even an analogy of it such as 'x<=y<=z'); instead, consider underlining the chained comparison operators in the snippet. Also keep in mind that the developer will see the diagnostic and the notes together; you can use the notes to help clarify the meaning of the diagnostic: error: chained comparison does not have its mathematical meaning if (a <= b < c) ^~ ~ note: perform two separate comparisons to compare the same operand twice if (a <= b < c) ^ && b note: place parentheses around the first comparison to compare against its 'bool' result and silence this warning if (a <= b < c) ^ ( ) | |
6137 | If you're going to quote the operator here, you should prepend "first" or similar if both operators are the same. | |
6139 | Don't say "intended". We don't know the intent. | |
clang/lib/Sema/SemaExpr.cpp | ||
14008–14010 | Why not also == and != here? a < b == c < d is a perfectly reasonable mathematical equation (modulo the spelling of ==), and it would seem sensible to catch that with the same diagnostic. | |
14031 | I think we should consider omitting the fixit if the y expression is long or has side-effects. | |
14032–14035 | The parentheses here clutter and distract from the point of the fixit (adding the && y). We usually don't need them, and in the cases where we do need them, the parens you added here aren't correct. For example, cond1 && a + b < c < d + e && cond2 doesn't need any parens, and a | b < c < d needs parens around the entire rewritten b < c && c < d expression, not around the individual terms. If you want the rewrite to always be correct, you should add a ( at the start, an ) at the end, and a && y before the second operator (or a y && after the first operator). If you're OK with it being wrong in the weird case of an & / | / ^ operator adjacent to the chained comparison, then no parens are needed at all. But there's no need to parenthesize the individual operands of the &&. |
use source from expression in fixit
s/seperate/separate/
address some chained comparison ambiguities outside of original scope of changes
Thanks for the recent comments. I just pushed a few improvements over the last patch that didn't comprehend latest comments from @rsmith and @Quuxplusone. I'll read through those carefully and address those.
clang/test/Sema/warn-compare-op-parentheses.c | ||
---|---|---|
49 | Not very user friendly warning text.. |
Please note this :)
You can revert the changes to this file; it's auto-generated. (We only check in a version because the clang website infrastructure isn't yet able to generate it on-demand.)