clang-tools  11.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 
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:
28  UnqualNameVisitor(const FunctionDecl &F) : F(F) {}
29 
30  bool Collision = false;
31 
32  bool shouldWalkTypesOfTypeLocs() const { return false; }
33 
34  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 
45  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  default:
70  break;
71  }
72  }
73 
74  return RecursiveASTVisitor<UnqualNameVisitor>::TraverseTypeLoc(TL);
75  }
76 
77  // Replace the base method in order to call ower own
78  // TraverseTypeLoc().
79  bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL) {
80  return TraverseTypeLoc(TL.getUnqualifiedLoc());
81  }
82 
83  // Replace the base version to inform TraverseTypeLoc that the type is
84  // elaborated.
85  bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc TL) {
86  if (TL.getQualifierLoc() &&
87  !TraverseNestedNameSpecifierLoc(TL.getQualifierLoc()))
88  return false;
89  return TraverseTypeLoc(TL.getNamedTypeLoc(), true);
90  }
91 
92  bool VisitDeclRefExpr(DeclRefExpr *S) {
93  DeclarationName Name = S->getNameInfo().getName();
94  return S->getQualifierLoc() || !Name.isIdentifier() ||
95  !VisitUnqualName(Name.getAsIdentifierInfo()->getName());
96  }
97 
98 private:
99  const FunctionDecl &F;
100 };
101 } // namespace
102 
103 constexpr llvm::StringLiteral Message =
104  "use a trailing return type for this function";
105 
106 static SourceLocation expandIfMacroId(SourceLocation Loc,
107  const SourceManager &SM) {
108  if (Loc.isMacroID())
109  Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
110  assert(!Loc.isMacroID() &&
111  "SourceLocation must not be a macro ID after recursive expansion");
112  return Loc;
113 }
114 
115 SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation(
116  const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
117  const SourceManager &SM, const LangOptions &LangOpts) {
118  // We start with the location of the closing parenthesis.
119  SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
120  if (ExceptionSpecRange.isValid())
121  return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
122  LangOpts);
123 
124  // If the function argument list ends inside of a macro, it is dangerous to
125  // start lexing from here - bail out.
126  SourceLocation ClosingParen = FTL.getRParenLoc();
127  if (ClosingParen.isMacroID())
128  return {};
129 
130  SourceLocation Result =
131  Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
132 
133  // Skip subsequent CV and ref qualifiers.
134  std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Result);
135  StringRef File = SM.getBufferData(Loc.first);
136  const char *TokenBegin = File.data() + Loc.second;
137  Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
138  TokenBegin, File.end());
139  Token T;
140  while (!Lexer.LexFromRawLexer(T)) {
141  if (T.is(tok::raw_identifier)) {
142  IdentifierInfo &Info = Ctx.Idents.get(
143  StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
144  T.setIdentifierInfo(&Info);
145  T.setKind(Info.getTokenID());
146  }
147 
148  if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
149  tok::kw_restrict)) {
150  Result = T.getEndLoc();
151  continue;
152  }
153  break;
154  }
155  return Result;
156 }
157 
158 static bool IsCVR(Token T) {
159  return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
160 }
161 
162 static bool IsSpecifier(Token T) {
163  return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
164  tok::kw_static, tok::kw_friend, tok::kw_virtual);
165 }
166 
167 static llvm::Optional<ClassifiedToken>
168 classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) {
169  ClassifiedToken CT;
170  CT.T = Tok;
171  CT.isQualifier = true;
172  CT.isSpecifier = true;
173  bool ContainsQualifiers = false;
174  bool ContainsSpecifiers = false;
175  bool ContainsSomethingElse = false;
176 
177  Token End;
178  End.startToken();
179  End.setKind(tok::eof);
180  SmallVector<Token, 2> Stream{Tok, End};
181 
182  // FIXME: do not report these token to Preprocessor.TokenWatcher.
183  PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
184  while (true) {
185  Token T;
186  PP.Lex(T);
187  if (T.is(tok::eof))
188  break;
189 
190  bool Qual = IsCVR(T);
191  bool Spec = IsSpecifier(T);
192  CT.isQualifier &= Qual;
193  CT.isSpecifier &= Spec;
194  ContainsQualifiers |= Qual;
195  ContainsSpecifiers |= Spec;
196  ContainsSomethingElse |= !Qual && !Spec;
197  }
198 
199  // If the Token/Macro contains more than one type of tokens, we would need
200  // to split the macro in order to move parts to the trailing return type.
201  if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
202  return llvm::None;
203 
204  return CT;
205 }
206 
207 llvm::Optional<SmallVector<ClassifiedToken, 8>>
208 UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
209  const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
210  const LangOptions &LangOpts) {
211  SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM);
212  SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
213 
214  // Create tokens for everything before the name of the function.
215  std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
216  StringRef File = SM.getBufferData(Loc.first);
217  const char *TokenBegin = File.data() + Loc.second;
218  Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
219  TokenBegin, File.end());
220  Token T;
221  SmallVector<ClassifiedToken, 8> ClassifiedTokens;
222  while (!Lexer.LexFromRawLexer(T) &&
223  SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
224  if (T.is(tok::raw_identifier)) {
225  IdentifierInfo &Info = Ctx.Idents.get(
226  StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
227 
228  if (Info.hasMacroDefinition()) {
229  const MacroInfo *MI = PP->getMacroInfo(&Info);
230  if (!MI || MI->isFunctionLike()) {
231  // Cannot handle function style macros.
232  diag(F.getLocation(), Message);
233  return llvm::None;
234  }
235  }
236 
237  T.setIdentifierInfo(&Info);
238  T.setKind(Info.getTokenID());
239  }
240 
241  if (llvm::Optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
242  ClassifiedTokens.push_back(*CT);
243  else {
244  diag(F.getLocation(), Message);
245  return llvm::None;
246  }
247  }
248 
249  return ClassifiedTokens;
250 }
251 
252 static bool hasAnyNestedLocalQualifiers(QualType Type) {
253  bool Result = Type.hasLocalQualifiers();
254  if (Type->isPointerType())
255  Result = Result || hasAnyNestedLocalQualifiers(
256  Type->castAs<PointerType>()->getPointeeType());
257  if (Type->isReferenceType())
258  Result = Result || hasAnyNestedLocalQualifiers(
259  Type->castAs<ReferenceType>()->getPointeeType());
260  return Result;
261 }
262 
263 SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
264  const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
265  const LangOptions &LangOpts) {
266 
267  // We start with the range of the return type and expand to neighboring
268  // qualifiers (const, volatile and restrict).
269  SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
270  if (ReturnTypeRange.isInvalid()) {
271  // Happens if e.g. clang cannot resolve all includes and the return type is
272  // unknown.
273  diag(F.getLocation(), Message);
274  return {};
275  }
276 
277  // If the return type has no local qualifiers, it's source range is accurate.
278  if (!hasAnyNestedLocalQualifiers(F.getReturnType()))
279  return ReturnTypeRange;
280 
281  // Include qualifiers to the left and right of the return type.
282  llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
283  classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
284  if (!MaybeTokens)
285  return {};
286  const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
287 
288  ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM));
289  ReturnTypeRange.setEnd(expandIfMacroId(ReturnTypeRange.getEnd(), SM));
290 
291  bool ExtendedLeft = false;
292  for (size_t I = 0; I < Tokens.size(); I++) {
293  // If we found the beginning of the return type, include left qualifiers.
294  if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
295  ReturnTypeRange.getBegin()) &&
296  !ExtendedLeft) {
297  assert(I <= size_t(std::numeric_limits<int>::max()) &&
298  "Integer overflow detected");
299  for (int J = static_cast<int>(I) - 1; J >= 0 && Tokens[J].isQualifier;
300  J--)
301  ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
302  ExtendedLeft = true;
303  }
304  // If we found the end of the return type, include right qualifiers.
305  if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
306  Tokens[I].T.getLocation())) {
307  for (size_t J = I; J < Tokens.size() && Tokens[J].isQualifier; J++)
308  ReturnTypeRange.setEnd(Tokens[J].T.getLocation());
309  break;
310  }
311  }
312 
313  assert(!ReturnTypeRange.getBegin().isMacroID() &&
314  "Return type source range begin must not be a macro");
315  assert(!ReturnTypeRange.getEnd().isMacroID() &&
316  "Return type source range end must not be a macro");
317  return ReturnTypeRange;
318 }
319 
320 bool UseTrailingReturnTypeCheck::keepSpecifiers(
321  std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange,
322  const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx,
323  const SourceManager &SM, const LangOptions &LangOpts) {
324  // Check if there are specifiers inside the return type. E.g. unsigned
325  // inline int.
326  const auto *M = dyn_cast<CXXMethodDecl>(&F);
327  if (!F.isConstexpr() && !F.isInlineSpecified() &&
328  F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
329  !Fr && !(M && M->isVirtualAsWritten()))
330  return true;
331 
332  // Tokenize return type. If it contains macros which contain a mix of
333  // qualifiers, specifiers and types, give up.
334  llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
335  classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
336  if (!MaybeTokens)
337  return false;
338 
339  // Find specifiers, remove them from the return type, add them to 'auto'.
340  unsigned int ReturnTypeBeginOffset =
341  SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second;
342  size_t InitialAutoLength = Auto.size();
343  unsigned int DeletedChars = 0;
344  for (ClassifiedToken CT : *MaybeTokens) {
345  if (SM.isBeforeInTranslationUnit(CT.T.getLocation(),
346  ReturnTypeCVRange.getBegin()) ||
347  SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(),
348  CT.T.getLocation()))
349  continue;
350  if (!CT.isSpecifier)
351  continue;
352 
353  // Add the token to 'auto' and remove it from the return type, including
354  // any whitespace following the token.
355  unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second;
356  assert(TOffset >= ReturnTypeBeginOffset &&
357  "Token location must be after the beginning of the return type");
358  unsigned int TOffsetInRT = TOffset - ReturnTypeBeginOffset - DeletedChars;
359  unsigned int TLengthWithWS = CT.T.getLength();
360  while (TOffsetInRT + TLengthWithWS < ReturnType.size() &&
361  llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS]))
362  TLengthWithWS++;
363  std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS);
364  if (!llvm::isSpace(Specifier.back()))
365  Specifier.push_back(' ');
366  Auto.insert(Auto.size() - InitialAutoLength, Specifier);
367  ReturnType.erase(TOffsetInRT, TLengthWithWS);
368  DeletedChars += TLengthWithWS;
369  }
370 
371  return true;
372 }
373 
374 void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
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 explicitly 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() || !llvm::isSpace(CharAfterReturnType[0]);
465 
466  std::string Auto = NeedSpaceAfterAuto ? "auto " : "auto";
467  std::string ReturnType =
468  std::string(tooling::fixit::getText(ReturnTypeCVRange, Ctx));
469  keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM,
470  LangOpts);
471 
472  diag(F->getLocation(), Message)
473  << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
474  << FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
475 }
476 
477 } // namespace modernize
478 } // namespace tidy
479 } // namespace clang
clang::tidy::modernize::ClassifiedToken
Definition: UseTrailingReturnTypeCheck.h:20
clang::tidy::modernize::expandIfMacroId
static SourceLocation expandIfMacroId(SourceLocation Loc, const SourceManager &SM)
Definition: UseTrailingReturnTypeCheck.cpp:106
clang::tidy::modernize::classifyToken
static llvm::Optional< ClassifiedToken > classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok)
Definition: UseTrailingReturnTypeCheck.cpp:168
Type
NodeType Type
Definition: HTMLGenerator.cpp:73
Collision
bool Collision
Definition: UseTrailingReturnTypeCheck.cpp:30
clang::clangd::WantDiagnostics::Auto
Diagnostics must not be generated for this snapshot.
clang::tidy::modernize::Message
constexpr llvm::StringLiteral Message
Definition: UseTrailingReturnTypeCheck.cpp:103
Ctx
Context Ctx
Definition: TUScheduler.cpp:324
clang::tidy::modernize::ClassifiedToken::isQualifier
bool isQualifier
Definition: UseTrailingReturnTypeCheck.h:22
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::modernize::ClassifiedToken::T
Token T
Definition: UseTrailingReturnTypeCheck.h:21
UseTrailingReturnTypeCheck.h
clang::tidy::modernize::IsSpecifier
static bool IsSpecifier(Token T)
Definition: UseTrailingReturnTypeCheck.cpp:162
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:27
clang::tidy::modernize::ClassifiedToken::isSpecifier
bool isSpecifier
Definition: UseTrailingReturnTypeCheck.h:23
clang::tidy::bugprone::PP
static Preprocessor * PP
Definition: BadSignalToKillThreadCheck.cpp:29
clang::tidy::modernize::IsCVR
static bool IsCVR(Token T)
Definition: UseTrailingReturnTypeCheck.cpp:158
Info
FunctionInfo Info
Definition: FunctionSizeCheck.cpp:120
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::doc::Record
llvm::SmallVector< uint64_t, 1024 > Record
Definition: BitcodeReader.cpp:18
clang::tidy::modernize::hasAnyNestedLocalQualifiers
static bool hasAnyNestedLocalQualifiers(QualType Type)
Definition: UseTrailingReturnTypeCheck.cpp:252
Loc
SourceLocation Loc
'#' location in the include directive
Definition: IncludeOrderCheck.cpp:37
ReturnType
std::string ReturnType
Definition: CodeComplete.cpp:407