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