1 | //===--- UseNodiscardCheck.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 "UseNodiscardCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/AST/Decl.h" |
12 | #include "clang/AST/Type.h" |
13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
14 | |
15 | using namespace clang::ast_matchers; |
16 | |
17 | namespace clang::tidy::modernize { |
18 | |
19 | static bool doesNoDiscardMacroExist(ASTContext &Context, |
20 | const llvm::StringRef &MacroId) { |
21 | // Don't check for the Macro existence if we are using an attribute |
22 | // either a C++17 standard attribute or pre C++17 syntax |
23 | if (MacroId.starts_with(Prefix: "[[" ) || MacroId.starts_with(Prefix: "__attribute__" )) |
24 | return true; |
25 | |
26 | // Otherwise look up the macro name in the context to see if its defined. |
27 | return Context.Idents.get(Name: MacroId).hasMacroDefinition(); |
28 | } |
29 | |
30 | namespace { |
31 | AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { |
32 | // Don't put ``[[nodiscard]]`` in front of operators. |
33 | return Node.isOverloadedOperator(); |
34 | } |
35 | AST_MATCHER(CXXMethodDecl, isConversionOperator) { |
36 | // Don't put ``[[nodiscard]]`` in front of a conversion decl |
37 | // like operator bool(). |
38 | return isa<CXXConversionDecl>(Val: Node); |
39 | } |
40 | AST_MATCHER(CXXMethodDecl, hasClassMutableFields) { |
41 | // Don't put ``[[nodiscard]]`` on functions on classes with |
42 | // mutable member variables. |
43 | return Node.getParent()->hasMutableFields(); |
44 | } |
45 | AST_MATCHER(ParmVarDecl, hasParameterPack) { |
46 | // Don't put ``[[nodiscard]]`` on functions with parameter pack arguments. |
47 | return Node.isParameterPack(); |
48 | } |
49 | AST_MATCHER(CXXMethodDecl, hasTemplateReturnType) { |
50 | // Don't put ``[[nodiscard]]`` in front of functions returning a template |
51 | // type. |
52 | return Node.getReturnType()->isTemplateTypeParmType() || |
53 | Node.getReturnType()->isInstantiationDependentType(); |
54 | } |
55 | AST_MATCHER(CXXMethodDecl, isDefinitionOrInline) { |
56 | // A function definition, with optional inline but not the declaration. |
57 | return !(Node.isThisDeclarationADefinition() && Node.isOutOfLine()); |
58 | } |
59 | AST_MATCHER(QualType, isInstantiationDependentType) { |
60 | return Node->isInstantiationDependentType(); |
61 | } |
62 | AST_MATCHER(QualType, isNonConstReferenceOrPointer) { |
63 | // If the function has any non-const-reference arguments |
64 | // bool foo(A &a) |
65 | // or pointer arguments |
66 | // bool foo(A*) |
67 | // then they may not care about the return value because of passing data |
68 | // via the arguments. |
69 | return (Node->isTemplateTypeParmType() || Node->isPointerType() || |
70 | (Node->isReferenceType() && |
71 | !Node.getNonReferenceType().isConstQualified()) || |
72 | Node->isInstantiationDependentType()); |
73 | } |
74 | } // namespace |
75 | |
76 | UseNodiscardCheck::UseNodiscardCheck(StringRef Name, ClangTidyContext *Context) |
77 | : ClangTidyCheck(Name, Context), |
78 | NoDiscardMacro(Options.get(LocalName: "ReplacementString" , Default: "[[nodiscard]]" )) {} |
79 | |
80 | void UseNodiscardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
81 | Options.store(Options&: Opts, LocalName: "ReplacementString" , Value: NoDiscardMacro); |
82 | } |
83 | |
84 | void UseNodiscardCheck::registerMatchers(MatchFinder *Finder) { |
85 | auto FunctionObj = |
86 | cxxRecordDecl(hasAnyName("::std::function" , "::boost::function" )); |
87 | |
88 | // Find all non-void const methods which have not already been marked to |
89 | // warn on unused result. |
90 | Finder->addMatcher( |
91 | cxxMethodDecl( |
92 | isConst(), isDefinitionOrInline(), |
93 | unless(anyOf( |
94 | returns(voidType()), |
95 | returns( |
96 | hasDeclaration(decl(hasAttr(clang::attr::WarnUnusedResult)))), |
97 | isNoReturn(), isOverloadedOperator(), isVariadic(), |
98 | hasTemplateReturnType(), hasClassMutableFields(), |
99 | isConversionOperator(), hasAttr(clang::attr::WarnUnusedResult), |
100 | hasType(isInstantiationDependentType()), |
101 | hasAnyParameter( |
102 | anyOf(parmVarDecl(anyOf(hasType(FunctionObj), |
103 | hasType(references(FunctionObj)))), |
104 | hasType(isNonConstReferenceOrPointer()), |
105 | hasParameterPack()))))) |
106 | .bind("no_discard" ), |
107 | this); |
108 | } |
109 | |
110 | void UseNodiscardCheck::check(const MatchFinder::MatchResult &Result) { |
111 | const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "no_discard" ); |
112 | // Don't make replacements if the location is invalid or in a macro. |
113 | SourceLocation Loc = MatchedDecl->getLocation(); |
114 | if (Loc.isInvalid() || Loc.isMacroID()) |
115 | return; |
116 | |
117 | SourceLocation RetLoc = MatchedDecl->getInnerLocStart(); |
118 | |
119 | ASTContext &Context = *Result.Context; |
120 | |
121 | auto Diag = diag(Loc: RetLoc, Description: "function %0 should be marked %1" ) |
122 | << MatchedDecl << NoDiscardMacro; |
123 | |
124 | // Check for the existence of the keyword being used as the ``[[nodiscard]]``. |
125 | if (!doesNoDiscardMacroExist(Context, MacroId: NoDiscardMacro)) |
126 | return; |
127 | |
128 | // Possible false positives include: |
129 | // 1. A const member function which returns a variable which is ignored |
130 | // but performs some external I/O operation and the return value could be |
131 | // ignored. |
132 | Diag << FixItHint::CreateInsertion(InsertionLoc: RetLoc, Code: (NoDiscardMacro + " " ).str()); |
133 | } |
134 | |
135 | bool UseNodiscardCheck::isLanguageVersionSupported( |
136 | const LangOptions &LangOpts) const { |
137 | // If we use ``[[nodiscard]]`` attribute, we require at least C++17. Use a |
138 | // macro or ``__attribute__`` with pre c++17 compilers by using |
139 | // ReplacementString option. |
140 | |
141 | if (NoDiscardMacro == "[[nodiscard]]" ) |
142 | return LangOpts.CPlusPlus17; |
143 | |
144 | return LangOpts.CPlusPlus; |
145 | } |
146 | |
147 | } // namespace clang::tidy::modernize |
148 | |