From: Chris Lattner Date: Fri, 16 Apr 2010 23:34:13 +0000 (+0000) Subject: make our existing "switch on bool" warning work for C. Since X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2b334bb3126a67895813e49e6228dad4aec0b4d6;p=clang make our existing "switch on bool" warning work for C. Since the result of comparisons are 'int' in C, it doesn't work to test just the result type of the expression. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@101576 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/Expr.h b/include/clang/AST/Expr.h index 68ca2680e9..88e87812d1 100644 --- a/include/clang/AST/Expr.h +++ b/include/clang/AST/Expr.h @@ -199,6 +199,12 @@ public: /// \brief Returns whether this expression refers to a vector element. bool refersToVectorElement() const; + /// isKnownToHaveBooleanValue - Return true if this is an integer expression + /// that is known to return 0 or 1. This happens for _Bool/bool expressions + /// but also int expressions which are produced by things like comparisons in + /// C. + bool isKnownToHaveBooleanValue() const; + /// isIntegerConstantExpr - Return true if this expression is a valid integer /// constant expression, and, if so, return its value in Result. If not a /// valid i-c-e, return false and fill in Loc (if specified) with the location @@ -304,7 +310,7 @@ public: /// its subexpression. If that subexpression is also a ParenExpr, /// then this method recursively returns its subexpression, and so forth. /// Otherwise, the method returns the current Expr. - Expr* IgnoreParens(); + Expr *IgnoreParens(); /// IgnoreParenCasts - Ignore parentheses and casts. Strip off any ParenExpr /// or CastExprs, returning their operand. @@ -333,7 +339,7 @@ public: /// temporary object. const Expr *getTemporaryObject() const; - const Expr* IgnoreParens() const { + const Expr *IgnoreParens() const { return const_cast(this)->IgnoreParens(); } const Expr *IgnoreParenCasts() const { diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 85ca9e9fbb..69c12398af 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -2782,7 +2782,7 @@ def err_default_not_in_switch : Error< "'default' statement not in switch statement">; def err_case_not_in_switch : Error<"'case' statement not in switch statement">; def warn_bool_switch_condition : Warning< - "switch condition is a bool">; + "switch condition has boolean value">; def warn_case_value_overflow : Warning< "overflow converting case value to switch condition type (%0 to %1)">, InGroup>; diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 72359c245a..7c715bd3c3 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -27,6 +27,65 @@ #include using namespace clang; +/// isKnownToHaveBooleanValue - Return true if this is an integer expression +/// that is known to return 0 or 1. This happens for _Bool/bool expressions +/// but also int expressions which are produced by things like comparisons in +/// C. +bool Expr::isKnownToHaveBooleanValue() const { + // If this value has _Bool type, it is obvious 0/1. + if (getType()->isBooleanType()) return true; + // If this is a non-scalar-integer type, we don't care enough to try. + if (!getType()->isIntegralType()) return false; + + if (const ParenExpr *PE = dyn_cast(this)) + return PE->getSubExpr()->isKnownToHaveBooleanValue(); + + if (const UnaryOperator *UO = dyn_cast(this)) { + switch (UO->getOpcode()) { + case UnaryOperator::Plus: + case UnaryOperator::Extension: + return UO->getSubExpr()->isKnownToHaveBooleanValue(); + default: + return false; + } + } + + if (const CastExpr *CE = dyn_cast(this)) + return CE->getSubExpr()->isKnownToHaveBooleanValue(); + + if (const BinaryOperator *BO = dyn_cast(this)) { + switch (BO->getOpcode()) { + default: return false; + case BinaryOperator::LT: // Relational operators. + case BinaryOperator::GT: + case BinaryOperator::LE: + case BinaryOperator::GE: + case BinaryOperator::EQ: // Equality operators. + case BinaryOperator::NE: + case BinaryOperator::LAnd: // AND operator. + case BinaryOperator::LOr: // Logical OR operator. + return true; + + case BinaryOperator::And: // Bitwise AND operator. + case BinaryOperator::Xor: // Bitwise XOR operator. + case BinaryOperator::Or: // Bitwise OR operator. + // Handle things like (x==2)|(y==12). + return BO->getLHS()->isKnownToHaveBooleanValue() && + BO->getRHS()->isKnownToHaveBooleanValue(); + + case BinaryOperator::Comma: + case BinaryOperator::Assign: + return BO->getRHS()->isKnownToHaveBooleanValue(); + } + } + + if (const ConditionalOperator *CO = dyn_cast(this)) + return CO->getTrueExpr()->isKnownToHaveBooleanValue() && + CO->getFalseExpr()->isKnownToHaveBooleanValue(); + + return false; +} + //===----------------------------------------------------------------------===// // Primary Expressions. //===----------------------------------------------------------------------===// diff --git a/lib/Sema/SemaStmt.cpp b/lib/Sema/SemaStmt.cpp index 74c64d3881..cb55393259 100644 --- a/lib/Sema/SemaStmt.cpp +++ b/lib/Sema/SemaStmt.cpp @@ -593,7 +593,7 @@ Sema::ActOnFinishSwitchStmt(SourceLocation SwitchLoc, StmtArg Switch, return StmtError(); } - if (CondTypeBeforePromotion->isBooleanType()) { + if (CondExpr->isKnownToHaveBooleanValue()) { // switch(bool_expr) {...} is often a programmer error, e.g. // switch(n && mask) { ... } // Doh - should be "n & mask". // One can always use an if statement instead of switch(bool_expr). diff --git a/test/Sema/statements.c b/test/Sema/statements.c index 52b9c7543d..e3c41f3e1a 100644 --- a/test/Sema/statements.c +++ b/test/Sema/statements.c @@ -41,3 +41,13 @@ void test11(int bit) { { } } + +// rdar://3271964 +enum Numbers { kOne, kTwo, kThree, kFour}; +int test12(enum Numbers num) { + switch (num == kOne) {// expected-warning {{switch condition has boolean value}} + default: + case kThree: + break; + } +} \ No newline at end of file