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