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