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