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 | |
20 | using namespace clang::ast_matchers; |
21 | |
22 | namespace clang::tidy::misc { |
23 | |
24 | StaticAssertCheck::StaticAssertCheck(StringRef Name, ClangTidyContext *Context) |
25 | : ClangTidyCheck(Name, Context) {} |
26 | |
27 | void 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 | |
79 | void 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 | |
142 | SourceLocation 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 | |