Skip to content

[clang] Improve diagnostic on [[nodiscard]] attribute #112521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,22 @@ Improvements to Clang's diagnostics

- Clang now omits shadow warnings for enum constants in separate class scopes (#GH62588).

- When diagnosing an unused return value of a type declared ``[[nodiscard]]``, the type
itself is now included in the diagnostic.

- Clang will now prefer the ``[[nodiscard]]`` declaration on function declarations over ``[[nodiscard]]``
declaration on the return type of a function. Previously, when both have a ``[[nodiscard]]`` declaration attached,
the one on the return type would be preferred. This may affect the generated warning message:

.. code-block:: c++

struct [[nodiscard("Reason 1")]] S {};
[[nodiscard("Reason 2")]] S getS();
void use()
{
getS(); // Now diagnoses "Reason 2", previously diagnoses "Reason 1"
}

Improvements to Clang's time-trace
----------------------------------

Expand Down
8 changes: 5 additions & 3 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3181,12 +3181,14 @@ class CallExpr : public Expr {
QualType getCallReturnType(const ASTContext &Ctx) const;

/// Returns the WarnUnusedResultAttr that is either declared on the called
/// function, or its return type declaration.
const Attr *getUnusedResultAttr(const ASTContext &Ctx) const;
/// function, or its return type declaration, together with a NamedDecl that
/// refers to the declaration the attribute is attached onto.
std::pair<const NamedDecl *, const Attr *>
getUnusedResultAttr(const ASTContext &Ctx) const;

/// Returns true if this call expression should warn on unused results.
bool hasUnusedResultAttr(const ASTContext &Ctx) const {
return getUnusedResultAttr(Ctx) != nullptr;
return getUnusedResultAttr(Ctx).second != nullptr;
}

SourceLocation getRParenLoc() const { return RParenLoc; }
Expand Down
13 changes: 5 additions & 8 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -9300,11 +9300,11 @@ def warn_unused_container_subscript_expr : Warning<
def warn_unused_call : Warning<
"ignoring return value of function declared with %0 attribute">,
InGroup<UnusedValue>;
def warn_unused_constructor : Warning<
"ignoring temporary created by a constructor declared with %0 attribute">,
def warn_unused_return_type : Warning<
"ignoring %select{return value|temporary}0 of type %2 declared with %1 attribute%select{|: %4}3">,
InGroup<UnusedValue>;
def warn_unused_constructor_msg : Warning<
"ignoring temporary created by a constructor declared with %0 attribute: %1">,
def warn_unused_constructor : Warning<
"ignoring temporary created by a constructor declared with %0 attribute%select{|: %2}1">,
InGroup<UnusedValue>;
def warn_side_effects_unevaluated_context : Warning<
"expression with side effects has no effect in an unevaluated context">,
Expand All @@ -9313,10 +9313,7 @@ def warn_side_effects_typeid : Warning<
"expression with side effects will be evaluated despite being used as an "
"operand to 'typeid'">, InGroup<PotentiallyEvaluatedExpression>;
def warn_unused_result : Warning<
"ignoring return value of function declared with %0 attribute">,
InGroup<UnusedResult>;
def warn_unused_result_msg : Warning<
"ignoring return value of function declared with %0 attribute: %1">,
"ignoring return value of function declared with %0 attribute%select{|: %2}1">,
InGroup<UnusedResult>;
def warn_unused_result_typedef_unsupported_spelling : Warning<
"'[[%select{nodiscard|gnu::warn_unused_result}0]]' attribute ignored when "
Expand Down
18 changes: 10 additions & 8 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1615,22 +1615,24 @@ QualType CallExpr::getCallReturnType(const ASTContext &Ctx) const {
return FnType->getReturnType();
}

const Attr *CallExpr::getUnusedResultAttr(const ASTContext &Ctx) const {
std::pair<const NamedDecl *, const Attr *>
CallExpr::getUnusedResultAttr(const ASTContext &Ctx) const {
// If the callee is marked nodiscard, return that attribute
const Decl *D = getCalleeDecl();
if (const auto *A = D->getAttr<WarnUnusedResultAttr>())
return {nullptr, A};

// If the return type is a struct, union, or enum that is marked nodiscard,
// then return the return type attribute.
if (const TagDecl *TD = getCallReturnType(Ctx)->getAsTagDecl())
if (const auto *A = TD->getAttr<WarnUnusedResultAttr>())
return A;
return {TD, A};

for (const auto *TD = getCallReturnType(Ctx)->getAs<TypedefType>(); TD;
TD = TD->desugar()->getAs<TypedefType>())
if (const auto *A = TD->getDecl()->getAttr<WarnUnusedResultAttr>())
return A;

// Otherwise, see if the callee is marked nodiscard and return that attribute
// instead.
const Decl *D = getCalleeDecl();
return D ? D->getAttr<WarnUnusedResultAttr>() : nullptr;
return {TD->getDecl(), A};
return {nullptr, nullptr};
}

SourceLocation CallExpr::getBeginLoc() const {
Expand Down
47 changes: 30 additions & 17 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,23 +200,30 @@ static bool DiagnoseUnusedComparison(Sema &S, const Expr *E) {
return true;
}

static bool DiagnoseNoDiscard(Sema &S, const WarnUnusedResultAttr *A,
SourceLocation Loc, SourceRange R1,
SourceRange R2, bool IsCtor) {
static bool DiagnoseNoDiscard(Sema &S, const NamedDecl *OffendingDecl,
const WarnUnusedResultAttr *A, SourceLocation Loc,
SourceRange R1, SourceRange R2, bool IsCtor) {
if (!A)
return false;
StringRef Msg = A->getMessage();

if (Msg.empty()) {
if (OffendingDecl)
return S.Diag(Loc, diag::warn_unused_return_type)
<< IsCtor << A << OffendingDecl << false << R1 << R2;
if (IsCtor)
return S.Diag(Loc, diag::warn_unused_constructor) << A << R1 << R2;
return S.Diag(Loc, diag::warn_unused_result) << A << R1 << R2;
return S.Diag(Loc, diag::warn_unused_constructor)
<< A << false << R1 << R2;
return S.Diag(Loc, diag::warn_unused_result) << A << false << R1 << R2;
}

if (OffendingDecl)
return S.Diag(Loc, diag::warn_unused_return_type)
<< IsCtor << A << OffendingDecl << true << Msg << R1 << R2;
if (IsCtor)
return S.Diag(Loc, diag::warn_unused_constructor_msg) << A << Msg << R1
<< R2;
return S.Diag(Loc, diag::warn_unused_result_msg) << A << Msg << R1 << R2;
return S.Diag(Loc, diag::warn_unused_constructor)
<< A << true << Msg << R1 << R2;
return S.Diag(Loc, diag::warn_unused_result) << A << true << Msg << R1 << R2;
}

void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
Expand Down Expand Up @@ -286,9 +293,10 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (E->getType()->isVoidType())
return;

if (DiagnoseNoDiscard(*this, cast_or_null<WarnUnusedResultAttr>(
CE->getUnusedResultAttr(Context)),
Loc, R1, R2, /*isCtor=*/false))
auto [OffendingDecl, A] = CE->getUnusedResultAttr(Context);
if (DiagnoseNoDiscard(*this, OffendingDecl,
cast_or_null<WarnUnusedResultAttr>(A), Loc, R1, R2,
/*isCtor=*/false))
return;

// If the callee has attribute pure, const, or warn_unused_result, warn with
Expand All @@ -309,16 +317,21 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
}
} else if (const auto *CE = dyn_cast<CXXConstructExpr>(E)) {
if (const CXXConstructorDecl *Ctor = CE->getConstructor()) {
const NamedDecl *OffendingDecl = nullptr;
const auto *A = Ctor->getAttr<WarnUnusedResultAttr>();
A = A ? A : Ctor->getParent()->getAttr<WarnUnusedResultAttr>();
if (DiagnoseNoDiscard(*this, A, Loc, R1, R2, /*isCtor=*/true))
if (!A) {
OffendingDecl = Ctor->getParent();
A = OffendingDecl->getAttr<WarnUnusedResultAttr>();
}
if (DiagnoseNoDiscard(*this, OffendingDecl, A, Loc, R1, R2,
/*isCtor=*/true))
return;
}
} else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
if (const TagDecl *TD = ILE->getType()->getAsTagDecl()) {

if (DiagnoseNoDiscard(*this, TD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
R2, /*isCtor=*/false))
if (DiagnoseNoDiscard(*this, TD, TD->getAttr<WarnUnusedResultAttr>(), Loc,
R1, R2, /*isCtor=*/false))
return;
}
} else if (ShouldSuppress)
Expand All @@ -332,8 +345,8 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
}
const ObjCMethodDecl *MD = ME->getMethodDecl();
if (MD) {
if (DiagnoseNoDiscard(*this, MD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
R2, /*isCtor=*/false))
if (DiagnoseNoDiscard(*this, nullptr, MD->getAttr<WarnUnusedResultAttr>(),
Loc, R1, R2, /*isCtor=*/false))
return;
}
} else if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) {
Expand Down
28 changes: 14 additions & 14 deletions clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ E get_e();
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}

