clang-tools 10.0.0

UseTrailingReturnTypeCheck.cpp
Go to the documentation of this file.
1 //===--- UseTrailingReturnTypeCheck.cpp - clang-tidy-----------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
10 #include "UseTrailingReturnTypeCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/RecursiveASTVisitor.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Tooling/FixIt.h"
15
16 #include <cctype>
17
18 using namespace clang::ast_matchers;
19
20 namespace clang {
21 namespace tidy {
22 namespace modernize {
23 namespace {
24 struct UnqualNameVisitor : public RecursiveASTVisitor<UnqualNameVisitor> {
25 public:
26 UnqualNameVisitor(const FunctionDecl &F) : F(F) {}
27
28 bool Collision = false;
29
30 bool shouldWalkTypesOfTypeLocs() const { return false; }
31
32 bool VisitUnqualName(StringRef UnqualName) {
33 // Check for collisions with function arguments.
34 for (ParmVarDecl *Param : F.parameters())
35 if (const IdentifierInfo *Ident = Param->getIdentifier())
36 if (Ident->getName() == UnqualName) {
37 Collision = true;
38 return true;
39 }
40 return false;
41 }
42
43 bool TraverseTypeLoc(TypeLoc TL, bool Elaborated = false) {
44 if (TL.isNull())
45 return true;
46
47 if (!Elaborated) {
48 switch (TL.getTypeLocClass()) {
49 case TypeLoc::Record:
50 if (VisitUnqualName(
51 TL.getAs<RecordTypeLoc>().getTypePtr()->getDecl()->getName()))
52 return false;
53 break;
54 case TypeLoc::Enum:
55 if (VisitUnqualName(
56 TL.getAs<EnumTypeLoc>().getTypePtr()->getDecl()->getName()))
57 return false;
58 break;
59 case TypeLoc::TemplateSpecialization:
60 if (VisitUnqualName(TL.getAs<TemplateSpecializationTypeLoc>()
61 .getTypePtr()
62 ->getTemplateName()
63 .getAsTemplateDecl()
64 ->getName()))
65 return false;
66 break;
67 default:
68 break;
69 }
70 }
71
72 return RecursiveASTVisitor<UnqualNameVisitor>::TraverseTypeLoc(TL);
73 }
74
75 // Replace the base method in order to call ower own
76 // TraverseTypeLoc().
77 bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL) {
78 return TraverseTypeLoc(TL.getUnqualifiedLoc());
79 }
80
81 // Replace the base version to inform TraverseTypeLoc that the type is
82 // elaborated.
83 bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc TL) {
84 if (TL.getQualifierLoc() &&
85 !TraverseNestedNameSpecifierLoc(TL.getQualifierLoc()))
86 return false;
87 return TraverseTypeLoc(TL.getNamedTypeLoc(), true);
88 }
89
90 bool VisitDeclRefExpr(DeclRefExpr *S) {
91 DeclarationName Name = S->getNameInfo().getName();
92 return S->getQualifierLoc() || !Name.isIdentifier() ||
93 !VisitUnqualName(Name.getAsIdentifierInfo()->getName());
94 }
95
96 private:
97 const FunctionDecl &F;
98 };
99 } // namespace
100
101 constexpr llvm::StringLiteral Message =
102 "use a trailing return type for this function";
103
104 static SourceLocation expandIfMacroId(SourceLocation Loc,
105 const SourceManager &SM) {
106 if (Loc.isMacroID())
107 Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
108 assert(!Loc.isMacroID() &&
109 "SourceLocation must not be a macro ID after recursive expansion");
110 return Loc;
111 }
112
113 SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation(
114 const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
115 const SourceManager &SM, const LangOptions &LangOpts) {
116 // We start with the location of the closing parenthesis.
117 SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
118 if (ExceptionSpecRange.isValid())
119 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
120 LangOpts);
121
122 // If the function argument list ends inside of a macro, it is dangerous to
123 // start lexing from here - bail out.
124 SourceLocation ClosingParen = FTL.getRParenLoc();
125 if (ClosingParen.isMacroID())
126 return {};
127
128 SourceLocation Result =
129 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
130
131 // Skip subsequent CV and ref qualifiers.
132 std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Result);
133 StringRef File = SM.getBufferData(Loc.first);
134 const char *TokenBegin = File.data() + Loc.second;
135 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
136 TokenBegin, File.end());
137 Token T;
138 while (!Lexer.LexFromRawLexer(T)) {
139 if (T.is(tok::raw_identifier)) {
140 IdentifierInfo &Info = Ctx.Idents.get(
141 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
142 T.setIdentifierInfo(&Info);
143 T.setKind(Info.getTokenID());
144 }
145
146 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
147 tok::kw_restrict)) {
148 Result = T.getEndLoc();
149 continue;
150 }
151 break;
152 }
153 return Result;
154 }
155
156 static bool IsCVR(Token T) {
157 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
158 }
159
160 static bool IsSpecifier(Token T) {
161 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
162 tok::kw_static, tok::kw_friend, tok::kw_virtual);
163 }
164
165 static llvm::Optional<ClassifiedToken>
166 classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) {
167 ClassifiedToken CT;
168 CT.T = Tok;
169 CT.isQualifier = true;
170 CT.isSpecifier = true;
171 bool ContainsQualifiers = false;
172 bool ContainsSpecifiers = false;
173 bool ContainsSomethingElse = false;
174
175 Token End;
176 End.setKind(tok::eof);
177 SmallVector<Token, 2> Stream{Tok, End};
178
179 // FIXME: do not report these token to Preprocessor.TokenWatcher.
180 PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
181 while (true) {
182 Token T;
183 PP.Lex(T);
184 if (T.is(tok::eof))
185 break;
186
187 bool Qual = IsCVR(T);
188 bool Spec = IsSpecifier(T);
189 CT.isQualifier &= Qual;
190 CT.isSpecifier &= Spec;
191 ContainsQualifiers |= Qual;
192 ContainsSpecifiers |= Spec;
193 ContainsSomethingElse |= !Qual && !Spec;
194 }
195
196 // If the Token/Macro contains more than one type of tokens, we would need
197 // to split the macro in order to move parts to the trailing return type.
198 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
199 return llvm::None;
200
201 return CT;
202 }
203
204 llvm::Optional<SmallVector<ClassifiedToken, 8>>
205 UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
206 const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
207 const LangOptions &LangOpts) {
208 SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM);
209 SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
210
211 // Create tokens for everything before the name of the function.
212 std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
213 StringRef File = SM.getBufferData(Loc.first);
214 const char *TokenBegin = File.data() + Loc.second;
215 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
216 TokenBegin, File.end());
217 Token T;
218 SmallVector<ClassifiedToken, 8> ClassifiedTokens;
219 while (!Lexer.LexFromRawLexer(T) &&
220 SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
221 if (T.is(tok::raw_identifier)) {
222 IdentifierInfo &Info = Ctx.Idents.get(
223 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
224
225 if (Info.hasMacroDefinition()) {
226 const MacroInfo *MI = PP->getMacroInfo(&Info);
227 if (!MI || MI->isFunctionLike()) {
228 // Cannot handle function style macros.
229 diag(F.getLocation(), Message);
230 return llvm::None;
231 }
232 }
233
234 T.setIdentifierInfo(&Info);
235 T.setKind(Info.getTokenID());
236 }
237
238 if (llvm::Optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
239 ClassifiedTokens.push_back(*CT);
240 else {
241 diag(F.getLocation(), Message);
242 return llvm::None;
243 }
244 }
245
246 return ClassifiedTokens;
247 }
248
249 static bool hasAnyNestedLocalQualifiers(QualType Type) {
250 bool Result = Type.hasLocalQualifiers();
251 if (Type->isPointerType())
252 Result = Result || hasAnyNestedLocalQualifiers(
253 Type->castAs<PointerType>()->getPointeeType());
254 if (Type->isReferenceType())
255 Result = Result || hasAnyNestedLocalQualifiers(
256 Type->castAs<ReferenceType>()->getPointeeType());
257 return Result;
258 }
259
260 SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
261 const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
262 const LangOptions &LangOpts) {
263
264 // We start with the range of the return type and expand to neighboring
265 // qualifiers (const, volatile and restrict).
266 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
267 if (ReturnTypeRange.isInvalid()) {
268 // Happens if e.g. clang cannot resolve all includes and the return type is
269 // unknown.
270 diag(F.getLocation(), Message);
271 return {};
272 }
273
274 // If the return type has no local qualifiers, it's source range is accurate.
275 if (!hasAnyNestedLocalQualifiers(F.getReturnType()))
276 return ReturnTypeRange;
277
278 // Include qualifiers to the left and right of the return type.
279 llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
280 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
281 if (!MaybeTokens)
282 return {};
283 const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
284
285 ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM));
286 ReturnTypeRange.setEnd(expandIfMacroId(ReturnTypeRange.getEnd(), SM));
287
288 bool ExtendedLeft = false;
289 for (size_t I = 0; I < Tokens.size(); I++) {
290 // If we found the beginning of the return type, include left qualifiers.
291 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
292 ReturnTypeRange.getBegin()) &&
293 !ExtendedLeft) {
294 assert(I <= size_t(std::numeric_limits<int>::max()) &&
295 "Integer overflow detected");
296 for (int J = static_cast<int>(I) - 1; J >= 0 && Tokens[J].isQualifier;
297 J--)
298 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
299 ExtendedLeft = true;
300 }
301 // If we found the end of the return type, include right qualifiers.
302 if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
303 Tokens[I].T.getLocation())) {
304 for (size_t J = I; J < Tokens.size() && Tokens[J].isQualifier; J++)
305 ReturnTypeRange.setEnd(Tokens[J].T.getLocation());
306 break;
307 }
308 }
309
310 assert(!ReturnTypeRange.getBegin().isMacroID() &&
311 "Return type source range begin must not be a macro");
312 assert(!ReturnTypeRange.getEnd().isMacroID() &&
313 "Return type source range end must not be a macro");
314 return ReturnTypeRange;
315 }
316
317 bool UseTrailingReturnTypeCheck::keepSpecifiers(
318 std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange,
319 const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx,
320 const SourceManager &SM, const LangOptions &LangOpts) {
321 // Check if there are specifiers inside the return type. E.g. unsigned
322 // inline int.
323 const auto *M = dyn_cast<CXXMethodDecl>(&F);
324 if (!F.isConstexpr() && !F.isInlineSpecified() &&
325 F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
326 !Fr && !(M && M->isVirtualAsWritten()))
327 return true;
328
329 // Tokenize return type. If it contains macros which contain a mix of
330 // qualifiers, specifiers and types, give up.
331 llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
332 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
333 if (!MaybeTokens)
334 return false;
335
336 // Find specifiers, remove them from the return type, add them to 'auto'.
337 unsigned int ReturnTypeBeginOffset =
338 SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second;
339 size_t InitialAutoLength = Auto.size();
340 unsigned int DeletedChars = 0;
341 for (ClassifiedToken CT : *MaybeTokens) {
342 if (SM.isBeforeInTranslationUnit(CT.T.getLocation(),
343 ReturnTypeCVRange.getBegin()) ||
344 SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(),
345 CT.T.getLocation()))
346 continue;
347 if (!CT.isSpecifier)
348 continue;
349
350 // Add the token to 'auto' and remove it from the return type, including
351 // any whitespace following the token.
352 unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second;
353 assert(TOffset >= ReturnTypeBeginOffset &&
354 "Token location must be after the beginning of the return type");
355 unsigned int TOffsetInRT = TOffset - ReturnTypeBeginOffset - DeletedChars;
356 unsigned int TLengthWithWS = CT.T.getLength();
357 while (TOffsetInRT + TLengthWithWS < ReturnType.size() &&
358 std::isspace(ReturnType[TOffsetInRT + TLengthWithWS]))
359 TLengthWithWS++;
360 std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS);
361 if (!std::isspace(Specifier.back()))
362 Specifier.push_back(' ');
363 Auto.insert(Auto.size() - InitialAutoLength, Specifier);
364 ReturnType.erase(TOffsetInRT, TLengthWithWS);
365 DeletedChars += TLengthWithWS;
366 }
367
368 return true;
369 }
370
371 void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
372 if (!getLangOpts().CPlusPlus11)
373 return;
374
375 auto F = functionDecl(unless(anyOf(hasTrailingReturn(), returns(voidType()),
376 returns(autoType()), cxxConversionDecl(),
377 cxxMethodDecl(isImplicit()))))
378 .bind("Func");
379
380 Finder->addMatcher(F, this);
381 Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
382 }
383
384 void UseTrailingReturnTypeCheck::registerPPCallbacks(
385 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
386 this->PP = PP;
387 }
388
389 void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
390 assert(PP && "Expected registerPPCallbacks() to have been called before so "
391 "preprocessor is available");
392
393 const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
394 const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
395 assert(F && "Matcher is expected to find only FunctionDecls");
396
397 if (F->getLocation().isInvalid())
398 return;
399
400 // TODO: implement those
401 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
402 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
403 F->getDeclaredReturnType()->isMemberPointerType() ||
404 F->getDeclaredReturnType()->getAs<DecltypeType>() != nullptr) {
405 diag(F->getLocation(), Message);
406 return;
407 }
408
409 const ASTContext &Ctx = *Result.Context;
410 const SourceManager &SM = *Result.SourceManager;
411 const LangOptions &LangOpts = getLangOpts();
412
413 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
414 if (!TSI)
415 return;
416
417 FunctionTypeLoc FTL =
418 TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
419 if (!FTL) {
420 // FIXME: This may happen if we have __attribute__((...)) on the function.
421 // We abort for now. Remove this when the function type location gets
422 // available in clang.
423 diag(F->getLocation(), Message);
424 return;
425 }
426
427 SourceLocation InsertionLoc =
428 findTrailingReturnTypeSourceLocation(*F, FTL, Ctx, SM, LangOpts);
429 if (InsertionLoc.isInvalid()) {
430 diag(F->getLocation(), Message);
431 return;
432 }
433
434 // Using the declared return type via F->getDeclaredReturnType().getAsString()
435 // discards user formatting and order of const, volatile, type, whitespace,
436 // space before & ... .
437 SourceRange ReturnTypeCVRange =
438 findReturnTypeAndCVSourceRange(*F, Ctx, SM, LangOpts);
439 if (ReturnTypeCVRange.isInvalid())
440 return;
441
442 // Check if unqualified names in the return type conflict with other entities
443 // after the rewrite.
444 // FIXME: this could be done better, by performing a lookup of all
445 // unqualified names in the return type in the scope of the function. If the
446 // lookup finds a different entity than the original entity identified by the
447 // name, then we can either not perform a rewrite or explicitely qualify the
448 // entity. Such entities could be function parameter names, (inherited) class
449 // members, template parameters, etc.
450 UnqualNameVisitor UNV{*F};
451 UNV.TraverseTypeLoc(FTL.getReturnLoc());
452 if (UNV.Collision) {
453 diag(F->getLocation(), Message);
454 return;
455 }
456
457 SourceLocation ReturnTypeEnd =
458 Lexer::getLocForEndOfToken(ReturnTypeCVRange.getEnd(), 0, SM, LangOpts);
459 StringRef CharAfterReturnType = Lexer::getSourceText(
460 CharSourceRange::getCharRange(ReturnTypeEnd,
461 ReturnTypeEnd.getLocWithOffset(1)),
462 SM, LangOpts);
463 bool NeedSpaceAfterAuto =
464 CharAfterReturnType.empty() || !std::isspace(CharAfterReturnType[0]);
465
466 std::string Auto = NeedSpaceAfterAuto ? "auto " : "auto";
467 std::string ReturnType = tooling::fixit::getText(ReturnTypeCVRange, Ctx);
468 keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM,
469 LangOpts);
470
471 diag(F->getLocation(), Message)
472 << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
473 << FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
474 }
475
476 } // namespace modernize
477 } // namespace tidy
478 } // namespace clang
Loc
SourceLocation Loc
&#39;#&#39; location in the include directive
Definition: IncludeOrderCheck.cpp:37
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::modernize::expandIfMacroId
static SourceLocation expandIfMacroId(SourceLocation Loc, const SourceManager &SM)
Definition: UseTrailingReturnTypeCheck.cpp:104
clang::tidy::modernize::ClassifiedToken::isSpecifier
bool isSpecifier
Definition: UseTrailingReturnTypeCheck.h:22
clang::doc::Record
llvm::SmallVector< uint64_t, 1024 > Record
Definition: BitcodeReader.cpp:18
clang::tidy::modernize::IsSpecifier
static bool IsSpecifier(Token T)
Definition: UseTrailingReturnTypeCheck.cpp:160
clang::tidy::modernize::Message
constexpr llvm::StringLiteral Message
Definition: UseTrailingReturnTypeCheck.cpp:101
UseTrailingReturnTypeCheck.h
clang::tidy::modernize::IsCVR
static bool IsCVR(Token T)
Definition: UseTrailingReturnTypeCheck.cpp:156
Ctx
Context Ctx
Definition: TUScheduler.cpp:237
clang::tidy::modernize::hasAnyNestedLocalQualifiers
static bool hasAnyNestedLocalQualifiers(QualType Type)
Definition: UseTrailingReturnTypeCheck.cpp:249
clang::tidy::modernize::ClassifiedToken
Definition: UseTrailingReturnTypeCheck.h:19
clang::tidy::modernize::classifyToken
static llvm::Optional< ClassifiedToken > classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok)
Definition: UseTrailingReturnTypeCheck.cpp:166
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:27
ReturnType
std::string ReturnType
Definition: CodeComplete.cpp:395
Info
FunctionInfo Info
Definition: FunctionSizeCheck.cpp:120
clang::tidy::modernize::ClassifiedToken::isQualifier
bool isQualifier
Definition: UseTrailingReturnTypeCheck.h:21
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::bugprone::PP
static Preprocessor * PP
Definition: BadSignalToKillThreadCheck.cpp:28
clang::tidy::modernize::ClassifiedToken::T
Token T
Definition: UseTrailingReturnTypeCheck.h:20
Collision
bool Collision
Definition: UseTrailingReturnTypeCheck.cpp:28
Type
NodeType Type
Definition: HTMLGenerator.cpp:73