-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[clang] Added warn-assignment-bool-context #115234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
e819360
2a06e37
419fb2a
303a982
b72f7a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -687,6 +687,48 @@ void Sema::diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E) { | |
| << FixItHint::CreateReplacement(E->getSourceRange(), "nullptr"); | ||
| } | ||
|
|
||
| void Sema::DiagnoseAssignmentBoolContext(Expr *E, QualType Ty) { | ||
| // Use copy to not alter original expression. | ||
|
||
| Expr *ECopy = E; | ||
|
|
||
| if (Ty->isBooleanType()) { | ||
| // `bool(x=0)` and if (x=0){} emit: | ||
| // - ImplicitCastExpr bool IntegralToBoolean | ||
| // -- ImplicitCastExpr int LValueToRValue | ||
| // --- Assignment ... | ||
| // But should still emit this warning (at least gcc does), even if bool-cast | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what the question is here, can you clarify? ALSO ALSO, if this ends up in C++ anywhere, you'll have some user-defined operators to mess with as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Basically just asking if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
How can I check for that? Do you have a more robust approach?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
None I can think of? We might end up having a case where we don't properly warn if we miss anything here, which is perhaps acceptable.
Having this happen on the implicit cast makes this a little awkward since we can't record 'what is going on here' as we go. That said, I think punting /not warning in the case of user-defined-operators is probably OK, that will prevent us from diagnosing in the cases of some DSLs or something, which is the behavior we probably want? |
||
| // is not directly followed by assignment. | ||
| // NOTE: Is this robust enough or can there be other semantic expression | ||
| // until the assignment? | ||
| while (ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(ECopy)) { | ||
| // If there is another implicit cast to bool then this warning would have | ||
| // been already emitted. | ||
| if (ICE->getType()->isBooleanType()) | ||
| return; | ||
| ECopy = ICE->getSubExpr(); | ||
| } | ||
|
|
||
| if (BinaryOperator *Op = dyn_cast<BinaryOperator>(ECopy)) { | ||
| // Should only be issued for regular assignment `=`, | ||
| // not for compound-assign like `+=`. | ||
| // NOTE: Might make sense to emit for all assignments even if gcc | ||
| // only does for regular assignment. | ||
| if (Op->getOpcode() == BO_Assign) { | ||
| SourceLocation Loc = Op->getOperatorLoc(); | ||
| Diag(Loc, diag::warn_assignment_bool_context) | ||
| << ECopy->getSourceRange(); | ||
|
|
||
| SourceLocation Open = ECopy->getBeginLoc(); | ||
| SourceLocation Close = | ||
| getLocForEndOfToken(ECopy->getSourceRange().getEnd()); | ||
| Diag(Loc, diag::note_condition_assign_silence) | ||
| << FixItHint::CreateInsertion(Open, "(") | ||
| << FixItHint::CreateInsertion(Close, ")"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// ImpCastExprToType - If Expr is not of type 'Type', insert an implicit cast. | ||
| /// If there is already an implicit cast, merge into the existing one. | ||
| /// The result is of the given category. | ||
|
|
@@ -761,6 +803,17 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty, | |
| } | ||
| } | ||
|
|
||
| // FIXME: Doesn't include C89, so this warning isn't emitted when passing | ||
| // `std=c89`. | ||
| auto isC = getLangOpts().C99 || getLangOpts().C11 || getLangOpts().C17 || | ||
|
||
| getLangOpts().C23; | ||
| // Do not emit this warning for Objective-C, since it's a common idiom. | ||
| // NOTE: Are there other languages that this could affect besides C and C++? | ||
| // Ideally would check `getLangOpts().Cplusplus || getLangOpts().C` but there | ||
| // is no option for C (only C99 etc.). | ||
| if ((getLangOpts().CPlusPlus || isC) && !getLangOpts().ObjC) | ||
| DiagnoseAssignmentBoolContext(E, Ty); | ||
|
|
||
| if (ImplicitCastExpr *ImpCast = dyn_cast<ImplicitCastExpr>(E)) { | ||
| if (ImpCast->getCastKind() == Kind && (!BasePath || BasePath->empty())) { | ||
| ImpCast->setType(Ty); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20287,7 +20287,11 @@ void Sema::DiagnoseEqualityWithExtraParens(ParenExpr *ParenE) { | |
|
|
||
| ExprResult Sema::CheckBooleanCondition(SourceLocation Loc, Expr *E, | ||
| bool IsConstexpr) { | ||
| DiagnoseAssignmentAsCondition(E); | ||
| // This warning is already covered by `warn_assignment_bool_context` in C++. | ||
| // NOTE: Ideally both warnings would be combined | ||
| if (!getLangOpts().CPlusPlus || getLangOpts().ObjC) | ||
|
||
| DiagnoseAssignmentAsCondition(E); | ||
|
|
||
| if (ParenExpr *parenE = dyn_cast<ParenExpr>(E)) | ||
| DiagnoseEqualityWithExtraParens(parenE); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // RUN: %clang_cc1 -x c -fsyntax-only -Wparentheses -verify %s | ||
|
|
||
| // NOTE: Don't know if tests allow includes. | ||
| #include <stdbool.h> | ||
|
||
|
|
||
| // Do not emit the warning for compound-assignments. | ||
| bool f(int x) { return x = 0; } // expected-warning {{suggest parentheses around assignment used as truth value}}\ | ||
| // expected-note{{place parentheses around the assignment to silence this warning}} | ||
| bool f2(int x) { return x += 0; } | ||
|
|
||
| bool f3(bool x) { return x = 0; } | ||
|
|
||
| void test() { | ||
| int x; | ||
|
|
||
| // This should emit the `warn_condition_is_assignment` warning, since | ||
| // C doesn't do implicit conversion booleans for conditions. | ||
| if (x = 0) {} // expected-warning {{using the result of an assignment as a condition without parentheses}} \ | ||
| // expected-note{{place parentheses around the assignment to silence this warning}}\ | ||
| // expected-note{{use '==' to turn this assignment into an equality comparison}} | ||
| if (x = 4 && x){} // expected-warning {{using the result of an assignment as a condition without parentheses}} \ | ||
| // expected-note{{place parentheses around the assignment to silence this warning}}\ | ||
| // expected-note{{use '==' to turn this assignment into an equality comparison}} | ||
|
|
||
| (void)(bool)(x = 1); | ||
| (void)(bool)(int)(x = 1); | ||
|
|
||
|
|
||
| bool _a = x = 3; // expected-warning {{suggest parentheses around assignment used as truth value}}\ | ||
| // expected-note{{place parentheses around the assignment to silence this warning}} | ||
|
|
||
| // Shouldn't warn for above cases if parentheses were provided. | ||
| if ((x = 0)) {} | ||
| bool _b = (x = 3); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||
| // RUN: %clang_cc1 -x c++ -fsyntax-only -Wparentheses -verify %s | ||||||
|
|
||||||
| // Do not emit the warning for compound-assignments. | ||||||
| bool f(int x) { return x = 0; } // expected-warning {{suggest parentheses around assignment used as truth value}} \ | ||||||
| // expected-note{{place parentheses around the assignment to silence this warning}} | ||||||
| bool f2(int x) { return x += 0; } | ||||||
|
|
||||||
| bool f3(bool x) { return x = 0; } | ||||||
|
|
||||||
| void test() { | ||||||
| int x; | ||||||
|
|
||||||
| // Assignemnts inside of conditions should still emit the more specific `warn_condition_is_assignment` warning. | ||||||
|
||||||
| // Assignemnts inside of conditions should still emit the more specific `warn_condition_is_assignment` warning. | |
| // Assignments inside of conditions should still emit the more specific `warn_condition_is_assignment` warning. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This diagnostic wording doesn't really make sense, it needs to tell the person what is wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize this is the C diagnostic text, but I'm still not a huge fan. The text in
warn_condition_is_idiomatic_assignmentis pretty close to what we want, right? Perhaps we should use that? Also, emittingnote_condition_assign_silencemight be sensible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but maybe just having the more generic
warn_assignment_bool_contextwith a message like "using the result of an assignment as a truth value without parentheses" which can be emitted during conditions as well. Since this implicit cast can also happen outside of conditions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that is already being submitted in
DiagnoseAssignmentBoolContext()