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