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
18namespace clang::tidy::utils::fixit {
19
20FixItHint 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
31static 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}
35static bool isValueType(QualType QT) { return isValueType(T: QT.getTypePtr()); }
36static bool isMemberOrFunctionPointer(QualType QT) {
37 return (QT->isPointerType() && QT->isFunctionPointerType()) ||
38 isa<MemberPointerType>(Val: QT.getTypePtr());
39}
40
41static bool locDangerous(SourceLocation S) {
42 return S.isInvalid() || S.isMacroID();
43}
44
45static std::optional<SourceLocation>
46skipLParensBackwards(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
66static 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 '.
75static 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
84static 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
104static 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
117static std::optional<FixItHint>
118changePointer(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
168static std::optional<FixItHint>
169changeReferencee(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
187std::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
230bool 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.
267static 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.
282std::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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp