xref: /llvm-project/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp (revision 1b0fcf1e42e05611ec37aa7956988ae6317ad116)
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       AmpLocation, Context.getSourceManager(), Context.getLangOpts());
24   if (!Token.is(tok::unknown))
25     AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0,
26                                              Context.getSourceManager(),
27                                              Context.getLangOpts());
28   return FixItHint::CreateInsertion(AmpLocation, "&");
29 }
30 
31 static bool isValueType(const Type *T) {
32   return !(isa<PointerType>(T) || isa<ReferenceType>(T) || isa<ArrayType>(T) ||
33            isa<MemberPointerType>(T) || isa<ObjCObjectPointerType>(T));
34 }
35 static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); }
36 static bool isMemberOrFunctionPointer(QualType QT) {
37   return (QT->isPointerType() && QT->isFunctionPointerType()) ||
38          isa<MemberPointerType>(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(Start))
48     return std::nullopt;
49 
50   auto PreviousTokenLParen = [&Start, &Context]() {
51     Token T;
52     T = lexer::getPreviousToken(Start, Context.getSourceManager(),
53                                 Context.getLangOpts());
54     return T.is(tok::l_paren);
55   };
56 
57   while (Start.isValid() && PreviousTokenLParen())
58     Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(),
59                                           Context.getLangOpts());
60 
61   if (locDangerous(Start))
62     return std::nullopt;
63   return Start;
64 }
65 
66 static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc,
67                                                   StringRef Text) {
68   if (locDangerous(Loc))
69     return std::nullopt;
70   return FixItHint::CreateInsertion(Loc, 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(Qualifier).getAsString())
79         .str();
80   return (llvm::Twine(Qualifiers::fromCVRMask(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(*IgnoredParens, 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(*IgnoredParens, 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(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           Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
140           tok::star);
141       if (locDangerous(BeforeStar))
142         return std::nullopt;
143 
144       std::optional<SourceLocation> IgnoredParens =
145           skipLParensBackwards(BeforeStar, Context);
146 
147       if (IgnoredParens)
148         return fixIfNotDangerous(*IgnoredParens,
149                                  buildQualifier(Qualifier, 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         Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
161         tok::star);
162     return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, 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(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(BeforeRef, Context);
181   if (IgnoredParens)
182     return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, 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(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(ParenStrippedType))
208     return changePointerItself(Var, Qualifier, Context);
209 
210   if (ParenStrippedType->isPointerType())
211     return changePointer(Var, Qualifier,
212                          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(AT))
220       return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
221 
222     if (AT->isPointerType())
223       return changePointer(Var, Qualifier, 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>(&Node))
232     return false;
233 
234   if (isa<clang::BinaryOperator>(&Node) || isa<UnaryOperator>(&Node))
235     return true;
236 
237   if (isa<clang::ConditionalOperator>(&Node) ||
238       isa<BinaryConditionalOperator>(&Node))
239     return true;
240 
241   if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&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>(&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>(&ExprNode) ||
269       isa<clang::ConditionalOperator>(&ExprNode)) {
270     return true;
271   }
272   if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&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>(&ExprNode)) {
284     if (Op->getOpcode() == UO_AddrOf) {
285       // Strip leading '&'.
286       return std::string(
287           tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(), Context));
288     }
289   }
290   StringRef Text = tooling::fixit::getText(ExprNode, Context);
291 
292   if (Text.empty())
293     return {};
294 
295   // Remove remaining '->' from overloaded operator call
296   Text.consume_back("->");
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