void f() {
get_s(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
get_s(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}
get_i(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
get_vi(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
get_e(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
get_e(); // expected-warning {{ignoring return value of type 'E' declared with 'nodiscard' attribute}}

// Okay, warnings are not encouraged
get_s_ref();
Expand Down Expand Up @@ -54,10 +54,10 @@ void f() {
fp3 three;
fp2_alias four;

one(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
two(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
three(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
four(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
one(); // expected-warning {{ignoring return value of type 'E' declared with 'nodiscard' attribute}}
two(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}
three(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}
four(); // expected-warning {{ignoring return value of type 'S' declared with 'nodiscard' attribute}}

// These are all okay because of the explicit cast to void.
(void)one();
Expand All @@ -84,8 +84,8 @@ LaterReason get_later_reason();
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}

void cxx20_use() {
get_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: reason}}
get_later_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: later reason}}
get_reason(); // expected-warning {{ignoring return value of type 'ReasonStruct' declared with 'nodiscard' attribute: reason}}
get_later_reason(); // expected-warning {{ignoring return value of type 'LaterReason' declared with 'nodiscard' attribute: later reason}}
another_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: another reason}}
conflicting_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: special reason}}
}
Expand Down Expand Up @@ -115,20 +115,20 @@ void usage() {
S('A'); // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't let that S-Char go!}}
S(1);
S(2.2);
Y(); // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't throw me away either!}}
Y(); // expected-warning {{ignoring temporary of type 'Y' declared with 'nodiscard' attribute: Don't throw me away either!}}
S s;
ConvertTo{}; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw me away!}}
ConvertTo{}; // expected-warning {{ignoring return value of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}

