blob: f992e9de37f536711551d745306e83c85cfc7936 [file] [log] [blame]
//===--- UseNodiscardCheck.cpp - clang-tidy -------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "UseNodiscardCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace modernize {
static bool doesNoDiscardMacroExist(ASTContext &Context,
const llvm::StringRef &MacroId) {
// Don't check for the Macro existence if we are using an attribute
// either a C++17 standard attribute or pre C++17 syntax
if (MacroId.startswith("[[") || MacroId.startswith("__attribute__"))
return true;
// Otherwise look up the macro name in the context to see if its defined.
return Context.Idents.get(MacroId).hasMacroDefinition();
}
namespace {
AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
// Don't put ``[[nodiscard]]`` in front of operators.
return Node.isOverloadedOperator();
}
AST_MATCHER(CXXMethodDecl, isConversionOperator) {
// Don't put ``[[nodiscard]]`` in front of a conversion decl
// like operator bool().
return isa<CXXConversionDecl>(Node);
}
AST_MATCHER(CXXMethodDecl, hasClassMutableFields) {
// Don't put ``[[nodiscard]]`` on functions on classes with
// mutable member variables.
return Node.getParent()->hasMutableFields();
}
AST_MATCHER(ParmVarDecl, hasParameterPack) {
// Don't put ``[[nodiscard]]`` on functions with parameter pack arguments.
return Node.isParameterPack();
}
AST_MATCHER(CXXMethodDecl, hasTemplateReturnType) {
// Don't put ``[[nodiscard]]`` in front of functions returning a template
// type.
return Node.getReturnType()->isTemplateTypeParmType() ||
Node.getReturnType()->isInstantiationDependentType();
}
AST_MATCHER(CXXMethodDecl, isDefinitionOrInline) {
// A function definition, with optional inline but not the declaration.
return !(Node.isThisDeclarationADefinition() && Node.isOutOfLine());
}
AST_MATCHER(QualType, isInstantiationDependentType) {
return Node->isInstantiationDependentType();
}
AST_MATCHER(QualType, isNonConstReferenceOrPointer) {
// If the function has any non-const-reference arguments
// bool foo(A &a)
// or pointer arguments
// bool foo(A*)
// then they may not care about the return value because of passing data
// via the arguments.
return (Node->isTemplateTypeParmType() || Node->isPointerType() ||
(Node->isReferenceType() &&
!Node.getNonReferenceType().isConstQualified()) ||
Node->isInstantiationDependentType());
}
} // namespace
UseNodiscardCheck::UseNodiscardCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
NoDiscardMacro(Options.get("ReplacementString", "[[nodiscard]]")) {}
void UseNodiscardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "ReplacementString", NoDiscardMacro);
}
void UseNodiscardCheck::registerMatchers(MatchFinder *Finder) {
auto FunctionObj =
cxxRecordDecl(hasAnyName("::std::function", "::boost::function"));
// Find all non-void const methods which have not already been marked to
// warn on unused result.
Finder->addMatcher(
cxxMethodDecl(
allOf(isConst(), isDefinitionOrInline(),
unless(anyOf(
returns(voidType()),
returns(hasDeclaration(decl(hasAttr(clang::attr::WarnUnusedResult)))),
isNoReturn(), isOverloadedOperator(),
isVariadic(), hasTemplateReturnType(),
hasClassMutableFields(), isConversionOperator(),
hasAttr(clang::attr::WarnUnusedResult),
hasType(isInstantiationDependentType()),
hasAnyParameter(anyOf(
parmVarDecl(anyOf(hasType(FunctionObj),
hasType(references(FunctionObj)))),
hasType(isNonConstReferenceOrPointer()),
hasParameterPack()))))))
.bind("no_discard"),
this);
}
void UseNodiscardCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXMethodDecl>("no_discard");
// Don't make replacements if the location is invalid or in a macro.
SourceLocation Loc = MatchedDecl->getLocation();
if (Loc.isInvalid() || Loc.isMacroID())
return;
SourceLocation RetLoc = MatchedDecl->getInnerLocStart();
ASTContext &Context = *Result.Context;
auto Diag = diag(RetLoc, "function %0 should be marked %1")
<< MatchedDecl << NoDiscardMacro;
// Check for the existence of the keyword being used as the ``[[nodiscard]]``.
if (!doesNoDiscardMacroExist(Context, NoDiscardMacro))
return;
// Possible false positives include:
// 1. A const member function which returns a variable which is ignored
// but performs some external I/O operation and the return value could be
// ignored.
Diag << FixItHint::CreateInsertion(RetLoc, NoDiscardMacro + " ");
}
bool UseNodiscardCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
// If we use ``[[nodiscard]]`` attribute, we require at least C++17. Use a
// macro or ``__attribute__`` with pre c++17 compilers by using
// ReplacementString option.
if (NoDiscardMacro == "[[nodiscard]]")
return LangOpts.CPlusPlus17;
return LangOpts.CPlusPlus;
}
} // namespace modernize
} // namespace tidy
} // namespace clang