clang-tools  9.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/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
SourceLocation Loc
&#39;#&#39; location in the include directive
static SourceLocation expandIfMacroId(SourceLocation Loc, const SourceManager &SM)
llvm::SmallVector< uint64_t, 1024 > Record
constexpr llvm::StringLiteral Message
Context Ctx
static bool hasAnyNestedLocalQualifiers(QualType Type)
static llvm::Optional< ClassifiedToken > classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok)
StringRef Tokens
static constexpr llvm::StringLiteral Name
std::string ReturnType
FunctionInfo Info
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
Definition: Rename.cpp:36
NodeType Type