1 | //===--- ProTypeVarargCheck.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 "ProTypeVarargCheck.h" |
10 | #include "../utils/Matchers.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | #include "clang/ASTMatchers/ASTMatchers.h" |
14 | #include "clang/Basic/TargetInfo.h" |
15 | #include "clang/Lex/PPCallbacks.h" |
16 | #include "clang/Lex/Preprocessor.h" |
17 | #include "clang/Lex/Token.h" |
18 | |
19 | using namespace clang::ast_matchers; |
20 | |
21 | namespace clang::tidy::cppcoreguidelines { |
22 | |
23 | const internal::VariadicDynCastAllOfMatcher<Stmt, VAArgExpr> VAArgExpr; |
24 | |
25 | static constexpr StringRef AllowedVariadics[] = { |
26 | // clang-format off |
27 | "__builtin_isgreater" , |
28 | "__builtin_isgreaterequal" , |
29 | "__builtin_isless" , |
30 | "__builtin_islessequal" , |
31 | "__builtin_islessgreater" , |
32 | "__builtin_isunordered" , |
33 | "__builtin_fpclassify" , |
34 | "__builtin_isfinite" , |
35 | "__builtin_isinf" , |
36 | "__builtin_isinf_sign" , |
37 | "__builtin_isnan" , |
38 | "__builtin_isnormal" , |
39 | "__builtin_signbit" , |
40 | "__builtin_constant_p" , |
41 | "__builtin_classify_type" , |
42 | "__builtin_va_start" , |
43 | "__builtin_assume_aligned" , // Documented as variadic to support default |
44 | // parameters. |
45 | "__builtin_prefetch" , // Documented as variadic to support default |
46 | // parameters. |
47 | "__builtin_shufflevector" , // Documented as variadic but with a defined |
48 | // number of args based on vector size. |
49 | "__builtin_convertvector" , |
50 | "__builtin_call_with_static_chain" , |
51 | "__builtin_annotation" , |
52 | "__builtin_add_overflow" , |
53 | "__builtin_sub_overflow" , |
54 | "__builtin_mul_overflow" , |
55 | "__builtin_preserve_access_index" , |
56 | "__builtin_nontemporal_store" , |
57 | "__builtin_nontemporal_load" , |
58 | "__builtin_ms_va_start" , |
59 | // clang-format on |
60 | }; |
61 | |
62 | static constexpr StringRef VaArgWarningMessage = |
63 | "do not use va_arg to define c-style vararg functions; " |
64 | "use variadic templates instead" ; |
65 | |
66 | namespace { |
67 | AST_MATCHER(QualType, isVAList) { |
68 | ASTContext &Context = Finder->getASTContext(); |
69 | QualType Desugar = Node.getDesugaredType(Context); |
70 | QualType NodeTy = Node.getUnqualifiedType(); |
71 | |
72 | auto CheckVaList = [](QualType NodeTy, QualType Expected, |
73 | const ASTContext &Context) { |
74 | if (NodeTy == Expected) |
75 | return true; |
76 | QualType Desugar = NodeTy; |
77 | QualType Ty; |
78 | do { |
79 | Ty = Desugar; |
80 | Desugar = Ty.getSingleStepDesugaredType(Context); |
81 | if (Desugar == Expected) |
82 | return true; |
83 | } while (Desugar != Ty); |
84 | return false; |
85 | }; |
86 | |
87 | // The internal implementation of __builtin_va_list depends on the target |
88 | // type. Some targets implements va_list as 'char *' or 'void *'. |
89 | // In these cases we need to remove all typedefs one by one to check this. |
90 | using BuiltinVaListKind = TargetInfo::BuiltinVaListKind; |
91 | BuiltinVaListKind VaListKind = Context.getTargetInfo().getBuiltinVaListKind(); |
92 | if (VaListKind == BuiltinVaListKind::CharPtrBuiltinVaList || |
93 | VaListKind == BuiltinVaListKind::VoidPtrBuiltinVaList) { |
94 | if (CheckVaList(NodeTy, Context.getBuiltinVaListType(), Context)) |
95 | return true; |
96 | } else if (Desugar == |
97 | Context.getBuiltinVaListType().getDesugaredType(Context)) { |
98 | return true; |
99 | } |
100 | |
101 | // We also need to check the implementation of __builtin_ms_va_list in the |
102 | // same way, because it may differ from the va_list implementation. |
103 | if (Desugar == Context.getBuiltinMSVaListType().getDesugaredType(Context) && |
104 | CheckVaList(NodeTy, Context.getBuiltinMSVaListType(), Context)) { |
105 | return true; |
106 | } |
107 | |
108 | return false; |
109 | } |
110 | |
111 | AST_MATCHER_P(AdjustedType, hasOriginalType, |
112 | ast_matchers::internal::Matcher<QualType>, InnerType) { |
113 | return InnerType.matches(Node: Node.getOriginalType(), Finder, Builder); |
114 | } |
115 | |
116 | class VaArgPPCallbacks : public PPCallbacks { |
117 | public: |
118 | VaArgPPCallbacks(ProTypeVarargCheck *Check) : Check(Check) {} |
119 | |
120 | void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, |
121 | SourceRange Range, const MacroArgs *Args) override { |
122 | if (MacroNameTok.getIdentifierInfo()->getName() == "va_arg" ) { |
123 | Check->diag(Loc: MacroNameTok.getLocation(), Description: VaArgWarningMessage); |
124 | } |
125 | } |
126 | |
127 | private: |
128 | ProTypeVarargCheck *Check; |
129 | }; |
130 | } // namespace |
131 | |
132 | void ProTypeVarargCheck::registerMatchers(MatchFinder *Finder) { |
133 | Finder->addMatcher(NodeMatch: VAArgExpr().bind(ID: "va_use" ), Action: this); |
134 | |
135 | Finder->addMatcher( |
136 | NodeMatch: callExpr(callee(InnerMatcher: functionDecl(isVariadic(), |
137 | unless(hasAnyName(AllowedVariadics)))), |
138 | unless(hasAncestor(expr(matchers::hasUnevaluatedContext()))), |
139 | unless(hasAncestor(typeLoc()))) |
140 | .bind(ID: "callvararg" ), |
141 | Action: this); |
142 | |
143 | Finder->addMatcher( |
144 | NodeMatch: varDecl(unless(parmVarDecl()), |
145 | hasType(InnerMatcher: qualType( |
146 | anyOf(isVAList(), decayedType(hasOriginalType(InnerType: isVAList())))))) |
147 | .bind(ID: "va_list" ), |
148 | Action: this); |
149 | } |
150 | |
151 | void ProTypeVarargCheck::registerPPCallbacks(const SourceManager &SM, |
152 | Preprocessor *PP, |
153 | Preprocessor *ModuleExpanderPP) { |
154 | PP->addPPCallbacks(C: std::make_unique<VaArgPPCallbacks>(args: this)); |
155 | } |
156 | |
157 | static bool hasSingleVariadicArgumentWithValue(const CallExpr *C, uint64_t I) { |
158 | const auto *FDecl = dyn_cast<FunctionDecl>(Val: C->getCalleeDecl()); |
159 | if (!FDecl) |
160 | return false; |
161 | |
162 | auto N = FDecl->getNumParams(); // Number of parameters without '...' |
163 | if (C->getNumArgs() != N + 1) |
164 | return false; // more/less than one argument passed to '...' |
165 | |
166 | const auto *IntLit = |
167 | dyn_cast<IntegerLiteral>(Val: C->getArg(Arg: N)->IgnoreParenImpCasts()); |
168 | if (!IntLit) |
169 | return false; |
170 | |
171 | if (IntLit->getValue() != I) |
172 | return false; |
173 | |
174 | return true; |
175 | } |
176 | |
177 | void ProTypeVarargCheck::check(const MatchFinder::MatchResult &Result) { |
178 | if (const auto *Matched = Result.Nodes.getNodeAs<CallExpr>(ID: "callvararg" )) { |
179 | if (hasSingleVariadicArgumentWithValue(C: Matched, I: 0)) |
180 | return; |
181 | diag(Matched->getExprLoc(), "do not call c-style vararg functions" ); |
182 | } |
183 | |
184 | if (const auto *Matched = Result.Nodes.getNodeAs<Expr>(ID: "va_use" )) { |
185 | diag(Loc: Matched->getExprLoc(), Description: VaArgWarningMessage); |
186 | } |
187 | |
188 | if (const auto *Matched = Result.Nodes.getNodeAs<VarDecl>(ID: "va_list" )) { |
189 | auto SR = Matched->getSourceRange(); |
190 | if (SR.isInvalid()) |
191 | return; // some implicitly generated builtins take va_list |
192 | diag(Loc: SR.getBegin(), Description: "do not declare variables of type va_list; " |
193 | "use variadic templates instead" ); |
194 | } |
195 | } |
196 | |
197 | } // namespace clang::tidy::cppcoreguidelines |
198 | |