1 //===--- ConstReturnTypeCheck.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 "ConstReturnTypeCheck.h"
10 #include "../utils/LexerUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Basic/SourceLocation.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/Optional.h"
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace readability {
22 
23 // Finds the location of the qualifying `const` token in the `FunctionDecl`'s
24 // return type. Returns `None` when the return type is not `const`-qualified or
25 // `const` does not appear in `Def`'s source, like when the type is an alias or
26 // a macro.
27 static llvm::Optional<Token>
findConstToRemove(const FunctionDecl * Def,const MatchFinder::MatchResult & Result)28 findConstToRemove(const FunctionDecl *Def,
29                   const MatchFinder::MatchResult &Result) {
30   if (!Def->getReturnType().isLocalConstQualified())
31     return None;
32 
33   // Get the begin location for the function name, including any qualifiers
34   // written in the source (for out-of-line declarations). A FunctionDecl's
35   // "location" is the start of its name, so, when the name is unqualified, we
36   // use `getLocation()`.
37   SourceLocation NameBeginLoc = Def->getQualifier()
38                                     ? Def->getQualifierLoc().getBeginLoc()
39                                     : Def->getLocation();
40   // Since either of the locs can be in a macro, use `makeFileCharRange` to be
41   // sure that we have a consistent `CharSourceRange`, located entirely in the
42   // source file.
43   CharSourceRange FileRange = Lexer::makeFileCharRange(
44       CharSourceRange::getCharRange(Def->getBeginLoc(), NameBeginLoc),
45       *Result.SourceManager, Result.Context->getLangOpts());
46 
47   if (FileRange.isInvalid())
48     return None;
49 
50   return utils::lexer::getQualifyingToken(
51       tok::kw_const, FileRange, *Result.Context, *Result.SourceManager);
52 }
53 
54 namespace {
55 
AST_MATCHER(QualType,isLocalConstQualified)56 AST_MATCHER(QualType, isLocalConstQualified) {
57   return Node.isLocalConstQualified();
58 }
59 
AST_MATCHER(QualType,isTypeOfType)60 AST_MATCHER(QualType, isTypeOfType) {
61   return isa<TypeOfType>(Node.getTypePtr());
62 }
63 
AST_MATCHER(QualType,isTypeOfExprType)64 AST_MATCHER(QualType, isTypeOfExprType) {
65   return isa<TypeOfExprType>(Node.getTypePtr());
66 }
67 
68 struct CheckResult {
69   // Source range of the relevant `const` token in the definition being checked.
70   CharSourceRange ConstRange;
71 
72   // FixItHints associated with the definition being checked.
73   llvm::SmallVector<clang::FixItHint, 4> Hints;
74 
75   // Locations of any declarations that could not be fixed.
76   llvm::SmallVector<clang::SourceLocation, 4> DeclLocs;
77 };
78 
79 } // namespace
80 
81 // Does the actual work of the check.
checkDef(const clang::FunctionDecl * Def,const MatchFinder::MatchResult & MatchResult)82 static CheckResult checkDef(const clang::FunctionDecl *Def,
83                             const MatchFinder::MatchResult &MatchResult) {
84   CheckResult Result;
85   llvm::Optional<Token> Tok = findConstToRemove(Def, MatchResult);
86   if (!Tok)
87     return Result;
88 
89   Result.ConstRange =
90       CharSourceRange::getCharRange(Tok->getLocation(), Tok->getEndLoc());
91   Result.Hints.push_back(FixItHint::CreateRemoval(Result.ConstRange));
92 
93   // Fix the definition and any visible declarations, but don't warn
94   // separately for each declaration. Instead, associate all fixes with the
95   // single warning at the definition.
96   for (const FunctionDecl *Decl = Def->getPreviousDecl(); Decl != nullptr;
97        Decl = Decl->getPreviousDecl()) {
98     if (llvm::Optional<Token> T = findConstToRemove(Decl, MatchResult))
99       Result.Hints.push_back(FixItHint::CreateRemoval(
100           CharSourceRange::getCharRange(T->getLocation(), T->getEndLoc())));
101     else
102       // `getInnerLocStart` gives the start of the return type.
103       Result.DeclLocs.push_back(Decl->getInnerLocStart());
104   }
105   return Result;
106 }
107 
registerMatchers(MatchFinder * Finder)108 void ConstReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
109   // Find all function definitions for which the return types are `const`
110   // qualified, ignoring decltype types.
111   auto NonLocalConstType = qualType(
112       unless(isLocalConstQualified()),
113       anyOf(decltypeType(), autoType(), isTypeOfType(), isTypeOfExprType()));
114   Finder->addMatcher(
115       functionDecl(
116           returns(allOf(isConstQualified(), unless(NonLocalConstType))),
117           anyOf(isDefinition(), cxxMethodDecl(isPure())))
118           .bind("func"),
119       this);
120 }
121 
check(const MatchFinder::MatchResult & Result)122 void ConstReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
123   const auto *Def = Result.Nodes.getNodeAs<FunctionDecl>("func");
124   CheckResult CR = checkDef(Def, Result);
125   {
126     // Clang only supports one in-flight diagnostic at a time. So, delimit the
127     // scope of `Diagnostic` to allow further diagnostics after the scope.  We
128     // use `getInnerLocStart` to get the start of the return type.
129     DiagnosticBuilder Diagnostic =
130         diag(Def->getInnerLocStart(),
131              "return type %0 is 'const'-qualified at the top level, which may "
132              "reduce code readability without improving const correctness")
133         << Def->getReturnType();
134     if (CR.ConstRange.isValid())
135       Diagnostic << CR.ConstRange;
136 
137     // Do not propose fixes for virtual function.
138     const auto *Method = dyn_cast<CXXMethodDecl>(Def);
139     if (Method && Method->isVirtual())
140       return;
141 
142     for (auto &Hint : CR.Hints)
143       Diagnostic << Hint;
144   }
145   for (auto Loc : CR.DeclLocs)
146     diag(Loc, "could not transform this declaration", DiagnosticIDs::Note);
147 }
148 
149 } // namespace readability
150 } // namespace tidy
151 } // namespace clang
152