From: Ryan Govostes Date: Sat, 11 Feb 2012 16:32:09 +0000 (+0000) Subject: [analyzer] New checker for assignment of non-0/1 values to Boolean variables. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b141b285d17934a08d1cb0f5f0a5a4d65b2caab2;p=clang [analyzer] New checker for assignment of non-0/1 values to Boolean variables. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@150306 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp new file mode 100644 index 0000000000..a4fc396c3e --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -0,0 +1,157 @@ +//== BoolAssignmentChecker.cpp - Boolean assignment checker -----*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This defines BoolAssignmentChecker, a builtin check in ExprEngine that +// performs checks for assignment of non-Boolean values to Boolean variables. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" + +using namespace clang; +using namespace ento; + +namespace { + class BoolAssignmentChecker : public Checker< check::Bind > { + mutable llvm::OwningPtr BT; + void emitReport(ProgramStateRef state, CheckerContext &C) const; + public: + void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const; + }; +} // end anonymous namespace + +void BoolAssignmentChecker::emitReport(ProgramStateRef state, + CheckerContext &C) const { + if (ExplodedNode *N = C.addTransition(state)) { + if (!BT) + BT.reset(new BuiltinBug("Assignment of a non-Boolean value")); + C.EmitReport(new BugReport(*BT, BT->getDescription(), N)); + } +} + +static bool isBooleanType(QualType Ty) { + if (Ty->isBooleanType()) // C++ or C99 + return true; + + if (const TypedefType *TT = Ty->getAs()) + return TT->getDecl()->getName() == "BOOL" || // Objective-C + TT->getDecl()->getName() == "_Bool" || // stdbool.h < C99 + TT->getDecl()->getName() == "Boolean"; // MacTypes.h + + return false; +} + +void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, + CheckerContext &C) const { + + // We are only interested in stores into Booleans. + const TypedValueRegion *TR = + dyn_cast_or_null(loc.getAsRegion()); + + if (!TR) + return; + + QualType valTy = TR->getValueType(); + + if (!isBooleanType(valTy)) + return; + + // Get the value of the right-hand side. We only care about values + // that are defined (UnknownVals and UndefinedVals are handled by other + // checkers). + const DefinedSVal *DV = dyn_cast(&val); + if (!DV) + return; + + // Check if the assigned value meets our criteria for correctness. It must + // be a value that is either 0 or 1. One way to check this is to see if + // the value is possibly < 0 (for a negative value) or greater than 1. + ProgramStateRef state = C.getState(); + SValBuilder &svalBuilder = C.getSValBuilder(); + ConstraintManager &CM = C.getConstraintManager(); + + // First, ensure that the value is >= 0. + DefinedSVal zeroVal = svalBuilder.makeIntVal(0, valTy); + SVal greaterThanOrEqualToZeroVal = + svalBuilder.evalBinOp(state, BO_GE, *DV, zeroVal, + svalBuilder.getConditionType()); + + DefinedSVal *greaterThanEqualToZero = + dyn_cast(&greaterThanOrEqualToZeroVal); + + if (!greaterThanEqualToZero) { + // The SValBuilder cannot construct a valid SVal for this condition. + // This means we cannot properly reason about it. + return; + } + + ProgramStateRef stateLT, stateGE; + llvm::tie(stateGE, stateLT) = CM.assumeDual(state, *greaterThanEqualToZero); + + // Is it possible for the value to be less than zero? + if (stateLT) { + // It is possible for the value to be less than zero. We only + // want to emit a warning, however, if that value is fully constrained. + // If it it possible for the value to be >= 0, then essentially the + // value is underconstrained and there is nothing left to be done. + if (!stateGE) + emitReport(stateLT, C); + + // In either case, we are done. + return; + } + + // If we reach here, it must be the case that the value is constrained + // to only be >= 0. + assert(stateGE == state); + + // At this point we know that the value is >= 0. + // Now check to ensure that the value is <= 1. + DefinedSVal OneVal = svalBuilder.makeIntVal(1, valTy); + SVal lessThanEqToOneVal = + svalBuilder.evalBinOp(state, BO_LE, *DV, OneVal, + svalBuilder.getConditionType()); + + DefinedSVal *lessThanEqToOne = + dyn_cast(&lessThanEqToOneVal); + + if (!lessThanEqToOne) { + // The SValBuilder cannot construct a valid SVal for this condition. + // This means we cannot properly reason about it. + return; + } + + ProgramStateRef stateGT, stateLE; + llvm::tie(stateLE, stateGT) = CM.assumeDual(state, *lessThanEqToOne); + + // Is it possible for the value to be greater than one? + if (stateGT) { + // It is possible for the value to be greater than one. We only + // want to emit a warning, however, if that value is fully constrained. + // If it is possible for the value to be <= 1, then essentially the + // value is underconstrained and there is nothing left to be done. + if (!stateLE) + emitReport(stateGT, C); + + // In either case, we are done. + return; + } + + // If we reach here, it must be the case that the value is constrained + // to only be <= 1. + assert(stateLE == state); +} + +void ento::registerBoolAssignmentChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index dc57a4cc36..096e3f32ea 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -12,6 +12,7 @@ add_clang_library(clangStaticAnalyzerCheckers ArrayBoundCheckerV2.cpp AttrNonNullChecker.cpp BasicObjCFoundationChecks.cpp + BoolAssignmentChecker.cpp BuiltinFunctionChecker.cpp CStringChecker.cpp CStringSyntaxChecker.cpp diff --git a/lib/StaticAnalyzer/Checkers/Checkers.td b/lib/StaticAnalyzer/Checkers/Checkers.td index f130b2560c..2bf06a0bf9 100644 --- a/lib/StaticAnalyzer/Checkers/Checkers.td +++ b/lib/StaticAnalyzer/Checkers/Checkers.td @@ -87,6 +87,10 @@ def StackAddrEscapeChecker : Checker<"StackAddressEscape">, let ParentPackage = CoreExperimental in { +def BoolAssignmentChecker : Checker<"BoolAssignment">, + HelpText<"Warn about assigning non-{0,1} values to Boolean variables">, + DescFile<"BoolAssignmentChecker.cpp">; + def CastSizeChecker : Checker<"CastSize">, HelpText<"Check when casting a malloc'ed type T, whether the size is a multiple of the size of T">, DescFile<"CastSizeChecker.cpp">; diff --git a/test/Analysis/bool-assignment.cpp b/test/Analysis/bool-assignment.cpp new file mode 100644 index 0000000000..e573129994 --- /dev/null +++ b/test/Analysis/bool-assignment.cpp @@ -0,0 +1,87 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=core,experimental.core.BoolAssignment -analyzer-store=region -verify %s + +// Test C++'s bool + +void test_cppbool_initialization(int y) { + if (y < 0) { + bool x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + bool x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + bool x = y; // no-warning +} + +void test_cppbool_assignment(int y) { + bool x = 0; // no-warning + if (y < 0) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + x = y; // no-warning +} + +// Test Objective-C's BOOL + +typedef signed char BOOL; + +void test_BOOL_initialization(int y) { + if (y < 0) { + BOOL x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + BOOL x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + BOOL x = y; // no-warning +} + +void test_BOOL_assignment(int y) { + BOOL x = 0; // no-warning + if (y < 0) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + x = y; // no-warning +} + + +// Test MacTypes.h's Boolean + +typedef unsigned char Boolean; + +void test_Boolean_initialization(int y) { + if (y < 0) { + Boolean x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + Boolean x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + Boolean x = y; // no-warning +} + +void test_Boolean_assignment(int y) { + Boolean x = 0; // no-warning + if (y < 0) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + x = y; // no-warning +} diff --git a/test/Analysis/bool-assignment2.c b/test/Analysis/bool-assignment2.c new file mode 100644 index 0000000000..9de26cf349 --- /dev/null +++ b/test/Analysis/bool-assignment2.c @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -std=c99 -analyze -analyzer-checker=core,experimental.core.BoolAssignment -analyzer-store=region -verify %s + +// Test stdbool.h's _Bool + +// Prior to C99, stdbool.h uses this typedef, but even in ANSI C mode, _Bool +// appears to be defined. + +// #if __STDC_VERSION__ < 199901L +// typedef int _Bool; +// #endif + +void test_stdbool_initialization(int y) { + if (y < 0) { + _Bool x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + _Bool x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + _Bool x = y; // no-warning +} + +void test_stdbool_assignment(int y) { + _Bool x = 0; // no-warning + if (y < 0) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + if (y > 1) { + x = y; // expected-warning {{Assignment of a non-Boolean value}} + return; + } + x = y; // no-warning +}