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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp