aboutsummaryrefslogtreecommitdiff
path: root/lib/Sema/SemaStmt.cpp
diff options
context:
space:
mode:
authorChandler Carruth <chandlerc@gmail.com>2011-08-17 08:38:04 +0000
committerChandler Carruth <chandlerc@gmail.com>2011-08-17 08:38:04 +0000
commit9d8eb3b2a892697aed332f6c318a8554fc2623ce (patch)
treeede9eecb3c4cbd376fc29beceea9ffe048e30c4a /lib/Sema/SemaStmt.cpp
parent8a01087664cc78c0572c985949b9ebf3c184dbee (diff)
Introduce a new warning, -Wtop-level-comparison. This warning is
a complement to the warnings we provide in condition expressions. Much like we warn on conditions such as: int x, y; ... if (x = y) ... // Almost always a typo of '==' This warning applies the complementary logic to "top-level" statements, or statements whose value is not consumed or used in some way: int x, y; ... x == y; // Almost always a type for '=' We also mirror the '!=' vs. '|=' logic. The warning is designed to fire even for overloaded operators for two reasons: 1) Especially in the presence of widespread templates that assume operator== and operator!= perform the expected comparison operations, it seems unreasonable to suppress warnings on the offchance that a user has written a class that abuses these operators, embedding side-effects or other magic within them. 2) There is a trivial source modification to silence the warning for truly exceptional cases: (void)(x == y); // No warning A (greatly reduced) form of this warning has already caught a number of bugs in our codebase, so there is precedent for it actually firing. That said, its currently off by default, but enabled under -Wall. There are several fixmes left here that I'm working on in follow-up patches, including de-duplicating warnings from -Wunused, sharing code with -Wunused's implementation (and creating a nice place to hook diagnostics on "top-level" statements), and handling cases where a proxy object with a bool conversion is returned, hiding the operation in the cleanup AST nodes. Suggestions for any of this code more than welcome. Also, I'd really love suggestions for better naming than "top-level". git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@137819 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib/Sema/SemaStmt.cpp')
-rw-r--r--lib/Sema/SemaStmt.cpp101
1 files changed, 101 insertions, 0 deletions
diff --git a/lib/Sema/SemaStmt.cpp b/lib/Sema/SemaStmt.cpp
index 6e814e53bd..79ae67210f 100644
--- a/lib/Sema/SemaStmt.cpp
+++ b/lib/Sema/SemaStmt.cpp
@@ -92,6 +92,103 @@ void Sema::ActOnForEachDeclStmt(DeclGroupPtrTy dg) {
}
}
+/// \brief Diagnose '==' and '!=' in top-level statements as likely
+/// typos for '=' or '|=' (resp.).
+///
+/// This function looks through common stand-alone statements to dig out the
+/// substatement of interest. It should be viable to call on any direct member
+/// of a CompoundStmt.
+///
+/// Adding a cast to void (or other expression wrappers) will prevent the
+/// warning from firing.
+static void DiagnoseTopLevelComparison(Sema &S, const Stmt *Statement) {
+ if (!Statement) return;
+ const Expr *E = dyn_cast<Expr>(Statement);
+ if (!E) {
+ // Descend to sub-statements where they remain "top-level" in that they're
+ // unused.
+ switch (Statement->getStmtClass()) {
+ default:
+ // Skip statements which don't have a direct substatement of interest.
+ // Compound statements are handled by the caller.
+ return;
+
+ case Stmt::CaseStmtClass:
+ case Stmt::DefaultStmtClass:
+ DiagnoseTopLevelComparison(S, cast<SwitchCase>(Statement)->getSubStmt());
+ return;
+ case Stmt::LabelStmtClass:
+ DiagnoseTopLevelComparison(S, cast<LabelStmt>(Statement)->getSubStmt());
+ return;
+
+ case Stmt::DoStmtClass:
+ DiagnoseTopLevelComparison(S, cast<DoStmt>(Statement)->getBody());
+ return;
+ case Stmt::IfStmtClass: {
+ const IfStmt *If = cast<IfStmt>(Statement);
+ DiagnoseTopLevelComparison(S, If->getThen());
+ DiagnoseTopLevelComparison(S, If->getElse());
+ return;
+ }
+ case Stmt::ForStmtClass: {
+ const ForStmt *ForLoop = cast<ForStmt>(Statement);
+ DiagnoseTopLevelComparison(S, ForLoop->getInit());
+ DiagnoseTopLevelComparison(S, ForLoop->getInc());
+ DiagnoseTopLevelComparison(S, ForLoop->getBody());
+ return;
+ }
+ case Stmt::SwitchStmtClass:
+ DiagnoseTopLevelComparison(S, cast<SwitchStmt>(Statement)->getBody());
+ return;
+ case Stmt::WhileStmtClass:
+ DiagnoseTopLevelComparison(S, cast<WhileStmt>(Statement)->getBody());
+ return;
+ }
+ }
+
+ SourceLocation Loc;
+ bool IsNotEqual = false;
+
+ if (const BinaryOperator *Op = dyn_cast<BinaryOperator>(E)) {
+ if (Op->getOpcode() != BO_EQ && Op->getOpcode() != BO_NE)
+ return;
+
+ IsNotEqual = Op->getOpcode() == BO_NE;
+ Loc = Op->getOperatorLoc();
+ } else if (const CXXOperatorCallExpr *Op = dyn_cast<CXXOperatorCallExpr>(E)) {
+ if (Op->getOperator() != OO_EqualEqual &&
+ Op->getOperator() != OO_ExclaimEqual)
+ return;
+
+ IsNotEqual = Op->getOperator() == OO_ExclaimEqual;
+ Loc = Op->getOperatorLoc();
+ } else {
+ // Not a typo-prone comparison.
+ return;
+ }
+
+ // Suppress warnings when the operator, suspicious as it may be, comes from
+ // a macro expansion.
+ if (Loc.isMacroID())
+ return;
+
+ S.Diag(Loc, diag::warn_comparison_top_level_stmt)
+ << (unsigned)IsNotEqual << E->getSourceRange();
+
+ SourceLocation Open = E->getSourceRange().getBegin();
+ SourceLocation Close = S.PP.getLocForEndOfToken(E->getSourceRange().getEnd());
+ S.Diag(Loc, diag::note_top_level_comparison_void_cast_silence)
+ << FixItHint::CreateInsertion(Open, "(void)(")
+ << FixItHint::CreateInsertion(Close, ")");
+
+ if (IsNotEqual)
+ S.Diag(Loc, diag::note_inequality_comparison_to_or_assign)
+ << FixItHint::CreateReplacement(Loc, "|=");
+ else
+ S.Diag(Loc, diag::note_equality_comparison_to_assign)
+ << FixItHint::CreateReplacement(Loc, "=");
+}
+
void Sema::DiagnoseUnusedExprResult(const Stmt *S) {
if (const LabelStmt *Label = dyn_cast_or_null<LabelStmt>(S))
return DiagnoseUnusedExprResult(Label->getSubStmt());
@@ -200,6 +297,10 @@ Sema::ActOnCompoundStmt(SourceLocation L, SourceLocation R,
if (isStmtExpr && i == NumElts - 1)
continue;
+ // FIXME: It'd be nice to not show both of these. The first is a more
+ // precise (and more likely to be enabled) warning. We should suppress the
+ // second when the first fires.
+ DiagnoseTopLevelComparison(*this, Elts[i]);
DiagnoseUnusedExprResult(Elts[i]);
}