1 | //===--- FixItHintUtils.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 "FixItHintUtils.h" |
10 | #include "LexerUtils.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/AST/ExprCXX.h" |
13 | #include "clang/AST/Type.h" |
14 | #include "clang/Sema/DeclSpec.h" |
15 | #include "clang/Tooling/FixIt.h" |
16 | #include <optional> |
17 | |
18 | namespace clang::tidy::utils::fixit { |
19 | |
20 | FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) { |
21 | SourceLocation AmpLocation = Var.getLocation(); |
22 | auto Token = utils::lexer::getPreviousToken( |
23 | Location: AmpLocation, SM: Context.getSourceManager(), LangOpts: Context.getLangOpts()); |
24 | if (!Token.is(tok::unknown)) |
25 | AmpLocation = Lexer::getLocForEndOfToken(Loc: Token.getLocation(), Offset: 0, |
26 | SM: Context.getSourceManager(), |
27 | LangOpts: Context.getLangOpts()); |
28 | return FixItHint::CreateInsertion(InsertionLoc: AmpLocation, Code: "&" ); |
29 | } |
30 | |
31 | static bool isValueType(const Type *T) { |
32 | return !(isa<PointerType>(Val: T) || isa<ReferenceType>(Val: T) || isa<ArrayType>(Val: T) || |
33 | isa<MemberPointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T)); |
34 | } |
35 | static bool isValueType(QualType QT) { return isValueType(T: QT.getTypePtr()); } |
36 | static bool isMemberOrFunctionPointer(QualType QT) { |
37 | return (QT->isPointerType() && QT->isFunctionPointerType()) || |
38 | isa<MemberPointerType>(Val: QT.getTypePtr()); |
39 | } |
40 | |
41 | static bool locDangerous(SourceLocation S) { |
42 | return S.isInvalid() || S.isMacroID(); |
43 | } |
44 | |
45 | static std::optional<SourceLocation> |
46 | skipLParensBackwards(SourceLocation Start, const ASTContext &Context) { |
47 | if (locDangerous(S: Start)) |
48 | return std::nullopt; |
49 | |
50 | auto PreviousTokenLParen = [&Start, &Context]() { |
51 | Token T; |
52 | T = lexer::getPreviousToken(Location: Start, SM: Context.getSourceManager(), |
53 | LangOpts: Context.getLangOpts()); |
54 | return T.is(K: tok::l_paren); |
55 | }; |
56 | |
57 | while (Start.isValid() && PreviousTokenLParen()) |
58 | Start = lexer::findPreviousTokenStart(Start, SM: Context.getSourceManager(), |
59 | LangOpts: Context.getLangOpts()); |
60 | |
61 | if (locDangerous(S: Start)) |
62 | return std::nullopt; |
63 | return Start; |
64 | } |
65 | |
66 | static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc, |
67 | StringRef Text) { |
68 | if (locDangerous(S: Loc)) |
69 | return std::nullopt; |
70 | return FixItHint::CreateInsertion(InsertionLoc: Loc, Code: Text); |
71 | } |
72 | |
73 | // Build a string that can be emitted as FixIt with either a space in before |
74 | // or after the qualifier, either ' const' or 'const '. |
75 | static std::string buildQualifier(Qualifiers::TQ Qualifier, |
76 | bool WhitespaceBefore = false) { |
77 | if (WhitespaceBefore) |
78 | return (llvm::Twine(' ') + Qualifiers::fromCVRMask(CVR: Qualifier).getAsString()) |
79 | .str(); |
80 | return (llvm::Twine(Qualifiers::fromCVRMask(CVR: Qualifier).getAsString()) + " " ) |
81 | .str(); |
82 | } |
83 | |
84 | static std::optional<FixItHint> changeValue(const VarDecl &Var, |
85 | Qualifiers::TQ Qualifier, |
86 | QualifierTarget QualTarget, |
87 | QualifierPolicy QualPolicy, |
88 | const ASTContext &Context) { |
89 | switch (QualPolicy) { |
90 | case QualifierPolicy::Left: |
91 | return fixIfNotDangerous(Var.getTypeSpecStartLoc(), |
92 | buildQualifier(Qualifier)); |
93 | case QualifierPolicy::Right: |
94 | std::optional<SourceLocation> IgnoredParens = |
95 | skipLParensBackwards(Var.getLocation(), Context); |
96 | |
97 | if (IgnoredParens) |
98 | return fixIfNotDangerous(Loc: *IgnoredParens, Text: buildQualifier(Qualifier)); |
99 | return std::nullopt; |
100 | } |
101 | llvm_unreachable("Unknown QualifierPolicy enum" ); |
102 | } |
103 | |
104 | static std::optional<FixItHint> changePointerItself(const VarDecl &Var, |
105 | Qualifiers::TQ Qualifier, |
106 | const ASTContext &Context) { |
107 | if (locDangerous(Var.getLocation())) |
108 | return std::nullopt; |
109 | |
110 | std::optional<SourceLocation> IgnoredParens = |
111 | skipLParensBackwards(Var.getLocation(), Context); |
112 | if (IgnoredParens) |
113 | return fixIfNotDangerous(Loc: *IgnoredParens, Text: buildQualifier(Qualifier)); |
114 | return std::nullopt; |
115 | } |
116 | |
117 | static std::optional<FixItHint> |
118 | changePointer(const VarDecl &Var, Qualifiers::TQ Qualifier, const Type *Pointee, |
119 | QualifierTarget QualTarget, QualifierPolicy QualPolicy, |
120 | const ASTContext &Context) { |
121 | // The pointer itself shall be marked as `const`. This is always to the right |
122 | // of the '*' or in front of the identifier. |
123 | if (QualTarget == QualifierTarget::Value) |
124 | return changePointerItself(Var, Qualifier, Context); |
125 | |
126 | // Mark the pointee `const` that is a normal value (`int* p = nullptr;`). |
127 | if (QualTarget == QualifierTarget::Pointee && isValueType(T: Pointee)) { |
128 | // Adding the `const` on the left side is just the beginning of the type |
129 | // specification. (`const int* p = nullptr;`) |
130 | if (QualPolicy == QualifierPolicy::Left) |
131 | return fixIfNotDangerous(Var.getTypeSpecStartLoc(), |
132 | buildQualifier(Qualifier)); |
133 | |
134 | // Adding the `const` on the right side of the value type requires finding |
135 | // the `*` token and placing the `const` left of it. |
136 | // (`int const* p = nullptr;`) |
137 | if (QualPolicy == QualifierPolicy::Right) { |
138 | SourceLocation BeforeStar = lexer::findPreviousTokenKind( |
139 | Start: Var.getLocation(), SM: Context.getSourceManager(), LangOpts: Context.getLangOpts(), |
140 | TK: tok::star); |
141 | if (locDangerous(S: BeforeStar)) |
142 | return std::nullopt; |
143 | |
144 | std::optional<SourceLocation> IgnoredParens = |
145 | skipLParensBackwards(Start: BeforeStar, Context); |
146 | |
147 | if (IgnoredParens) |
148 | return fixIfNotDangerous(Loc: *IgnoredParens, |
149 | Text: buildQualifier(Qualifier, WhitespaceBefore: true)); |
150 | return std::nullopt; |
151 | } |
152 | } |
153 | |
154 | if (QualTarget == QualifierTarget::Pointee && Pointee->isPointerType()) { |
155 | // Adding the `const` to the pointee if the pointee is a pointer |
156 | // is the same as 'QualPolicy == Right && isValueType(Pointee)'. |
157 | // The `const` must be left of the last `*` token. |
158 | // (`int * const* p = nullptr;`) |
159 | SourceLocation BeforeStar = lexer::findPreviousTokenKind( |
160 | Start: Var.getLocation(), SM: Context.getSourceManager(), LangOpts: Context.getLangOpts(), |
161 | TK: tok::star); |
162 | return fixIfNotDangerous(Loc: BeforeStar, Text: buildQualifier(Qualifier, WhitespaceBefore: true)); |
163 | } |
164 | |
165 | return std::nullopt; |
166 | } |
167 | |
168 | static std::optional<FixItHint> |
169 | changeReferencee(const VarDecl &Var, Qualifiers::TQ Qualifier, QualType Pointee, |
170 | QualifierTarget QualTarget, QualifierPolicy QualPolicy, |
171 | const ASTContext &Context) { |
172 | if (QualPolicy == QualifierPolicy::Left && isValueType(QT: Pointee)) |
173 | return fixIfNotDangerous(Var.getTypeSpecStartLoc(), |
174 | buildQualifier(Qualifier)); |
175 | |
176 | SourceLocation BeforeRef = lexer::findPreviousAnyTokenKind( |
177 | Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), |
178 | tok::amp, tok::ampamp); |
179 | std::optional<SourceLocation> IgnoredParens = |
180 | skipLParensBackwards(Start: BeforeRef, Context); |
181 | if (IgnoredParens) |
182 | return fixIfNotDangerous(Loc: *IgnoredParens, Text: buildQualifier(Qualifier, WhitespaceBefore: true)); |
183 | |
184 | return std::nullopt; |
185 | } |
186 | |
187 | std::optional<FixItHint> addQualifierToVarDecl(const VarDecl &Var, |
188 | const ASTContext &Context, |
189 | Qualifiers::TQ Qualifier, |
190 | QualifierTarget QualTarget, |
191 | QualifierPolicy QualPolicy) { |
192 | assert((QualPolicy == QualifierPolicy::Left || |
193 | QualPolicy == QualifierPolicy::Right) && |
194 | "Unexpected Insertion Policy" ); |
195 | assert((QualTarget == QualifierTarget::Pointee || |
196 | QualTarget == QualifierTarget::Value) && |
197 | "Unexpected Target" ); |
198 | |
199 | QualType ParenStrippedType = Var.getType().IgnoreParens(); |
200 | if (isValueType(QT: ParenStrippedType)) |
201 | return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); |
202 | |
203 | if (ParenStrippedType->isReferenceType()) |
204 | return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(), |
205 | QualTarget, QualPolicy, Context); |
206 | |
207 | if (isMemberOrFunctionPointer(QT: ParenStrippedType)) |
208 | return changePointerItself(Var, Qualifier, Context); |
209 | |
210 | if (ParenStrippedType->isPointerType()) |
211 | return changePointer(Var, Qualifier, |
212 | Pointee: ParenStrippedType->getPointeeType().getTypePtr(), |
213 | QualTarget, QualPolicy, Context); |
214 | |
215 | if (ParenStrippedType->isArrayType()) { |
216 | const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe(); |
217 | assert(AT && "Did not retrieve array element type for an array." ); |
218 | |
219 | if (isValueType(T: AT)) |
220 | return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); |
221 | |
222 | if (AT->isPointerType()) |
223 | return changePointer(Var, Qualifier, Pointee: AT->getPointeeType().getTypePtr(), |
224 | QualTarget, QualPolicy, Context); |
225 | } |
226 | |
227 | return std::nullopt; |
228 | } |
229 | |
230 | bool areParensNeededForStatement(const Stmt &Node) { |
231 | if (isa<ParenExpr>(Val: &Node)) |
232 | return false; |
233 | |
234 | if (isa<clang::BinaryOperator>(Val: &Node) || isa<UnaryOperator>(Val: &Node)) |
235 | return true; |
236 | |
237 | if (isa<clang::ConditionalOperator>(Val: &Node) || |
238 | isa<BinaryConditionalOperator>(Val: &Node)) |
239 | return true; |
240 | |
241 | if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Val: &Node)) { |
242 | switch (Op->getOperator()) { |
243 | case OO_PlusPlus: |
244 | [[fallthrough]]; |
245 | case OO_MinusMinus: |
246 | return Op->getNumArgs() != 2; |
247 | case OO_Call: |
248 | [[fallthrough]]; |
249 | case OO_Subscript: |
250 | [[fallthrough]]; |
251 | case OO_Arrow: |
252 | return false; |
253 | default: |
254 | return true; |
255 | }; |
256 | } |
257 | |
258 | if (isa<CStyleCastExpr>(Val: &Node)) |
259 | return true; |
260 | |
261 | return false; |
262 | } |
263 | |
264 | // Return true if expr needs to be put in parens when it is an argument of a |
265 | // prefix unary operator, e.g. when it is a binary or ternary operator |
266 | // syntactically. |
267 | static bool needParensAfterUnaryOperator(const Expr &ExprNode) { |
268 | if (isa<clang::BinaryOperator>(Val: &ExprNode) || |
269 | isa<clang::ConditionalOperator>(Val: &ExprNode)) { |
270 | return true; |
271 | } |
272 | if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Val: &ExprNode)) { |
273 | return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && |
274 | Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && |
275 | Op->getOperator() != OO_Subscript; |
276 | } |
277 | return false; |
278 | } |
279 | |
280 | // Format a pointer to an expression: prefix with '*' but simplify |
281 | // when it already begins with '&'. Return empty string on failure. |
282 | std::string formatDereference(const Expr &ExprNode, const ASTContext &Context) { |
283 | if (const auto *Op = dyn_cast<clang::UnaryOperator>(Val: &ExprNode)) { |
284 | if (Op->getOpcode() == UO_AddrOf) { |
285 | // Strip leading '&'. |
286 | return std::string( |
287 | tooling::fixit::getText(Node: *Op->getSubExpr()->IgnoreParens(), Context)); |
288 | } |
289 | } |
290 | StringRef Text = tooling::fixit::getText(Node: ExprNode, Context); |
291 | |
292 | if (Text.empty()) |
293 | return {}; |
294 | |
295 | // Remove remaining '->' from overloaded operator call |
296 | Text.consume_back(Suffix: "->" ); |
297 | |
298 | // Add leading '*'. |
299 | if (needParensAfterUnaryOperator(ExprNode)) { |
300 | return (llvm::Twine("*(" ) + Text + ")" ).str(); |
301 | } |
302 | return (llvm::Twine("*" ) + Text).str(); |
303 | } |
304 | |
305 | } // namespace clang::tidy::utils::fixit |
306 | |