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/Type.h"
13 
14 namespace clang {
15 namespace tidy {
16 namespace utils {
17 namespace fixit {
18 
changeVarDeclToReference(const VarDecl & Var,ASTContext & Context)19 FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) {
20   SourceLocation AmpLocation = Var.getLocation();
21   auto Token = utils::lexer::getPreviousToken(
22       AmpLocation, Context.getSourceManager(), Context.getLangOpts());
23   if (!Token.is(tok::unknown))
24     AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0,
25                                              Context.getSourceManager(),
26                                              Context.getLangOpts());
27   return FixItHint::CreateInsertion(AmpLocation, "&");
28 }
29 
isValueType(const Type * T)30 static bool isValueType(const Type *T) {
31   return !(isa<PointerType>(T) || isa<ReferenceType>(T) || isa<ArrayType>(T) ||
32            isa<MemberPointerType>(T) || isa<ObjCObjectPointerType>(T));
33 }
isValueType(QualType QT)34 static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); }
isMemberOrFunctionPointer(QualType QT)35 static bool isMemberOrFunctionPointer(QualType QT) {
36   return (QT->isPointerType() && QT->isFunctionPointerType()) ||
37          isa<MemberPointerType>(QT.getTypePtr());
38 }
39 
locDangerous(SourceLocation S)40 static bool locDangerous(SourceLocation S) {
41   return S.isInvalid() || S.isMacroID();
42 }
43 
44 static Optional<SourceLocation>
skipLParensBackwards(SourceLocation Start,const ASTContext & Context)45 skipLParensBackwards(SourceLocation Start, const ASTContext &Context) {
46   if (locDangerous(Start))
47     return None;
48 
49   auto PreviousTokenLParen = [&Start, &Context]() {
50     Token T;
51     T = lexer::getPreviousToken(Start, Context.getSourceManager(),
52                                 Context.getLangOpts());
53     return T.is(tok::l_paren);
54   };
55 
56   while (Start.isValid() && PreviousTokenLParen())
57     Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(),
58                                           Context.getLangOpts());
59 
60   if (locDangerous(Start))
61     return None;
62   return Start;
63 }
64 
fixIfNotDangerous(SourceLocation Loc,StringRef Text)65 static Optional<FixItHint> fixIfNotDangerous(SourceLocation Loc,
66                                              StringRef Text) {
67   if (locDangerous(Loc))
68     return None;
69   return FixItHint::CreateInsertion(Loc, 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 '.
buildQualifier(DeclSpec::TQ Qualifier,bool WhitespaceBefore=false)74 static std::string buildQualifier(DeclSpec::TQ Qualifier,
75                                   bool WhitespaceBefore = false) {
76   if (WhitespaceBefore)
77     return (llvm::Twine(' ') + DeclSpec::getSpecifierName(Qualifier)).str();
78   return (llvm::Twine(DeclSpec::getSpecifierName(Qualifier)) + " ").str();
79 }
80 
changeValue(const VarDecl & Var,DeclSpec::TQ Qualifier,QualifierTarget QualTarget,QualifierPolicy QualPolicy,const ASTContext & Context)81 static 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     Optional<SourceLocation> IgnoredParens =
92         skipLParensBackwards(Var.getLocation(), Context);
93 
94     if (IgnoredParens)
95       return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
96     return None;
97   }
98   llvm_unreachable("Unknown QualifierPolicy enum");
99 }
100 
changePointerItself(const VarDecl & Var,DeclSpec::TQ Qualifier,const ASTContext & Context)101 static Optional<FixItHint> changePointerItself(const VarDecl &Var,
102                                                DeclSpec::TQ Qualifier,
103                                                const ASTContext &Context) {
104   if (locDangerous(Var.getLocation()))
105     return None;
106 
107   Optional<SourceLocation> IgnoredParens =
108       skipLParensBackwards(Var.getLocation(), Context);
109   if (IgnoredParens)
110     return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
111   return None;
112 }
113 
114 static Optional<FixItHint>
changePointer(const VarDecl & Var,DeclSpec::TQ Qualifier,const Type * Pointee,QualifierTarget QualTarget,QualifierPolicy QualPolicy,const ASTContext & Context)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(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           Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
137           tok::star);
138       if (locDangerous(BeforeStar))
139         return None;
140 
141       Optional<SourceLocation> IgnoredParens =
142           skipLParensBackwards(BeforeStar, Context);
143 
144       if (IgnoredParens)
145         return fixIfNotDangerous(*IgnoredParens,
146                                  buildQualifier(Qualifier, true));
147       return None;
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         Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
158         tok::star);
159     return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true));
160   }
161 
162   return None;
163 }
164 
165 static Optional<FixItHint>
changeReferencee(const VarDecl & Var,DeclSpec::TQ Qualifier,QualType Pointee,QualifierTarget QualTarget,QualifierPolicy QualPolicy,const ASTContext & Context)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(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   Optional<SourceLocation> IgnoredParens =
177       skipLParensBackwards(BeforeRef, Context);
178   if (IgnoredParens)
179     return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true));
180 
181   return None;
182 }
183 
addQualifierToVarDecl(const VarDecl & Var,const ASTContext & Context,DeclSpec::TQ Qualifier,QualifierTarget QualTarget,QualifierPolicy QualPolicy)184 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(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(ParenStrippedType))
205     return changePointerItself(Var, Qualifier, Context);
206 
207   if (ParenStrippedType->isPointerType())
208     return changePointer(Var, Qualifier,
209                          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(AT))
217       return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
218 
219     if (AT->isPointerType())
220       return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(),
221                            QualTarget, QualPolicy, Context);
222   }
223 
224   return None;
225 }
226 } // namespace fixit
227 } // namespace utils
228 } // namespace tidy
229 } // namespace clang
230