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