// AST is different in C++17 mode. Before, a move ctor for ConvertTo is there
// as well, hence the constructor warning.

// since-cxx17-warning@+2 {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw me away!}}
// cxx11-warning@+1 {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't throw me away!}}
// since-cxx17-warning@+2 {{ignoring return value of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
// cxx11-warning@+1 {{ignoring temporary of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
(ConvertTo) s;
(int)s; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
(S)'c'; // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't let that S-Char go!}}
// since-cxx17-warning@+2 {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw me away!}}
// cxx11-warning@+1 {{ignoring temporary created by a constructor declared with 'nodiscard' attribute: Don't throw me away!}}
// since-cxx17-warning@+2 {{ignoring return value of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
// cxx11-warning@+1 {{ignoring temporary of type 'ConvertTo' declared with 'nodiscard' attribute: Don't throw me away!}}
static_cast<ConvertTo>(s);
static_cast<int>(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
static_cast<double>(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw away as a double}}
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace std_example {
error_info enable_missile_safety_mode();
void launch_missiles();
void test_missiles() {
enable_missile_safety_mode(); // expected-warning {{ignoring return value of function declared with 'nodiscard'}}
enable_missile_safety_mode(); // expected-warning {{ignoring return value of type 'error_info' declared with 'nodiscard'}}
launch_missiles();
}

Expand Down
8 changes: 4 additions & 4 deletions clang/test/Sema/c2x-nodiscard.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ enum E2 get_e(void);
[[nodiscard]] int get_i(void);

void f2(void) {
get_s(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
get_s3(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: Wrong}}
get_s(); // expected-warning {{ignoring return value of type 'S4' declared with 'nodiscard' attribute}}
get_s3(); // expected-warning {{ignoring return value of type 'S3' declared with 'nodiscard' attribute: Wrong}}
get_i(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
get_e(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
get_e(); // expected-warning {{ignoring return value of type 'E2' declared with 'nodiscard' attribute}}

// Okay, warnings are not encouraged
(void)get_s();
Expand All @@ -50,7 +50,7 @@ struct [[nodiscard]] error_info{
struct error_info enable_missile_safety_mode(void);
void launch_missiles(void);
void test_missiles(void) {
enable_missile_safety_mode(); // expected-warning {{ignoring return value of function declared with 'nodiscard'}}
enable_missile_safety_mode(); // expected-warning {{ignoring return value of type 'error_info' declared with 'nodiscard'}}
launch_missiles();
}

Expand Down
Loading
Loading