clang-tools 17.0.0git
StaticAssertCheck.cpp
Go to the documentation of this file.
1//===--- StaticAssertCheck.cpp - clang-tidy -------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "StaticAssertCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/Expr.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Frontend/CompilerInstance.h"
14#include "clang/Lex/Lexer.h"
15#include "llvm/ADT/SmallVector.h"
16#include "llvm/ADT/StringRef.h"
17#include "llvm/Support/Casting.h"
18#include <optional>
19#include <string>
20
21using namespace clang::ast_matchers;
22
23namespace clang::tidy::misc {
24
26 : ClangTidyCheck(Name, Context) {}
27
28void StaticAssertCheck::registerMatchers(MatchFinder *Finder) {
29 auto NegatedString = unaryOperator(
30 hasOperatorName("!"), hasUnaryOperand(ignoringImpCasts(stringLiteral())));
31 auto IsAlwaysFalse =
32 expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)),
33 cxxNullPtrLiteralExpr(), gnuNullExpr(), NegatedString))
34 .bind("isAlwaysFalse");
35 auto IsAlwaysFalseWithCast = ignoringParenImpCasts(anyOf(
36 IsAlwaysFalse, cStyleCastExpr(has(ignoringParenImpCasts(IsAlwaysFalse)))
37 .bind("castExpr")));
38 auto AssertExprRoot = anyOf(
39 binaryOperator(
40 hasAnyOperatorName("&&", "=="),
41 hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))),
42 anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)),
43 anything()))
44 .bind("assertExprRoot"),
45 IsAlwaysFalse);
46 auto NonConstexprFunctionCall =
47 callExpr(hasDeclaration(functionDecl(unless(isConstexpr()))));
48 auto AssertCondition =
49 expr(
50 anyOf(expr(ignoringParenCasts(anyOf(
51 AssertExprRoot, unaryOperator(hasUnaryOperand(
52 ignoringParenCasts(AssertExprRoot)))))),
53 anything()),
54 unless(findAll(NonConstexprFunctionCall)))
55 .bind("condition");
56 auto Condition =
57 anyOf(ignoringParenImpCasts(callExpr(
58 hasDeclaration(functionDecl(hasName("__builtin_expect"))),
59 hasArgument(0, AssertCondition))),
60 AssertCondition);
61
62 Finder->addMatcher(conditionalOperator(hasCondition(Condition),
63 unless(isInTemplateInstantiation()))
64 .bind("condStmt"),
65 this);
66
67 Finder->addMatcher(
68 ifStmt(hasCondition(Condition), unless(isInTemplateInstantiation()))
69 .bind("condStmt"),
70 this);
71}
72
73void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
74 const ASTContext *ASTCtx = Result.Context;
75 const LangOptions &Opts = ASTCtx->getLangOpts();
76 const SourceManager &SM = ASTCtx->getSourceManager();
77 const auto *CondStmt = Result.Nodes.getNodeAs<Stmt>("condStmt");
78 const auto *Condition = Result.Nodes.getNodeAs<Expr>("condition");
79 const auto *IsAlwaysFalse = Result.Nodes.getNodeAs<Expr>("isAlwaysFalse");
80 const auto *AssertMSG = Result.Nodes.getNodeAs<StringLiteral>("assertMSG");
81 const auto *AssertExprRoot =
82 Result.Nodes.getNodeAs<BinaryOperator>("assertExprRoot");
83 const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("castExpr");
84 SourceLocation AssertExpansionLoc = CondStmt->getBeginLoc();
85
86 if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID())
87 return;
88
89 StringRef MacroName =
90 Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts);
91
92 if (MacroName != "assert" || Condition->isValueDependent() ||
93 Condition->isTypeDependent() || Condition->isInstantiationDependent() ||
94 !Condition->isEvaluatable(*ASTCtx))
95 return;
96
97 // False literal is not the result of macro expansion.
98 if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) {
99 SourceLocation FalseLiteralLoc =
100 SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc());
101 if (!FalseLiteralLoc.isMacroID())
102 return;
103
104 StringRef FalseMacroName =
105 Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts);
106 if (FalseMacroName.compare_insensitive("false") == 0 ||
107 FalseMacroName.compare_insensitive("null") == 0)
108 return;
109 }
110
111 SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc);
112
113 SmallVector<FixItHint, 4> FixItHints;
114 SourceLocation LastParenLoc;
115 if (AssertLoc.isValid() && !AssertLoc.isMacroID() &&
116 (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) {
117 FixItHints.push_back(
118 FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert"));
119
120 if (AssertExprRoot) {
121 FixItHints.push_back(FixItHint::CreateRemoval(
122 SourceRange(AssertExprRoot->getOperatorLoc())));
123 FixItHints.push_back(FixItHint::CreateRemoval(
124 SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc())));
125 FixItHints.push_back(FixItHint::CreateInsertion(
126 LastParenLoc, (Twine(", \"") + AssertMSG->getString() + "\"").str()));
127 } else if (!Opts.CPlusPlus17) {
128 FixItHints.push_back(FixItHint::CreateInsertion(LastParenLoc, ", \"\""));
129 }
130 }
131
132 diag(AssertLoc, "found assert() that could be replaced by static_assert()")
133 << FixItHints;
134}
135
136SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx,
137 SourceLocation AssertLoc) {
138 const LangOptions &Opts = ASTCtx->getLangOpts();
139 const SourceManager &SM = ASTCtx->getSourceManager();
140
141 std::optional<llvm::MemoryBufferRef> Buffer =
142 SM.getBufferOrNone(SM.getFileID(AssertLoc));
143 if (!Buffer)
144 return SourceLocation();
145
146 const char *BufferPos = SM.getCharacterData(AssertLoc);
147
148 Token Token;
149 Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts,
150 Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
151
152 // assert first left parenthesis
153 if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) ||
154 !Token.is(tok::l_paren))
155 return SourceLocation();
156
157 unsigned int ParenCount = 1;
158 while (ParenCount && !Lexer.LexFromRawLexer(Token)) {
159 if (Token.is(tok::l_paren))
160 ++ParenCount;
161 else if (Token.is(tok::r_paren))
162 --ParenCount;
163 }
164
165 return Token.getLocation();
166}
167
168} // namespace clang::tidy::misc
Token Name
std::string Condition
Condition used after the preprocessor directive.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
StaticAssertCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.