diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt @@ -5,6 +5,7 @@ add_clang_library(clangTidyMiscModule DefinitionsInHeadersCheck.cpp + DiscardedReturnValueCheck.cpp MiscTidyModule.cpp MisleadingBidirectional.cpp MisleadingIdentifier.cpp diff --git a/clang-tools-extra/clang-tidy/misc/DiscardedReturnValueCheck.h b/clang-tools-extra/clang-tidy/misc/DiscardedReturnValueCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/DiscardedReturnValueCheck.h @@ -0,0 +1,68 @@ +//===--- DiscardedReturnValueCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DISCARDEDRETURNVALUECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DISCARDEDRETURNVALUECHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallPtrSet.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Flags function calls which return value is discarded if most of the +/// other calls of the function consume the return value. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-discarded-return-value.html +class DiscardedReturnValueCheck : public ClangTidyCheck { +public: + struct Function { + std::size_t ConsumedCalls; + std::size_t TotalCalls; + llvm::SmallPtrSet DiscardedCEs; + + /// Returns ConsumedCalls / TotalCalls expressed as a whole percentage. + std::uint8_t ratio() const; + }; + using FunctionMapTy = llvm::DenseMap; + + DiscardedReturnValueCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void onStartOfTranslationUnit() override; + void onEndOfTranslationUnit() override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// The threshold (expressed as a whole percentage) the calls to a function + /// must be consumed for discarded calls to be warned about. A threshold of + /// 80 means that if 8 calls out of 10 calls (80%) to a function is consumed, + /// the remaining 2 will be warned. + const std::uint8_t ConsumeThreshold; + + /// Stores AST nodes which we have observed to be consuming calls. + /// (This is a helper data structure to prevent matchers matching consuming + /// contexts firing multiple times and messing up the statistics created.) + llvm::DenseMap> ConsumedCalls; + + FunctionMapTy CallMap; + + void registerCall(const CallExpr *CE, const FunctionDecl *FD, + const void *ConsumingContext); + void diagnose(const FunctionDecl *FD, const Function &F); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DISCARDEDRETURNVALUECHECK_H diff --git a/clang-tools-extra/clang-tidy/misc/DiscardedReturnValueCheck.cpp b/clang-tools-extra/clang-tidy/misc/DiscardedReturnValueCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/DiscardedReturnValueCheck.cpp @@ -0,0 +1,233 @@ +//===--- DiscardedReturnValueCheck.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 "DiscardedReturnValueCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::tidy::misc; + +static constexpr llvm::StringLiteral Call = "call"; +static constexpr llvm::StringLiteral Consume = "consume"; + +namespace clang { +namespace tidy { +namespace misc { + +std::uint8_t DiscardedReturnValueCheck::Function::ratio() const { + if (TotalCalls == 0) + return 0; + + std::uint8_t R = static_cast(ConsumedCalls * 100) / TotalCalls; + assert(R <= 100 && "Invalid percentage, maybe bogus compacted data?"); + return R; +} + +void DiscardedReturnValueCheck::registerCall(const CallExpr *CE, + const FunctionDecl *FD, + const void *ConsumingContext) { + assert(CE && FD); + FD = FD->getCanonicalDecl(); + Function &Data = CallMap.try_emplace(FD, Function{0, 0, {}}).first->second; + + if (ConsumingContext) { + using ConsumeVecTy = decltype(ConsumedCalls)::mapped_type; + ConsumeVecTy &ConsumeVec = + ConsumedCalls.try_emplace(CE, ConsumeVecTy{}).first->second; + if (llvm::find(ConsumeVec, ConsumingContext) != ConsumeVec.end()) + return; + ConsumeVec.emplace_back(ConsumingContext); + } else + Data.DiscardedCEs.insert(CE); + + if (ConsumingContext) + ++Data.ConsumedCalls; + ++Data.TotalCalls; +} + +void DiscardedReturnValueCheck::diagnose(const FunctionDecl *FD, + const Function &F) { + if (F.ratio() < ConsumeThreshold) + return; + + for (const CallExpr *Call : F.DiscardedCEs) { + diag(Call->getExprLoc(), + "return value of %0 is used in most calls, but not in this one", + DiagnosticIDs::Warning) + << *FD << F.ratio() << F.ConsumedCalls << F.TotalCalls; + + diag(Call->getExprLoc(), + "value consumed or checked in %0%% (%1 out of %2) of cases", + DiagnosticIDs::Note) + << F.ratio() << F.ConsumedCalls << F.TotalCalls; + } +} + +DiscardedReturnValueCheck::DiscardedReturnValueCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ConsumeThreshold(Options.get("ConsumeThreshold", 80)) {} + +void DiscardedReturnValueCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ConsumeThreshold", ConsumeThreshold); +} + +void DiscardedReturnValueCheck::onStartOfTranslationUnit() { + ConsumedCalls.clear(); + CallMap.clear(); +} + +void DiscardedReturnValueCheck::onEndOfTranslationUnit() { + // Once everything had been counted, emit the results. + for (const auto &Call : CallMap) + diagnose(Call.first, Call.second); +} + +namespace { + +AST_MATCHER_P(StaticAssertDecl, hasAssertExpr, + ast_matchers::internal::Matcher, InnerMatcher) { + return InnerMatcher.matches(*Node.getAssertExpr(), Finder, Builder); +} + +AST_MATCHER_P(DecltypeType, hasUnderlyingExpr, + ast_matchers::internal::Matcher, InnerMatcher) { + return InnerMatcher.matches(*Node.getUnderlyingExpr(), Finder, Builder); +} + +// FIXME: Move ASTMatcher library. +// Note: Taken from UnusedUsingDeclsCheck. +AST_POLYMORPHIC_MATCHER_P( + forEachTemplateArgument, + AST_POLYMORPHIC_SUPPORTED_TYPES(ClassTemplateSpecializationDecl, + TemplateSpecializationType, FunctionDecl), + clang::ast_matchers::internal::Matcher, InnerMatcher) { + ArrayRef TemplateArgs = + clang::ast_matchers::internal::getTemplateSpecializationArgs(Node); + clang::ast_matchers::internal::BoundNodesTreeBuilder Result; + bool Matched = false; + for (const auto &Arg : TemplateArgs) { + clang::ast_matchers::internal::BoundNodesTreeBuilder ArgBuilder(*Builder); + if (InnerMatcher.matches(Arg, Finder, &ArgBuilder)) { + Matched = true; + Result.addMatch(ArgBuilder); + } + } + *Builder = std::move(Result); + return Matched; +} + +} // namespace + +void DiscardedReturnValueCheck::registerMatchers(MatchFinder *Finder) { + static const auto Call = + callExpr(hasDeclaration(functionDecl(unless(anyOf( + returns(voidType()), hasAttr(attr::WarnUnusedResult))))), + unless(cxxOperatorCallExpr( + unless(anyOf(hasAnyOverloadedOperatorName("()", "[]"), + allOf(hasAnyOverloadedOperatorName("*", "&"), + argumentCountIs(1))))))) + .bind(::Call); + + static const auto StaticAssert = staticAssertDecl(hasAssertExpr(Call)); + static const auto VarDecl = decl(anyOf(varDecl(hasInitializer(Call)), + fieldDecl(hasInClassInitializer(Call)), + enumConstantDecl(has(Call)))); + static const auto CtorInits = + cxxConstructorDecl(forEachConstructorInitializer(withInitializer(Call))); + + static const auto ExplicitCast = explicitCastExpr(hasSourceExpression(Call)); + static const auto New = + expr(anyOf(initListExpr(forEach(Call)), cxxNewExpr(forEach(Call)))); + static const auto Delete = cxxDeleteExpr(has(Call)); + static const auto Argument = expr(anyOf( + invocation(forEach(Call)), cxxUnresolvedConstructExpr(forEach(Call)), + parenListExpr(forEach(Call)))); + static const auto Unary = expr(anyOf( + unaryOperator(hasUnaryOperand(Call)), unaryExprOrTypeTraitExpr(has(Call)), + arraySubscriptExpr(hasIndex(Call)), + cxxOperatorCallExpr(hasUnaryOperand(Call)))); + static const auto Dereference = expr( + anyOf(arraySubscriptExpr(hasBase(Call)), unresolvedMemberExpr(has(Call)), + memberExpr(hasObjectExpression(Call)))); + static const auto BinaryLHS = + anyOf(binaryOperator(hasLHS(Call)), + conditionalOperator(hasTrueExpression(Call)), + binaryConditionalOperator(hasTrueExpression(Call))); + static const auto BinaryRHS = + anyOf(binaryOperator(hasRHS(Call)), + conditionalOperator(hasFalseExpression(Call)), + binaryConditionalOperator(hasFalseExpression(Call))); + static const auto If = + anyOf(ifStmt(hasCondition(Call)), conditionalOperator(hasCondition(Call)), + binaryConditionalOperator(hasCondition(Call))); + static const auto While = + stmt(anyOf(whileStmt(hasCondition(Call)), doStmt(hasCondition(Call)))); + static const auto For = anyOf( + forStmt(eachOf(hasLoopInit(findAll(Call)), hasCondition(findAll(Call)), + hasIncrement(findAll(Call)))), + cxxForRangeStmt(hasRangeInit(findAll(Call)))); + static const auto Switch = switchStmt(hasCondition(Call)); + static const auto Return = returnStmt(hasReturnValue(Call)); + + static const auto Decltype = decltypeType(hasUnderlyingExpr(Call)); + static const auto TemplateArg = + templateSpecializationType(forEachTemplateArgument(isExpr(Call))); + static const auto VLA = variableArrayType(hasSizeExpr(Call)); + + // Match consumed (using the return value) function calls. + Finder->addMatcher( + traverse( + TK_IgnoreUnlessSpelledInSource, + stmt(eachOf(ExplicitCast, New, Delete, Argument, Unary, Dereference, + BinaryLHS, BinaryRHS, If, While, For, Switch, Return))) + .bind(Consume), + this); + Finder->addMatcher(traverse(TK_IgnoreUnlessSpelledInSource, + decl(eachOf(StaticAssert, VarDecl, CtorInits))) + .bind(Consume), + this); + Finder->addMatcher(traverse(TK_IgnoreUnlessSpelledInSource, + type(eachOf(Decltype, TemplateArg, VLA))) + .bind(Consume), + this); + + // Matches discarded function calls. + Finder->addMatcher(traverse(TK_IgnoreUnlessSpelledInSource, Call), this); +} + +void DiscardedReturnValueCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CE = Result.Nodes.getNodeAs(Call); + assert(CE && "Bad matcher"); + + // There are two matchers registered, one that matches the larger tree that + // contains the consume of the call's result, and the other that matches every + // call. + const void *ConsumeNode = nullptr; + if (const auto *D = Result.Nodes.getNodeAs(Consume)) + ConsumeNode = D; + if (const auto *S = Result.Nodes.getNodeAs(Consume)) + ConsumeNode = S; + if (const auto *T = Result.Nodes.getNodeAs(Consume)) + ConsumeNode = T; + if (ConsumeNode) + return registerCall(CE, CE->getDirectCallee(), ConsumeNode); + + // If ConsumeNode is left to be nullptr, the current match is not through the + // "consuming" matcher. It might be the only match of this function (and then + // it is discarded), or it might have been matched earlier and consumed. + if (ConsumedCalls.find(CE) == ConsumedCalls.end()) + return registerCall(CE, CE->getDirectCallee(), nullptr); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp --- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp @@ -10,6 +10,7 @@ #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" #include "DefinitionsInHeadersCheck.h" +#include "DiscardedReturnValueCheck.h" #include "MisleadingBidirectional.h" #include "MisleadingIdentifier.h" #include "MisplacedConstCheck.h" @@ -35,6 +36,8 @@ void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { CheckFactories.registerCheck( "misc-definitions-in-headers"); + CheckFactories.registerCheck( + "misc-discarded-return-value"); CheckFactories.registerCheck( "misc-misleading-bidirectional"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -113,6 +113,12 @@ Finds initializations of C++ shared pointers to non-array type that are initialized with an array. +- New :doc:`misc-discarded-return-value ` + check. + + Flags function calls which return value is discarded if most of the other calls + to the function consume the return value. + - New :doc:`modernize-macro-to-enum ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -213,6 +213,7 @@ `llvmlibc-implementation-in-namespace `_, `llvmlibc-restrict-system-libc-headers `_, "Yes" `misc-definitions-in-headers `_, "Yes" + `misc-discarded-return-value `_, `misc-misleading-bidirectional `_, `misc-misleading-identifier `_, `misc-misplaced-const `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc-discarded-return-value.rst b/clang-tools-extra/docs/clang-tidy/checks/misc-discarded-return-value.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/misc-discarded-return-value.rst @@ -0,0 +1,42 @@ +.. title:: clang-tidy - misc-discarded-return-value + +misc-discarded-return-value +=========================== + +Flags function calls which return value is discarded if most of the other calls +to the function consume the return value. + +.. code-block:: c++ + #include + + void good_remove() { + Container C; + + // Return value of remove_if used. + auto EraseIt = std::remove_if(C.begin(), C.end(), Predicate); + std::remove(EraseIt, C.end()); + } + + void bad_remove() { + Container C; + // Unused, discarded return value. + std::remove_if(C.begin(), C.end(), Predicate); + } + +The check is based off of a statistical approach. +If at least `Threshold` percent of calls are consumed and not discarded, the +remaining calls are flagged. +The suggestion is to either consume the value (as in the case of ``remove_if`` +above), or use an explicit silencing ``(void)`` cast, like in the case of +``[[nodiscard]]``. + +Options +------- + +.. option:: ConsumeThreshold + + The percentage of calls that must be consumed for the check to trigger on + the discarded calls. + Must be a whole number between `0` and `100`, indicating 0% and 100%, + respectively. + Defaults to `80` (80%). diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc-discarded-return-value-50p.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc-discarded-return-value-50p.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc-discarded-return-value-50p.cpp @@ -0,0 +1,856 @@ +// RUN: %check_clang_tidy %s misc-discarded-return-value -std=c++17 %t \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: misc-discarded-return-value.ConsumeThreshold, value: 50} \ +// RUN: ]}' + +extern bool Coin; +extern int sink(int); +extern int sink2(int, int); + +namespace std { + +using size_t = decltype(sizeof(void *)); + +int printf(const char *Format, ...); + +template +T &&declval() noexcept; + +template +struct default_delete {}; + +template +struct initializer_list { + initializer_list(const T *, std::size_t) {} +}; + +template +struct numeric_limits { + static constexpr std::size_t min() noexcept { return 0; } + static constexpr std::size_t max() noexcept { return 4; } +}; + +template +struct remove_reference { typedef T type; }; +template +struct remove_reference { typedef T type; }; +template +struct remove_reference { typedef T type; }; + +template +typename remove_reference::type &&move(T &&V) noexcept { + return static_cast::type &&>(V); +} + +template > +class unique_ptr { +public: + unique_ptr(); + explicit unique_ptr(T *); + template + unique_ptr(unique_ptr &&); +}; + +} // namespace std + +void voidFn(); +void voidTest() { + for (voidFn();; voidFn()) + ; + voidFn(); // NO-WARN: void functions do not count for usages. +} + +[[nodiscard]] int nodiscard(); +void nodiscardTest() { + int Consume = nodiscard(); + nodiscard(); // NO-WARN from the check - [[nodiscard]] handled by Sema. +} + +int silence(); +void silenceTest() { + (void)silence(); + static_cast(silence()); + reinterpret_cast(silence()); + silence(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'silence' is used in most calls, but not in this one [misc-discarded-return-value] + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 75% (3 out of 4) of cases +} + +int varInit(); +int varInit2(); +void varinitTest() { + int X = varInit(); + varInit(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'varInit' is used in most calls, but not in this one [misc-discarded-return-value] + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) of cases + + int Y = varInit2(), Z = varInit2(); + varInit2(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'varInit2' is used in most calls, but not in this one [misc-discarded-return-value] + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) of cases +} + +int passToFn(); +void passToFnTest() { + sink(passToFn()); + passToFn(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'passToFn' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +int *array(); +int index(); +void indexTest() { + int T[4]; + array()[index()]; + T[index()]; + array()[0]; + + index(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'index' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) + + array(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'array' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) +} + +int varargVal(); +void varargTest() { + std::printf("%d %d", varargVal(), varargVal()); + varargVal(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'varargVal' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) +} + +int unary(); +void unaryTest() { + if (!unary()) + unary(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'unary' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) +} + +bool lhs(); +bool rhs(); +void lhsrhsTest() { + bool X = true; + lhs() == X; + X == rhs(); + + lhs(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'lhs' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + rhs(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'rhs' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +bool inIf(); +bool inElseIf(); +void ifTest() { + if (inIf()) + inIf(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'inIf' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) + + if (Coin) + voidFn(); + else if (inElseIf()) + inElseIf(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'inElseIf' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) +} + +int forInit(); +int forCondition(); +int forIncrement(); +int inFor(); +struct Range { + int *begin(); + int *end(); +}; +Range makeRange(); +void forTest() { + for (forInit();;) + forInit(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'forInit' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) + + for (; forCondition();) + forCondition(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'forCondition' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) + + for (;; forIncrement()) + forIncrement(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'forIncrement' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) + + for (inFor(); inFor(); inFor()) + inFor(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'inFor' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 75% (3 out of 4) + + for (int &I : makeRange()) + ; + + makeRange(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'makeRange' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +int whileCond(); +int doCond(); +void doWhileTest() { + while (whileCond()) + whileCond(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'whileCond' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) + + do + doCond(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'doCond' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) + while (doCond()); +} + +int inSwitch(); +void switchTest() { + switch (inSwitch()) { + case 0: + break; + } + inSwitch(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'inSwitch' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +int returned(); +int returnTest() { + returned(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'returned' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + return returned(); +} + +struct S { + S(); + S(int); + int member(); + int operator()(); + bool operator==(const S &); + bool operator!=(const S &); + int operator[](int); + int operator*(); + void *operator&(); + S operator++(int); + S &operator++(); + S &operator+(int); + int plusValueMaker(); + + int memberA(); + int memberB(); + + void memberTestInside() { + sink(member()); + member(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'member' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (2 out of 4) + + sink((*this)()); + (*this)(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'operator()' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (2 out of 4) + + sink2(memberA(), memberB()); + memberA(); + memberB(); + // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: return value of 'memberA' + // CHECK-MESSAGES: :[[@LINE-3]]:5: note: value consumed or checked in 50% (1 out of 2) + // CHECK-MESSAGES: :[[@LINE-3]]:5: warning: return value of 'memberB' + // CHECK-MESSAGES: :[[@LINE-4]]:5: note: value consumed or checked in 50% (1 out of 2) + } +}; + +int ctorParam(); +void constructorTest() { + S Obj = ctorParam(); + S Obj2{ctorParam()}; + ctorParam(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'ctorParam' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) +} + +void memberTest() { + S Obj; + int I = Obj.member(); + Obj.member(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: return value of 'member' + // CHECK-MESSAGES: :[[@LINE-2]]:7: note: value consumed or checked in 50% (2 out of 4) + + sink(Obj()); + Obj(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'operator()' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (2 out of 4) + + int E = Obj[I]; + Obj[I + 1]; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'operator[]' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + + // NO-WARN: Matching operators would be too noisy, and + // -Wunused-(comparison|value) takes care of this case. + S O2 = ++Obj; + ++Obj; + + S O3 = Obj++; + Obj++; + + bool B = O2 == O3; + O2 == O3; + + B = O2 != O3; + O2 != O3; + + auto &OR = Obj = O2; + Obj = O3; + + E = *Obj; + *Obj; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'operator*' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + + void *P = &Obj; + &Obj; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'operator&' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + + Obj + Obj.plusValueMaker(); + Obj.plusValueMaker(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: return value of 'plusValueMaker' + // CHECK-MESSAGES: :[[@LINE-2]]:7: note: value consumed or checked in 50% (1 out of 2) +} + +static int internal() { return 1; } +void internalTest() { + int I = internal(); + internal(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'internal' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +template +struct Integer { + struct Unsigned { + static constexpr unsigned Max = static_cast(-1); + Unsigned(int); + }; +}; +template +struct IntegerInfo { using UnsignedIntegerTag = typename Integer::Unsigned; }; +constexpr int intNoMask() { return 0; } +template +struct TaggedInteger { + using IntegerType = IntTy; + + static constexpr IntegerType max() { + return IntegerType( + typename IntegerInfo::UnsignedIntegerTag(intNoMask())); + } +}; +struct MyInteger { + int Value; + template + MyInteger(IntegerTag IT) : Value(IT.Max) {} +}; +int unresolvedCtorExprTest() { + intNoMask(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'intNoMask' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + return TaggedInteger::max().Value; +} + +struct W1 {}; +struct W2 {}; +struct WMaker { + W1 w1() { return {}; } + W2 w2() { return {}; } +}; +template +struct Wrapper1 { + Wrapper1(T) {} +}; +template +struct Wrapper2 { + Wrapper2(T, U) {} +}; +template +struct WrapMaker1 { + Wrapper1 make() { + WMaker *WP; + Wrapper1 Wrapper(WP->w1()); + return Wrapper; + } +}; +template +struct WrapMaker2 { + Wrapper2 make() { + WMaker *WP; + Wrapper2 Wrapper(WP->w1(), WP->w2()); + return Wrapper; + } +}; +void parenListExprTest() { + WMaker{}.w1(); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: return value of 'w1' + // CHECK-MESSAGES: :[[@LINE-2]]:12: note: value consumed or checked in 66% (2 out of 3) + WMaker{}.w2(); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: return value of 'w2' + // CHECK-MESSAGES: :[[@LINE-2]]:12: note: value consumed or checked in 50% (1 out of 2) +} + +struct T { + S make(); + T &operator+(S); +}; + +void utilityTest() { + T TObj; + S SObj; + + TObj + std::move(SObj); + std::move(SObj); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'move' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +struct La { + int throughThis(); + void testThisCapture() { + auto L = [this] { return throughThis(); }; + throughThis(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'throughThis' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) + } +}; + +int lambda(); +int lambda1(); +int lambda2(); +void lambdaTest() { + auto L = [X = lambda()] { return X; }; + lambda(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'lambda' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + + auto L2 = [X = lambda1(), Y = lambda2()] { return X + Y; }; + lambda1(); + lambda2(); + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: return value of 'lambda1' + // CHECK-MESSAGES: :[[@LINE-3]]:3: note: value consumed or checked in 50% (1 out of 2) + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: return value of 'lambda2' + // CHECK-MESSAGES: :[[@LINE-4]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +struct P { + struct Inner { + int func(); + int operator[](int); + }; + Inner *get(); + Inner &getRefForIndex(); +}; +void dereferenceTest() { + P Obj; + Obj.get()->func(); + Obj.get(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: return value of 'get' + // CHECK-MESSAGES: :[[@LINE-2]]:7: note: value consumed or checked in 50% (1 out of 2) + + Obj.getRefForIndex()[1]; + Obj.getRefForIndex(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: return value of 'getRefForIndex' + // CHECK-MESSAGES: :[[@LINE-2]]:7: note: value consumed or checked in 50% (1 out of 2) +} + +struct ZP {}; +struct ZQ {}; +struct Z { + struct Context { + template + T getAs() { return T{}; } + }; + struct Ctx { + Context *getContext(ZP *); + }; + + Ctx ContextObj; + + template + T getAs() { + ZP X{}; + + Context *C = ContextObj.getContext(&X); + ContextObj.getContext(&X); + return ContextObj.getContext(&X)->getAs(); + // CHECK-MESSAGES: :[[@LINE-2]]:16: warning: return value of 'getContext' + // CHECK-MESSAGES: :[[@LINE-3]]:16: note: value consumed or checked in 66% (2 out of 3) + } +}; +void templateFunctionDerefTest() { + Z Obj; + auto Got = Obj.getAs(); + Obj.getAs(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: return value of 'getAs' + // CHECK-MESSAGES: :[[@LINE-2]]:7: note: value consumed or checked in 50% (1 out of 2) +} + +int ctorMemberHelper(); +struct Q { + static int transform(int); + + int M; + int N; + Q(int I) : M(I), N(transform(I)) {} +}; +void ctorMemberInitializerTest() { + Q Obj{ctorMemberHelper()}; + ctorMemberHelper(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'ctorMemberHelper' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + + Q::transform(1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'transform' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +int inlineMemberHelper(); +struct V { + int X = inlineMemberHelper(); +}; +void inlineMemberInitTest() { + inlineMemberHelper(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'inlineMemberHelper' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +int defaultParam(); +struct W { + W(int I = defaultParam()); +}; +void defaultSink(int P = defaultParam()); +void defaultParamTest() { + defaultParam(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'defaultParam' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) +} + +constexpr int enumValue() { return 1; } +enum EA { EA_A = enumValue() }; +enum EB : long { EB_A = enumValue() }; +enum class EC { A = enumValue() }; +enum class ED : short { A = enumValue() }; +void enumValueTest() { + enumValue(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'enumValue' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 80% (4 out of 5) +} + +struct InDecltype {}; +void decltypeTest() { + using T = decltype(std::declval()); + std::declval(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'declval' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +struct InSizeof { + char C[8]; +}; +InSizeof makeSizeof(); +void sizeofTest() { + auto X = sizeof(makeSizeof()); + makeSizeof(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'makeSizeof' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +struct InDecltypeMember {}; +InDecltypeMember dtMemberHelper(); +struct DecltypeMember { + decltype(dtMemberHelper()) Member; +}; +void decltypeMemberTest() { + dtMemberHelper(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'dtMemberHelper' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +constexpr bool inStaticAssert() { return true; } +void staticTest() { + static_assert(inStaticAssert(), ""); + inStaticAssert(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'inStaticAssert' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +int inTernaryLHS(); +int inTernaryRHS(); +void ternaryTest() { + int X = Coin ? inTernaryLHS() : inTernaryRHS(); + + inTernaryLHS(); + inTernaryRHS(); + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: return value of 'inTernaryLHS' + // CHECK-MESSAGES: :[[@LINE-3]]:3: note: value consumed or checked in 50% (1 out of 2) + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: return value of 'inTernaryRHS' + // CHECK-MESSAGES: :[[@LINE-4]]:3: note: value consumed or checked in 50% (1 out of 2) +} + +template +auto call(Lambda &&L) { + return L(); +} +void lambdaCallerTest() { + auto L1 = [] { return 1; }; + auto L2 = [] { return 2; }; + + auto Take1 = call(L1); + call(L1); + + auto Take2 = call(L2); + auto Take2b = call(L2); + call(L2); + + // CHECK-MESSAGES: :[[@LINE-6]]:3: warning: return value of 'call<{{.*}}[[@LINE-10]]:13{{.*}}>' + // CHECK-MESSAGES: :[[@LINE-7]]:3: note: value consumed or checked in 60% (3 out of 5) + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: return value of 'call<{{.*}}[[@LINE-12]]:13{{.*}}>' + // CHECK-MESSAGES: :[[@LINE-5]]:3: note: value consumed or checked in 60% (3 out of 5) + + // FIXME: Because call is a template, calls to it, even with different + // lambdas are calculated together, but the resulting diagnostic message is + // wrong. The culprit seems to be the USR generated for + // 'call<(lambda in lambdaCallerTest)>' being + // "c:misc-discarded-return-value-50p.cpp@F@call<#&$@F@lambdaCallerTest#@Sa>#S0_#" +} +void lambdaCallerTest2() { + auto X = [] { return 4; }; + + auto Var = call(X); + call(X); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'call<{{.*}}[[@LINE-4]]:12{{.*}}>' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + + // Here, call<...> has a different key: + // "c:misc-discarded-return-value-50p.cpp@F@call<#&$@F@lambdaCallerTest2#@Sa>#S0_#" +} + +int calledThroughLambda(); +void calledThroughLambdaTest() { + int I = calledThroughLambda(), J = calledThroughLambda(); + + call([] { calledThroughLambda(); }); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: return value of 'calledThroughLambda' + // CHECK-MESSAGES: :[[@LINE-2]]:13: note: value consumed or checked in 50% (2 out of 4) + calledThroughLambda(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'calledThroughLambda' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (2 out of 4) +} + +struct Factory { + struct Element {}; + + static Element make(); + static Element make2(); +}; +struct ElementArray { + ElementArray(std::initializer_list) {} +}; +struct InitListIniter { + ElementArray Arr; + + InitListIniter() : Arr({Factory::make(), Factory::make(), Factory::make()}) { + Factory::make(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'make' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 75% (3 out of 4) + } +}; +void initListTest() { + Factory::Element Array[] = {Factory::make2(), Factory::make2(), Factory::make2()}; + Factory::make2(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'make2' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 75% (3 out of 4) +} + +struct Container { + int begin(); + int end(); +}; +struct BaseRange { + int Begin, End; + BaseRange(int B, int E) : Begin(B), End(E) {} +}; +struct DerivedRange : BaseRange { + DerivedRange(Container C) : BaseRange(C.begin(), C.end()) {} +}; +void baseClassInitTest() { + Container Cont; + DerivedRange DR{Cont}; + + Cont.begin(); + Cont.end(); + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: return value of 'begin' + // CHECK-MESSAGES: :[[@LINE-3]]:8: note: value consumed or checked in 50% (1 out of 2) + // CHECK-MESSAGES: :[[@LINE-3]]:8: warning: return value of 'end' + // CHECK-MESSAGES: :[[@LINE-4]]:8: note: value consumed or checked in 50% (1 out of 2) +} + +template +struct ContainerT { + T *begin(); + T *end(); +}; +struct CharContainer { + char *begin(); + char *end(); +}; +template +struct TypedRange { + T *Begin, *End; + TypedRange(T *B, T *E) : Begin(B), End(E) {} +}; +struct CharRange : TypedRange { + CharRange(CharContainer C) : TypedRange(C.begin(), C.end()) {} +}; +template +struct SmallTypedRange : TypedRange { + SmallTypedRange(T *B, T *E) : TypedRange(B, E) {} +}; +template +struct SmallString : SmallTypedRange { + SmallString(CharContainer C) : SmallTypedRange(C.begin(), C.end()) {} +}; +void baseTemplateClassInitTest() { + CharContainer Cont; + CharRange CR{Cont}; + + Cont.begin(); + Cont.end(); + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: return value of 'begin' + // CHECK-MESSAGES: :[[@LINE-3]]:8: note: value consumed or checked in 66% (2 out of 3) + // CHECK-MESSAGES: :[[@LINE-3]]:8: warning: return value of 'end' + // CHECK-MESSAGES: :[[@LINE-4]]:8: note: value consumed or checked in 66% (2 out of 3) +} + +struct CharContainer2 { + char *begin(); + char *end(); +}; +TypedRange rangeTemporaryTest() { + CharContainer2 CC; + CC.begin(); + CC.end(); + // CHECK-MESSAGES: :[[@LINE-2]]:6: warning: return value of 'begin' + // CHECK-MESSAGES: :[[@LINE-3]]:6: note: value consumed or checked in 50% (1 out of 2) + // CHECK-MESSAGES: :[[@LINE-3]]:6: warning: return value of 'end' + // CHECK-MESSAGES: :[[@LINE-4]]:6: note: value consumed or checked in 50% (1 out of 2) + + return TypedRange(CC.begin(), CC.end()); +} + +void *rawAllocate(); +int aObjectParam(); +struct AObjectParamMaker { + int Param(); +}; +struct AObject { + AObject(); + AObject(int); +}; +AObject *aoAddress(); +struct Allocator { +}; +Allocator createAlloc(); +void *operator new(std::size_t, void *); +void *operator new(std::size_t N, const Allocator &Arg); +std::size_t padding(); +Allocator createOtherAlloc(); +void *operator new(std::size_t N, const Allocator &Alloc, std::size_t Padding); +std::size_t count(); +void newDeleteTest() { + AObject *AO = new (rawAllocate()) AObject{}; + delete aoAddress(); + + rawAllocate(); + aoAddress(); + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: return value of 'rawAllocate' + // CHECK-MESSAGES: :[[@LINE-3]]:3: note: value consumed or checked in 50% (1 out of 2) + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: return value of 'aoAddress' + // CHECK-MESSAGES: :[[@LINE-4]]:3: note: value consumed or checked in 50% (1 out of 2) + + AObject *AO2 = new (createAlloc()) AObject{aObjectParam()}; + createAlloc(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'createAlloc' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% + + AObject *AO3 = new (createOtherAlloc(), padding()) AObject{aObjectParam()}; + createOtherAlloc(); + padding(); + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: return value of 'createOtherAlloc' + // CHECK-MESSAGES: :[[@LINE-3]]:3: note: value consumed or checked in 50% (1 out of 2) + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: return value of 'padding' + // CHECK-MESSAGES: :[[@LINE-4]]:3: note: value consumed or checked in 50% (1 out of 2) + + aObjectParam(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'aObjectParam' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) + + AObject *AO4 = new AObject(AObjectParamMaker{}.Param()); + AObjectParamMaker{}.Param(); + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: return value of 'Param' + // CHECK-MESSAGES: :[[@LINE-2]]:23: note: value consumed or checked in 50% (1 out of 2) + + AObject **AObjArr = new AObject *[count()]; + AObject VLA[count()]; + count(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'count' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 66% (2 out of 3) +} + +class Empty {}; +class Payload { +public: + std::unique_ptr take(); +}; + +template +class FromPayload { +public: + using empty_base = std::unique_ptr; + empty_base *base(); + + FromPayload(Payload P) { + new (base()) empty_base(P.take()); + } +}; +void nonTrivialInitExprInNewTest() { + Payload P; + P.take(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return value of 'take' + // CHECK-MESSAGES: :[[@LINE-2]]:5: note: value consumed or checked in 50% (1 out of 2) +} + +template +struct NTTP {}; +extern NTTP::min(), + std::numeric_limits::max()> + X1; +void nttpTest() { + std::numeric_limits::min(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'min' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) + std::numeric_limits::max(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: return value of 'max' + // CHECK-MESSAGES: :[[@LINE-2]]:3: note: value consumed or checked in 50% (1 out of 2) +}