Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -59,6 +59,9 @@ ``-Wno-c++98-compat-extra-semi``, so if you want that diagnostic, you need to explicitly re-enable it (e.g. by appending ``-Wextra-semi``). +- ``-Wunused-value`` diagnostic was taught to detect unused return value of + ``std::move()`` function. + Non-comprehensive list of changes in this release ------------------------------------------------- Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -7277,6 +7277,12 @@ def warn_unused_call : Warning< "ignoring return value of function declared with %0 attribute">, InGroup; +def warn_unused_stdmove : Warning< + "ignoring return value of std::move() function returning rvalue reference of type %0">, + InGroup; +def warn_unused_xvalue_cast : Warning< + "ignoring return value of cast to rvalue reference of type %0">, + InGroup; def warn_side_effects_unevaluated_context : Warning< "expression with side effects has no effect in an unevaluated context">, InGroup; Index: lib/AST/Expr.cpp =================================================================== --- lib/AST/Expr.cpp +++ lib/AST/Expr.cpp @@ -2196,7 +2196,7 @@ // // Note: If new cases are added here, DiagnoseUnusedExprResult should be // updated to match for QoI. - if (HasWarnUnusedResultAttr || + if (CE->isCallToStdMove() || HasWarnUnusedResultAttr || FD->hasAttr() || FD->hasAttr()) { WarnE = this; Loc = CE->getCallee()->getLocStart(); Index: lib/Sema/SemaStmt.cpp =================================================================== --- lib/Sema/SemaStmt.cpp +++ lib/Sema/SemaStmt.cpp @@ -264,6 +264,10 @@ // is written in a macro body, only warn if it has the warn_unused_result // attribute. if (const Decl *FD = CE->getCalleeDecl()) { + if (CE->isCallToStdMove()) { + Diag(Loc, diag::warn_unused_stdmove) << E->getType().getCanonicalType(); + return; + } if (const Attr *A = isa(FD) ? cast(FD)->getUnusedResultAttr() : FD->getAttr()) { @@ -329,6 +333,11 @@ } } + if (E->isXValue() && isa(E)) { + Diag(Loc, diag::warn_unused_xvalue_cast) << E->getType().getCanonicalType(); + return; + } + if (E->isGLValue() && E->getType().isVolatileQualified()) { Diag(Loc, diag::warn_unused_volatile) << R1 << R2; return; Index: test/Analysis/MisusedMovedObject.cpp =================================================================== --- test/Analysis/MisusedMovedObject.cpp +++ test/Analysis/MisusedMovedObject.cpp @@ -185,7 +185,7 @@ { A a; if (i == 1) { // expected-note {{Taking false branch}} expected-note {{Taking false branch}} - std::move(a); + std::move(a); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'A'}} } if (i == 2) { // expected-note {{Taking false branch}} expected-note {{Taking false branch}} a = A(); @@ -409,7 +409,7 @@ // Moves of global variables are not reported. A global_a; void globalVariablesTest() { - std::move(global_a); + std::move(global_a); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'A'}} global_a.foo(); // no-warning } Index: test/SemaCXX/warn-unused-result-nodiscard.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-unused-result-nodiscard.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 %s + +// definitions for somthing similar to the std::move. +namespace notstd { +inline namespace foo { +template +struct remove_reference { typedef T type; }; +template +struct remove_reference { typedef T type; }; +template +struct remove_reference { typedef T type; }; + +// This is just like the std::move(), but marked with warn_unused_result attribute +template +__attribute__((warn_unused_result)) typename remove_reference::type &¬move1(T &&t); +template +[[clang::warn_unused_result]] typename remove_reference::type &¬move2(T &&t); +template +[[nodiscard]] typename remove_reference::type &¬move3(T &&t); +} // namespace foo +} // namespace notstd + +// definitions for std::move, but actually marked with nodiscard attribute +namespace std { +inline namespace foo { +template +struct remove_reference { typedef T type; }; +template +struct remove_reference { typedef T type; }; +template +struct remove_reference { typedef T type; }; + +template +[[nodiscard]] typename remove_reference::type &&move(T &&t); +} // namespace foo +} // namespace std + +void arg(int a) { + notstd::notmove1(a); // expected-warning {{ignoring return value of function declared with 'warn_unused_result' attribute}} + notstd::notmove2(a); // expected-warning {{ignoring return value of function declared with 'warn_unused_result' attribute}} + notstd::notmove3(a); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + + std::move(a); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} +} Index: test/SemaCXX/warn-unused-xvalue.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-unused-xvalue.cpp @@ -0,0 +1,97 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// definitions for std::move +namespace std { +inline namespace foo { +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 &&t); +} // namespace foo +} // namespace std + +int f0(); +int &&f1(); + +int global; + +void test_negative() { + // Ok. Should not warn + f0(); + f1(); + + static_cast(f0()); // expected-warning {{expression result unused}} + static_cast(f1()); // expected-warning {{expression result unused}} + + static_cast(global); // expected-warning {{expression result unused}} + + (void)std::move(global); + (void)noexcept(std::move(global)); + (void)sizeof(std::move(global)); +} + +// Unevaluated contexts should not trigger unused result warnings. +template +auto foo(T) -> decltype(std::move(global), bool()) { // Should not warn. + return true; +} + +void test_simple() { + int x = 1; + + static_cast(x); // expected-warning {{ignoring return value of cast to rvalue reference of type 'int'}} + std::move(x); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} + + using std::move; + move(x); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} +} + +void test_global() { + static_cast(global); // expected-warning {{ignoring return value of cast to rvalue reference of type 'int'}} + std::move(global); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} + + using std::move; + move(global); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} +} + +void test_call() { + static_cast(f0()); // expected-warning {{ignoring return value of cast to rvalue reference of type 'int'}} + static_cast(f1()); // expected-warning {{ignoring return value of cast to rvalue reference of type 'int'}} + std::move(f0()); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} + std::move(f1()); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} + + using std::move; + move(f0()); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} + move(f1()); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} +} + +void arg(int a) { + static_cast(a); // expected-warning {{ignoring return value of cast to rvalue reference of type 'int'}} + + std::move(a); // expected-warning {{ignoring return value of std::move() function returning rvalue reference of type 'int'}} +} + +struct A {}; +struct B { + A a; +}; +struct C { + C(){}; + ~C() {} +}; +void struct_test() { + A a; + std::move(a); // expected-warning{{ignoring return value of std::move() function returning rvalue reference of type 'A'}} + + B b; + std::move(b); // expected-warning{{ignoring return value of std::move() function returning rvalue reference of type 'B'}} + std::move(b.a); // expected-warning{{ignoring return value of std::move() function returning rvalue reference of type 'A'}} + + C c; + std::move(c); // expected-warning{{ignoring return value of std::move() function returning rvalue reference of type 'C'}} +}