clang-tools 19.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 "../utils/Matchers.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Expr.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Frontend/CompilerInstance.h"
15#include "clang/Lex/Lexer.h"
16#include "llvm/ADT/SmallVector.h"
17#include "llvm/ADT/StringRef.h"
18#include "llvm/Support/Casting.h"
19#include <optional>
20#include <string>
21
22using namespace clang::ast_matchers;
23
24namespace clang::tidy::misc {
25
27 : ClangTidyCheck(Name, Context) {}
28
29void StaticAssertCheck::registerMatchers(MatchFinder *Finder) {
30 auto NegatedString = unaryOperator(
31 hasOperatorName("!"), hasUnaryOperand(ignoringImpCasts(stringLiteral())));
32 auto IsAlwaysFalse =
33 expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)),
34 cxxNullPtrLiteralExpr(), gnuNullExpr(), NegatedString))
35 .bind("isAlwaysFalse");
36 auto IsAlwaysFalseWithCast = ignoringParenImpCasts(anyOf(
37 IsAlwaysFalse, cStyleCastExpr(has(ignoringParenImpCasts(IsAlwaysFalse)))
38 .bind("castExpr")));
39 auto AssertExprRoot = anyOf(
40 binaryOperator(
41 hasAnyOperatorName("&&", "=="),
42 hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))),
43 anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)),
44 anything()))
45 .bind("assertExprRoot"),
46 IsAlwaysFalse);
47 auto NonConstexprFunctionCall =
48 callExpr(hasDeclaration(functionDecl(unless(isConstexpr()))));
49 auto NonConstexprVariableReference =
50 declRefExpr(to(varDecl(unless(isConstexpr()))),
51 unless(hasAncestor(expr(matchers::hasUnevaluatedContext()))),
52 unless(hasAncestor(typeLoc())));
53
54 auto NonConstexprCode =
55 expr(anyOf(NonConstexprFunctionCall, NonConstexprVariableReference));
56 auto AssertCondition =
57 expr(
58 anyOf(expr(ignoringParenCasts(anyOf(
59 AssertExprRoot, unaryOperator(hasUnaryOperand(
60 ignoringParenCasts(AssertExprRoot)))))),
61 anything()),
62 unless(NonConstexprCode), unless(hasDescendant(NonConstexprCode)))
63 .bind("condition");
64 auto Condition =
65 anyOf(ignoringParenImpCasts(callExpr(
66 hasDeclaration(functionDecl(hasName("__builtin_expect"))),
67 hasArgument(0, AssertCondition))),
68 AssertCondition);
69
70 Finder->addMatcher(conditionalOperator(hasCondition(Condition),
71 unless(isInTemplateInstantiation()))
72 .bind("condStmt"),
73 this);
74
75 Finder->addMatcher(
76 ifStmt(hasCondition(Condition), unless(isInTemplateInstantiation()))
77 .bind("condStmt"),
78 this);
79}
80
81void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
82 const ASTContext *ASTCtx = Result.Context;
83 const LangOptions &Opts = ASTCtx->getLangOpts();
84 const SourceManager &SM = ASTCtx->getSourceManager();
85 const auto *CondStmt = Result.Nodes.getNodeAs<Stmt>("condStmt");
86 const auto *Condition = Result.Nodes.getNodeAs<Expr>("condition");
87 const auto *IsAlwaysFalse = Result.Nodes.getNodeAs<Expr>("isAlwaysFalse");
88 const auto *AssertMSG = Result.Nodes.getNodeAs<StringLiteral>("assertMSG");
89 const auto *AssertExprRoot =
90 Result.Nodes.getNodeAs<BinaryOperator>("assertExprRoot");
91 const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("castExpr");
92 SourceLocation AssertExpansionLoc = CondStmt->getBeginLoc();
93
94 if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID())
95 return;
96
97 StringRef MacroName =
98 Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts);
99
100 if (MacroName != "assert" || Condition->isValueDependent() ||
101 Condition->isTypeDependent() || Condition->isInstantiationDependent() ||
102 !Condition->isEvaluatable(*ASTCtx))
103 return;
104
105 // False literal is not the result of macro expansion.
106 if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) {
107 SourceLocation FalseLiteralLoc =
108 SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc());
109 if (!FalseLiteralLoc.isMacroID())
110 return;
111
112 StringRef FalseMacroName =
113 Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts);
114 if (FalseMacroName.compare_insensitive("false") == 0 ||
115 FalseMacroName.compare_insensitive("null") == 0)
116 return;
117 }
118
119 SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc);
120
121 SmallVector<FixItHint, 4> FixItHints;
122 SourceLocation LastParenLoc;
123 if (AssertLoc.isValid() && !AssertLoc.isMacroID() &&
124 (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) {
125 FixItHints.push_back(
126 FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert"));
127
128 if (AssertExprRoot) {
129 FixItHints.push_back(FixItHint::CreateRemoval(
130 SourceRange(AssertExprRoot->getOperatorLoc())));
131 FixItHints.push_back(FixItHint::CreateRemoval(
132 SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc())));
133 FixItHints.push_back(FixItHint::CreateInsertion(
134 LastParenLoc, (Twine(", \"") + AssertMSG->getString() + "\"").str()));
135 } else if (!Opts.CPlusPlus17) {
136 FixItHints.push_back(FixItHint::CreateInsertion(LastParenLoc, ", \"\""));
137 }
138 }
139
140 diag(AssertLoc, "found assert() that could be replaced by static_assert()")
141 << FixItHints;
142}
143
144SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx,
145 SourceLocation AssertLoc) {
146 const LangOptions &Opts = ASTCtx->getLangOpts();
147 const SourceManager &SM = ASTCtx->getSourceManager();
148
149 std::optional<llvm::MemoryBufferRef> Buffer =
150 SM.getBufferOrNone(SM.getFileID(AssertLoc));
151 if (!Buffer)
152 return {};
153
154 const char *BufferPos = SM.getCharacterData(AssertLoc);
155
156 Token Token;
157 Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts,
158 Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
159
160 // assert first left parenthesis
161 if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) ||
162 !Token.is(tok::l_paren))
163 return {};
164
165 unsigned int ParenCount = 1;
166 while (ParenCount && !Lexer.LexFromRawLexer(Token)) {
167 if (Token.is(tok::l_paren))
168 ++ParenCount;
169 else if (Token.is(tok::r_paren))
170 --ParenCount;
171 }
172
173 return Token.getLocation();
174}
175
176} // namespace clang::tidy::misc
llvm::SmallString< 256U > Name
std::string MacroName
Definition: Preamble.cpp:240
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.