[clang-tidy] Assert related checkers

This patch contains two assert related checkers. These checkers are the part of
those that is being open sourced by Ericsson
(http://lists.cs.uiuc.edu/pipermail/cfe-dev/2014-December/040520.html).

The checkers:

AssertSideEffect:
/// \brief Finds \c assert() with side effect.
///
/// The conition of \c assert() is evaluated only in debug builds so a condition
/// with side effect can cause different behaviour in debug / relesase builds.

StaticAssert:
/// \brief Replaces \c assert() with \c static_assert() if the condition is 
/// evaluatable at compile time.
///
/// The condition of \c static_assert() is evaluated at compile time which is
/// safer and more efficient.

http://reviews.llvm.org/D7375

Patch by Szabolcs Sipos!

llvm-svn: 230943
This commit is contained in:
Alexander Kornienko 2015-03-02 10:46:43 +00:00
parent e662316994
commit 1ca3b83255
8 changed files with 521 additions and 0 deletions

View File

@ -0,0 +1,114 @@
//===--- AssertSideEffectCheck.cpp - clang-tidy ---------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "AssertSideEffectCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include <algorithm>
#include <string>
using namespace clang::ast_matchers;
namespace clang {
namespace ast_matchers {
AST_MATCHER_P(Expr, hasSideEffect, bool, CheckFunctionCalls) {
const Expr *E = &Node;
if (const auto *Op = dyn_cast<UnaryOperator>(E)) {
UnaryOperator::Opcode OC = Op->getOpcode();
return OC == UO_PostInc || OC == UO_PostDec || OC == UO_PreInc ||
OC == UO_PreDec;
}
if (const auto *Op = dyn_cast<BinaryOperator>(E)) {
BinaryOperator::Opcode OC = Op->getOpcode();
return OC == BO_Assign || OC == BO_MulAssign || OC == BO_DivAssign ||
OC == BO_RemAssign || OC == BO_AddAssign || OC == BO_SubAssign ||
OC == BO_ShlAssign || OC == BO_ShrAssign || OC == BO_AndAssign ||
OC == BO_XorAssign || OC == BO_OrAssign;
}
if (const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
OverloadedOperatorKind OpKind = OpCallExpr->getOperator();
return OpKind == OO_Equal || OpKind == OO_PlusEqual ||
OpKind == OO_MinusEqual || OpKind == OO_StarEqual ||
OpKind == OO_SlashEqual || OpKind == OO_AmpEqual ||
OpKind == OO_PipeEqual || OpKind == OO_CaretEqual ||
OpKind == OO_LessLessEqual || OpKind == OO_GreaterGreaterEqual ||
OpKind == OO_PlusPlus || OpKind == OO_MinusMinus ||
OpKind == OO_PercentEqual || OpKind == OO_New ||
OpKind == OO_Delete || OpKind == OO_Array_New ||
OpKind == OO_Array_Delete;
}
if (const auto *CExpr = dyn_cast<CallExpr>(E)) {
bool Result = CheckFunctionCalls;
if (const auto *FuncDecl = CExpr->getDirectCallee())
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FuncDecl))
Result &= !MethodDecl->isConst();
return Result;
}
return isa<CXXNewExpr>(E) || isa<CXXDeleteExpr>(E) || isa<CXXThrowExpr>(E);
}
} // namespace ast_matchers
namespace tidy {
AssertSideEffectCheck::AssertSideEffectCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
CheckFunctionCalls(Options.get("CheckFunctionCalls", false)),
RawAssertList(Options.get("AssertMacros", "assert")) {
StringRef(RawAssertList).split(AssertMacros, ",", -1, false);
}
// The options are explained in AssertSideEffectCheck.h.
void AssertSideEffectCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "CheckFunctionCalls", CheckFunctionCalls);
Options.store(Opts, "AssertMacros", RawAssertList);
}
void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) {
auto ConditionWithSideEffect =
hasCondition(hasDescendant(expr(hasSideEffect(CheckFunctionCalls))));
Finder->addMatcher(
stmt(anyOf(conditionalOperator(ConditionWithSideEffect),
ifStmt(ConditionWithSideEffect))).bind("condStmt"),
this);
}
void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) {
const ASTContext *ASTCtx = Result.Context;
const auto *CondStmt = Result.Nodes.getNodeAs<Stmt>("condStmt");
SourceLocation Loc = CondStmt->getLocStart();
if (!Loc.isValid() || !Loc.isMacroID())
return;
StringRef MacroName = Lexer::getImmediateMacroName(
Loc, ASTCtx->getSourceManager(), ASTCtx->getLangOpts());
// Check if this macro is an assert.
if (std::find(AssertMacros.begin(), AssertMacros.end(), MacroName) ==
AssertMacros.end())
return;
diag(Loc, "found " + MacroName.str() + "() with side effect");
}
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,50 @@
//===--- AssertSideEffectCheck.h - clang-tidy -------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERT_SIDE_EFFECT_CHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERT_SIDE_EFFECT_CHECK_H
#include "../ClangTidy.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <string>
namespace clang {
namespace tidy {
/// \brief Finds \c assert() with side effect.
///
/// The condition of \c assert() is evaluated only in debug builds so a
/// condition with side effect can cause different behaviour in debug / relesase
/// builds.
///
/// There are two options:
/// - AssertMacros: AssertMacros: A comma-separated list of the names of assert
/// macros to be checked.
/// - CheckFunctionCalls: Whether to treat non-const member and non-member
/// functions as they produce side effects. Disabled by default
/// because it can increase the number of false positive warnings.
class AssertSideEffectCheck : public ClangTidyCheck {
public:
AssertSideEffectCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
const bool CheckFunctionCalls;
const std::string RawAssertList;
SmallVector<StringRef, 5> AssertMacros;
};
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERT_SIDE_EFFECT_CHECK_H

View File

@ -2,11 +2,13 @@ set(LLVM_LINK_COMPONENTS support)
add_clang_library(clangTidyMiscModule
ArgumentCommentCheck.cpp
AssertSideEffectCheck.cpp
AssignOperatorSignatureCheck.cpp
BoolPointerImplicitConversion.cpp
InaccurateEraseCheck.cpp
InefficientAlgorithmCheck.cpp
MiscTidyModule.cpp
StaticAssertCheck.cpp
SwappedArgumentsCheck.cpp
UndelegatedConstructor.cpp
UnusedRAII.cpp

View File

@ -11,10 +11,12 @@
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "ArgumentCommentCheck.h"
#include "AssertSideEffectCheck.h"
#include "AssignOperatorSignatureCheck.h"
#include "BoolPointerImplicitConversion.h"
#include "InaccurateEraseCheck.h"
#include "InefficientAlgorithmCheck.h"
#include "StaticAssertCheck.h"
#include "SwappedArgumentsCheck.h"
#include "UndelegatedConstructor.h"
#include "UniqueptrResetRelease.h"
@ -28,6 +30,8 @@ class MiscModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<ArgumentCommentCheck>("misc-argument-comment");
CheckFactories.registerCheck<AssertSideEffectCheck>(
"misc-assert-side-effect");
CheckFactories.registerCheck<AssignOperatorSignatureCheck>(
"misc-assign-operator-signature");
CheckFactories.registerCheck<BoolPointerImplicitConversion>(
@ -36,6 +40,8 @@ public:
"misc-inaccurate-erase");
CheckFactories.registerCheck<InefficientAlgorithmCheck>(
"misc-inefficient-algorithm");
CheckFactories.registerCheck<StaticAssertCheck>(
"misc-static-assert");
CheckFactories.registerCheck<SwappedArgumentsCheck>(
"misc-swapped-arguments");
CheckFactories.registerCheck<UndelegatedConstructorCheck>(

View File

@ -0,0 +1,138 @@
//===--- StaticAssertCheck.cpp - clang-tidy -------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "StaticAssertCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include <string>
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
StaticAssertCheck::StaticAssertCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void StaticAssertCheck::registerMatchers(MatchFinder *Finder) {
auto IsAlwaysFalse = ignoringParenImpCasts(
anyOf(boolLiteral(equals(false)).bind("isAlwaysFalse"),
integerLiteral(equals(0)).bind("isAlwaysFalse")));
auto AssertExprRoot = anyOf(
binaryOperator(
hasOperatorName("&&"),
hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))),
anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalse)), anything()))
.bind("assertExprRoot"),
IsAlwaysFalse);
auto Condition = expr(anyOf(
expr(ignoringParenCasts(anyOf(
AssertExprRoot,
unaryOperator(hasUnaryOperand(ignoringParenCasts(AssertExprRoot)))))),
anything()));
Finder->addMatcher(
stmt(anyOf(conditionalOperator(hasCondition(Condition.bind("condition"))),
ifStmt(hasCondition(Condition.bind("condition")))),
unless(isInTemplateInstantiation())).bind("condStmt"),
this);
}
void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
const ASTContext *ASTCtx = Result.Context;
const LangOptions &Opts = ASTCtx->getLangOpts();
const SourceManager &SM = ASTCtx->getSourceManager();
const auto *CondStmt = Result.Nodes.getNodeAs<Stmt>("condStmt");
const auto *Condition = Result.Nodes.getNodeAs<Expr>("condition");
const auto *IsAlwaysFalse = Result.Nodes.getNodeAs<Expr>("isAlwaysFalse");
const auto *AssertMSG = Result.Nodes.getNodeAs<StringLiteral>("assertMSG");
const auto *AssertExprRoot =
Result.Nodes.getNodeAs<BinaryOperator>("assertExprRoot");
SourceLocation AssertExpansionLoc = CondStmt->getLocStart();
if (!Opts.CPlusPlus11 || !AssertExpansionLoc.isValid() ||
!AssertExpansionLoc.isMacroID())
return;
StringRef MacroName =
Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts);
if (MacroName != "assert" || !Condition->isEvaluatable(*ASTCtx))
return;
// False literal is not the result of macro expansion.
if (IsAlwaysFalse &&
!SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc()).isMacroID())
return;
SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc);
SmallVector<FixItHint, 4> FixItHints;
SourceLocation LastParenLoc;
if (AssertLoc.isValid() && !AssertLoc.isMacroID() &&
(LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) {
FixItHints.push_back(
FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert"));
std::string StaticAssertMSG = ", \"\"";
if (AssertExprRoot) {
FixItHints.push_back(FixItHint::CreateRemoval(
SourceRange(AssertExprRoot->getOperatorLoc())));
FixItHints.push_back(FixItHint::CreateRemoval(
SourceRange(AssertMSG->getLocStart(), AssertMSG->getLocEnd())));
StaticAssertMSG = (Twine(", \"") + AssertMSG->getString() + "\"").str();
}
FixItHints.push_back(
FixItHint::CreateInsertion(LastParenLoc, StaticAssertMSG));
}
diag(AssertLoc, "found assert() that could be replaced by static_assert()")
<< FixItHints;
}
SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx,
SourceLocation AssertLoc) {
const LangOptions &Opts = ASTCtx->getLangOpts();
const SourceManager &SM = ASTCtx->getSourceManager();
llvm::MemoryBuffer *Buffer = SM.getBuffer(SM.getFileID(AssertLoc));
if (!Buffer)
return SourceLocation();
const char *BufferPos = SM.getCharacterData(AssertLoc);
Token Token;
Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts,
Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
// assert first left parenthesis
if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) ||
!Token.is(tok::l_paren))
return SourceLocation();
unsigned int ParenCount = 1;
while (ParenCount && !Lexer.LexFromRawLexer(Token)) {
if (Token.is(tok::l_paren))
++ParenCount;
else if (Token.is(tok::r_paren))
--ParenCount;
}
return Token.getLocation();
}
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,39 @@
//===--- StaticAssertCheck.h - clang-tidy -----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATIC_ASSERT_CHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATIC_ASSERT_CHECK_H
#include "../ClangTidy.h"
#include "llvm/ADT/StringRef.h"
#include <string>
namespace clang {
namespace tidy {
/// \brief Replaces \c assert() with \c static_assert() if the condition is
/// evaluatable at compile time.
///
/// The condition of \c static_assert() is evaluated at compile time which is
/// safer and more efficient.
class StaticAssertCheck : public ClangTidyCheck {
public:
StaticAssertCheck(StringRef Name, ClangTidyContext *Context);
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
SourceLocation getLastParenLoc(const ASTContext *ASTCtx,
SourceLocation AssertLoc);
};
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATIC_ASSERT_CHECK_H

View File

@ -0,0 +1,92 @@
// RUN: $(dirname %s)/check_clang_tidy.sh %s misc-assert-side-effect %t -config="{CheckOptions: [{key: misc-assert-side-effect.CheckFunctionCalls, value: 1}, {key: misc-assert-side-effect.AssertMacros, value: 'assert,my_assert'}]}" --
// REQUIRES: shell
//===--- assert definition block ------------------------------------------===//
int abort() { return 0; }
#ifdef NDEBUG
#define assert(x) 1
#else
#define assert(x) \
if (!(x)) \
(void)abort()
#endif
#ifdef NDEBUG
#define my_assert(x) 1
#else
#define my_assert(x) \
((void)((x) ? 1 : abort()))
#endif
#ifdef NDEBUG
#define not_my_assert(x) 1
#else
#define not_my_assert(x) \
if (!(x)) \
(void)abort()
#endif
//===----------------------------------------------------------------------===//
class MyClass {
public:
bool badFunc(int a, int b) { return a * b > 0; }
bool goodFunc(int a, int b) const { return a * b > 0; }
MyClass &operator=(const MyClass &rhs) { return *this; }
int operator-() { return 1; }
operator bool() const { return true; }
void operator delete(void *p) {}
};
bool freeFunction() {
return true;
}
int main() {
int X = 0;
bool B = false;
assert(X == 1);
assert(X = 1);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect [misc-assert-side-effect]
my_assert(X = 1);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found my_assert() with side effect
not_my_assert(X = 1);
assert(++X);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect
assert(!B);
assert(B || true);
assert(freeFunction());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect
MyClass mc;
assert(mc.badFunc(0, 1));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect
assert(mc.goodFunc(0, 1));
MyClass mc2;
assert(mc2 = mc);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect
assert(-mc > 0);
MyClass *mcp;
assert(mcp = new MyClass);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect
assert((delete mcp, false));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect
assert((throw 1, false));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() with side effect
return 0;
}

View File

@ -0,0 +1,80 @@
// RUN: $(dirname %s)/check_clang_tidy.sh %s misc-static-assert %t
// REQUIRES: shell
void abort() {}
#ifdef NDEBUG
#define assert(x) 1
#else
#define assert(x) \
if (!(x)) \
abort()
#endif
#define ZERO_MACRO 0
#define my_macro() assert(0 == 1)
// CHECK-FIXES: #define my_macro() assert(0 == 1)
constexpr bool myfunc(int a, int b) { return a * b == 0; }
class A {
public:
bool method() { return true; }
};
class B {
public:
constexpr bool method() { return true; }
};
template <class T> void doSomething(T t) {
assert(myfunc(1, 2));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be replaced by static_assert() [misc-static-assert]
// CHECK-FIXES: {{^ }}static_assert(myfunc(1, 2), "");
assert(t.method());
// CHECK-FIXES: {{^ }}assert(t.method());
}
int main() {
my_macro();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be
// CHECK-FIXES: {{^ }}my_macro();
assert(myfunc(1, 2) && (3 == 4));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be
// CHECK-FIXES: {{^ }}static_assert(myfunc(1, 2) && (3 == 4), "");
int x = 1;
assert(x == 0);
// CHECK-FIXES: {{^ }}assert(x == 0);
A a;
B b;
doSomething<A>(a);
doSomething<B>(b);
assert(false);
// CHECK-FIXES: {{^ }}assert(false);
assert(ZERO_MACRO);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be
// CHECK-FIXES: {{^ }}static_assert(ZERO_MACRO, "");
assert(0 && "Don't report me!");
// CHECK-FIXES: {{^ }}assert(0 && "Don't report me!");
assert(false && "Don't report me!");
// CHECK-FIXES: {{^ }}assert(false && "Don't report me!");
assert(ZERO_MACRO && "Report me!");
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be
// CHECK-FIXES: {{^ }}static_assert(ZERO_MACRO , "Report me!");
assert(10==5 && "Report me!");
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be
// CHECK-FIXES: {{^ }}static_assert(10==5 , "Report me!");
return 0;
}