Index: include/clang/AST/ExprCXX.h =================================================================== --- include/clang/AST/ExprCXX.h +++ include/clang/AST/ExprCXX.h @@ -1940,7 +1940,7 @@ /// has a non-throwing exception-specification. The '03 rule is /// identical except that the definition of a non-throwing /// exception specification is just "is it throw()?". - bool shouldNullCheckAllocation(const ASTContext &Ctx) const; + bool shouldNullCheckAllocation() const; FunctionDecl *getOperatorNew() const { return OperatorNew; } void setOperatorNew(FunctionDecl *D) { OperatorNew = D; } Index: include/clang/ASTMatchers/ASTMatchers.h =================================================================== --- include/clang/ASTMatchers/ASTMatchers.h +++ include/clang/ASTMatchers/ASTMatchers.h @@ -5973,6 +5973,18 @@ InnerMatcher.matches(*Node.getArraySize(), Finder, Builder); } +/// Matches nothrow new expressions. +/// +/// Given: +/// \code +/// MyClass *p1 = new MyClass; +/// \endcode +/// cxxNewExpr(shouldNullCheckAllocation()) +/// matches the expression 'new (std::nothrow) MyClass'. +AST_MATCHER(CXXNewExpr, shouldNullCheckAllocation) { + return Node.shouldNullCheckAllocation(); +} + /// Matches a class declaration that is defined. /// /// Example matches x (matcher = cxxRecordDecl(hasDefinition())) Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -346,6 +346,11 @@ def DeadStoresChecker : Checker<"DeadStores">, HelpText<"Check for values stored to variables that are never read afterwards">, DescFile<"DeadStoresChecker.cpp">; + +def DeadStatusChecker : Checker<"DeadStatus">, + HelpText<"Check for impossible status">, + DescFile<"DeadStatusChecker.cpp">; + } // end DeadCode let ParentPackage = DeadCodeAlpha in { Index: lib/AST/ExprCXX.cpp =================================================================== --- lib/AST/ExprCXX.cpp +++ lib/AST/ExprCXX.cpp @@ -168,7 +168,7 @@ SubExprs = new (C) Stmt*[TotalSize]; } -bool CXXNewExpr::shouldNullCheckAllocation(const ASTContext &Ctx) const { +bool CXXNewExpr::shouldNullCheckAllocation() const { return getOperatorNew()->getType()->castAs() ->isNothrow() && !getOperatorNew()->isReservedGlobalPlacementOperator(); Index: lib/ASTMatchers/Dynamic/Registry.cpp =================================================================== --- lib/ASTMatchers/Dynamic/Registry.cpp +++ lib/ASTMatchers/Dynamic/Registry.cpp @@ -435,6 +435,7 @@ REGISTER_MATCHER(returns); REGISTER_MATCHER(returnStmt); REGISTER_MATCHER(rValueReferenceType); + REGISTER_MATCHER(shouldNullCheckAllocation); REGISTER_MATCHER(sizeOfExpr); REGISTER_MATCHER(specifiesNamespace); REGISTER_MATCHER(specifiesType); Index: lib/CodeGen/CGExprCXX.cpp =================================================================== --- lib/CodeGen/CGExprCXX.cpp +++ lib/CodeGen/CGExprCXX.cpp @@ -1649,7 +1649,7 @@ // function is allowed to return null (because it has a non-throwing // exception spec or is the reserved placement new) and we have an // interesting initializer. - bool nullCheck = E->shouldNullCheckAllocation(getContext()) && + bool nullCheck = E->shouldNullCheckAllocation() && (!allocType.isPODType(getContext()) || E->hasInitializer()); llvm::BasicBlock *nullCheckBB = nullptr; Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -27,6 +27,7 @@ CloneChecker.cpp ConversionChecker.cpp CXXSelfAssignmentChecker.cpp + DeadStatusChecker.cpp DeadStoresChecker.cpp DebugCheckers.cpp DeleteWithNonVirtualDtorChecker.cpp Index: lib/StaticAnalyzer/Checkers/DeadStatusChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/DeadStatusChecker.cpp @@ -0,0 +1,140 @@ +//==- DeadStatusChecker.cpp - Check for impossible status -*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a DeadStatus, a checker that looks for impossible +// status checks. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { +class DeadStatusChecker : public Checker { + class Walker : public RecursiveASTVisitor { + BugReporter &BR; + const DeadStatusChecker *Checker; + AnalysisDeclContext *AC; + llvm::SmallPtrSet throwingNews; + + const Expr *hasNullptr(const BinaryOperator *BO) const { + const Expr *ret = nullptr; + const Expr *E[2] = {BO->getLHS()->IgnoreParenCasts(), + BO->getRHS()->IgnoreParenCasts()}; + const Expr *EO[2] = {E[1], E[0]}; + for (int i = 0; i < 2; i++) { + if (isa(E[i]) || isa(E[i]) || + (isa(E[i]) && + dyn_cast(E[i])->getValue() == 0)) { + ret = EO[i]; + break; + } + } + return ret; + } + + const Expr *hasThrowingNew(const BinaryOperator *BO) const { + const Expr *ret = nullptr; + const Expr *rhs = BO->getRHS()->IgnoreParenCasts(); + if (isa(rhs)) { + auto NE = static_cast(rhs); + if (!NE->shouldNullCheckAllocation()) { + ret = BO->getLHS()->IgnoreParenCasts(); + } + } + return ret; + } + + bool hasThrowingNew(const VarDecl *VD) const { + bool ret = false; + const Expr *E = VD->getInit(); + if (E != nullptr) { + E = E->IgnoreParenCasts(); + if (isa(E)) { + auto NE = static_cast(E); + if (!NE->shouldNullCheckAllocation()) { + ret = true; + } + } + } + return ret; + } + + public: + explicit Walker(BugReporter &BR, const DeadStatusChecker *C, + AnalysisDeclContext *AC) + : BR(BR), Checker(C), AC(AC) {} + bool VisitBinaryOperator(const BinaryOperator *BO) { + const Expr *E = nullptr; + BinaryOperator::Opcode Op = BO->getOpcode(); + if (BinaryOperator::isEqualityOp(Op)) { + E = hasNullptr(BO); + } else if (BinaryOperator::isAssignmentOp(Op)) { + E = hasThrowingNew(BO); + } + if (E != nullptr) { + if (isa(E)) { + auto DR = static_cast(E); + if (DR->getType()->isPointerType()) { + auto D = dyn_cast(DR->getDecl()); + if (D) { + if (BinaryOperator::isEqualityOp(Op)) { + if (throwingNews.count(D)) { + auto DSC = dyn_cast(Checker); + if (DSC != nullptr) + DSC->reportThrowingNewStatusBug(BR, AC, BO, D); + } + } else { + // assignment + throwingNews.insert(D); + } + } + } + } + } + return true; + } + bool VisitVarDecl(const VarDecl *D) { + if (hasThrowingNew(D)) + throwingNews.insert(D); + return true; + } + }; + + void reportThrowingNewStatusBug(BugReporter &BR, AnalysisDeclContext *AC, + const BinaryOperator *BO, + const VarDecl *D) const { + PathDiagnosticLocation L = + PathDiagnosticLocation::createBegin(BO, BR.getSourceManager(), AC); + + BR.EmitBasicReport(D, this, "Dead nullptr check", categories::LogicError, + "This variable can never be a nullptr", L, + BO->getSourceRange()); + } + +public: + DeadStatusChecker() {} + void checkASTCodeBody(const Decl *D, AnalysisManager &M, + BugReporter &BR) const { + Walker V(BR, this, M.getAnalysisDeclContext(D)); + V.TraverseDecl(const_cast(D)); + } +}; +} // namespace + +void ento::registerDeadStatusChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} Index: test/Analysis/dead-status-new-nullptr.cpp =================================================================== --- /dev/null +++ test/Analysis/dead-status-new-nullptr.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=deadcode.DeadStatus -verify %s + +void *t1(unsigned n, bool *fail); +void *t1(unsigned n, bool *fail) { + char *m = new char[n]; + if (nullptr == m) // expected-warning{{This variable can never be a nullptr}} + *fail = true; + else + *fail = false; + return m; +} + +void *t2(unsigned n, bool *fail); +void *t2(unsigned n, bool *fail) { + char *m = new char[n]; + if (m == nullptr) // expected-warning{{This variable can never be a nullptr}} + *fail = true; + else + *fail = false; + return m; +} + +// a variant of nullptr +void *t3(unsigned n, bool *fail); +void *t3(unsigned n, bool *fail) { + char *m = new char[n]; + if (m == 0) // expected-warning{{This variable can never be a nullptr}} + *fail = true; + else + *fail = false; + return m; +} + +// a variant of nullptr +#define NULL __null +void *t4(unsigned n, bool *fail); +void *t4(unsigned n, bool *fail) { + char *m = new char[n]; + if (m == NULL) // expected-warning{{This variable can never be a nullptr}} + *fail = true; + else + *fail = false; + return m; +}