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