Skip to content

[clang] Generate note on declaration for nodiscard-related attributes #112289

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

Closed
wants to merge 1 commit into from
Closed
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
5 changes: 3 additions & 2 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3182,11 +3182,12 @@ class CallExpr : public Expr {

/// Returns the WarnUnusedResultAttr that is either declared on the called
/// function, or its return type declaration.
const Attr *getUnusedResultAttr(const ASTContext &Ctx) const;
std::pair<const Decl *, 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
6 changes: 3 additions & 3 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -5939,8 +5939,8 @@ def warn_unavailable_fwdclass_message : Warning<
"%0 may be unavailable because the receiver type is unknown">,
InGroup<UnavailableDeclarations>;
def note_availability_specified_here : Note<
"%0 has been explicitly marked "
"%select{unavailable|deleted|deprecated}1 here">;
"%select{|%1 }0has been explicitly marked "
"%select{unavailable|deleted|deprecated|nodiscard|warn_unused_result|pure|const}2 here">;
def note_partial_availability_specified_here : Note<
"%0 has been marked as being introduced in %1 %2 %select{|in %5 environment }4here, "
"but the deployment target is %1 %3%select{| %6 environment }4">;
Expand Down Expand Up @@ -9263,7 +9263,7 @@ def warn_unused_container_subscript_expr : Warning<
"container access result unused - container access should not be used for side effects">,
InGroup<UnusedValue>;
def warn_unused_call : Warning<
"ignoring return value of function declared with %0 attribute">,
"ignoring return value of function declared with %select{pure|const}0 attribute">,
InGroup<UnusedValue>;
def warn_unused_constructor : Warning<
"ignoring temporary created by a constructor declared with %0 attribute">,
Expand Down
9 changes: 5 additions & 4 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1616,22 +1616,23 @@ QualType CallExpr::getCallReturnType(const ASTContext &Ctx) const {
return FnType->getReturnType();
}

const Attr *CallExpr::getUnusedResultAttr(const ASTContext &Ctx) const {
std::pair<const Decl *, const Attr *>
CallExpr::getUnusedResultAttr(const ASTContext &Ctx) const {
// 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;
return {TD->getDecl(), 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 {D, D ? D->getAttr<WarnUnusedResultAttr>() : nullptr};
}

SourceLocation CallExpr::getBeginLoc() const {
Expand Down
8 changes: 6 additions & 2 deletions clang/lib/Sema/SemaAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,12 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
S.Diag(UnknownObjCClass->getLocation(), diag::note_forward_class);
}

S.Diag(NoteLocation, diag_available_here)
<< OffendingDecl << available_here_select_kind;
if (diag_available_here == diag::note_availability_specified_here)
S.Diag(NoteLocation, diag_available_here)
<< true << OffendingDecl << available_here_select_kind;
else
S.Diag(NoteLocation, diag_available_here)
<< OffendingDecl << available_here_select_kind;
}

void Sema::handleDelayedAvailabilityCheck(DelayedDiagnostic &DD, Decl *Ctx) {
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ void Sema::NoteDeletedFunction(FunctionDecl *Decl) {
return NoteDeletedInheritingConstructor(Ctor);

Diag(Decl->getLocation(), diag::note_availability_specified_here)
<< Decl << 1;
<< true << Decl << 1;
}

/// Determine whether a FunctionDecl was ever declared with an
Expand Down
77 changes: 53 additions & 24 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/DeclObjC.h"
Expand Down Expand Up @@ -204,23 +205,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 Decl *OffendingDecl,
const WarnUnusedResultAttr *A, SourceLocation Loc,
SourceRange R1, SourceRange R2, bool IsCtor) {
if (!A)
return false;
StringRef Msg = A->getMessage();

if (Msg.empty()) {
if (IsCtor)
return S.Diag(Loc, diag::warn_unused_constructor) << A << R1 << R2;
return S.Diag(Loc, diag::warn_unused_result) << A << R1 << R2;
S.Diag(Loc, diag::warn_unused_constructor) << A << R1 << R2;
else
S.Diag(Loc, diag::warn_unused_result) << A << R1 << R2;
} else if (IsCtor) {
S.Diag(Loc, diag::warn_unused_constructor_msg) << A << Msg << R1 << R2;
} else {
S.Diag(Loc, diag::warn_unused_result_msg) << A << 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;
// nodiscard = 3, warn_unused_result = 4
unsigned available_here_select_kind =
(A->getSpelling() == StringRef{"nodiscard"} ? 3 : 4);
return S.Diag(A->getLocation(), diag::note_availability_specified_here)
<< isa<NamedDecl>(OffendingDecl) << dyn_cast<NamedDecl>(OffendingDecl)
<< available_here_select_kind;
}

void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
Expand Down Expand Up @@ -290,9 +298,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))
const 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 @@ -302,27 +311,47 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (const Decl *FD = CE->getCalleeDecl()) {
if (ShouldSuppress)
return;
if (FD->hasAttr<PureAttr>()) {
Diag(Loc, diag::warn_unused_call) << R1 << R2 << "pure";
return;
}
if (FD->hasAttr<ConstAttr>()) {
Diag(Loc, diag::warn_unused_call) << R1 << R2 << "const";

const InheritableAttr *A = nullptr;
if (const auto *PA = FD->getAttr<PureAttr>())
A = PA;
else if (const auto *CA = FD->getAttr<ConstAttr>())
A = CA;

if (A) {
// pure = 0, const = 1 for warn_unused_call
unsigned available_here_select_kind = (isa<PureAttr>(A) ? 0 : 1);
Diag(Loc, diag::warn_unused_call)
<< R1 << R2 << available_here_select_kind;
available_here_select_kind += 5; // pure = 5, const = 6 for availability
if (const auto *ND = dyn_cast<NamedDecl>(OffendingDecl)) {
if (!ND->getIdentifier()->getBuiltinID())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still didn't get the justification for skipping if this is a builtin? It seems sensible to do so, even if the source location is a builtin.

Copy link
Contributor Author

@Mick235711 Mick235711 Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said above, the real motivation for avoiding marking here is twofold:

  1. Builtins like isdigit are just marked pure internally, the note "explicitly marked pure/const here" is just wrong, there is no such attribute on that line.
int isdigit(int c) __attribute__((overloadable)); // The note actually fires on this line, which doesn't have a pure attribute
int isdigit(int c) __attribute__((overloadable)) // expected-note {{'isdigit' has been explicitly marked unavailable here}}
  __attribute__((enable_if(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF")))
  __attribute__((unavailable("'c' must have the value of an unsigned char or EOF")));

void test3(int c) {
  isdigit(c); // expected-warning{{ignoring return value of function declared with pure attribute}}
  isdigit(10); // expected-warning{{ignoring return value of function declared with pure attribute}}
#ifndef CODEGEN
  isdigit(-10);  // expected-error{{'isdigit' is unavailable: 'c' must have the value of an unsigned char or EOF}}
#endif
}

Unless A->getLocation() is not the right invocation to find the attribute location, I don't think there is a "builtin" declaration line that can be referenced by the note.

  1. Language builtins like __builtin_operator_new will just fire the note on the first use:
void test_typo_in_args() {
  __builtin_operator_new(DNE);          // expected-error {{undeclared identifier 'DNE'}}
  __builtin_operator_new(DNE, DNE2);    // expected-error {{undeclared identifier 'DNE'}} expected-error {{'DNE2'}}
  __builtin_operator_delete(DNE);       // expected-error {{'DNE'}}
  __builtin_operator_delete(DNE, DNE2); // expected-error {{'DNE'}} expected-error {{'DNE2'}}
}

In this test case (SemaCXX/builtin-operator-new-delete.cpp), the note is generated on the second line, i.e. first use of builtin, which is even more wrong as it is not even a declaration.

Excluding builtins from note generation fixes both errors, I think...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... thats actually pretty strange: https://godbolt.org/z/q6EMMs6fa
If you make it NOT an error for the first declaration, it does what I suspect it SHOULD be doing in all cases, creating the overload set without a location: https://godbolt.org/z/4zeq99bne

So there is perhaps a bug in the builtin recovery we need to understand before we can move on.

Diag(A->getLocation(), diag::note_availability_specified_here)
<< true << ND << available_here_select_kind;
} else {
Diag(A->getLocation(), diag::note_availability_specified_here)
<< false << ND << available_here_select_kind;
}
return;
}
}
} else if (const auto *CE = dyn_cast<CXXConstructExpr>(E)) {
if (const CXXConstructorDecl *Ctor = CE->getConstructor()) {
const NamedDecl *OffendingDecl = Ctor;
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 @@ -336,8 +365,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, MD, MD->getAttr<WarnUnusedResultAttr>(), Loc,
R1, R2, /*isCtor=*/false))
return;
}
} else if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) {
Expand Down
29 changes: 15 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 @@ -2,18 +2,18 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=expected,cxx11-17,since-cxx17 -pedantic %s
// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify=expected,since-cxx17 -pedantic %s

struct [[nodiscard]] S {};
struct [[nodiscard]] S {}; // expected-note 4 {{'S' has been explicitly marked nodiscard here}}
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
S get_s();
S& get_s_ref();

enum [[nodiscard]] E {};
enum [[nodiscard]] E {}; // expected-note 2 {{'E' has been explicitly marked nodiscard here}}
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
E get_e();

[[nodiscard]] int get_i();
[[nodiscard]] int get_i(); // expected-note {{'get_i' has been explicitly marked nodiscard here}}
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
[[nodiscard]] volatile int &get_vi();
[[nodiscard]] volatile int &get_vi(); // expected-note {{'get_vi' has been explicitly marked nodiscard here}}
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}

void f() {
Expand All @@ -32,6 +32,7 @@ void f() {

[[nodiscard]] volatile char &(*fp)(); // expected-warning {{'nodiscard' attribute only applies to functions, classes, or enumerations}}
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
// expected-note@-2 {{'fp' has been explicitly marked nodiscard here}}
void g() {
fp(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}

Expand Down Expand Up @@ -67,20 +68,20 @@ void f() {
}
} // namespace PR31526

struct [[nodiscard("reason")]] ReasonStruct {};
struct [[nodiscard("reason")]] ReasonStruct {}; // expected-note {{'ReasonStruct' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}
struct LaterReason;
struct [[nodiscard("later reason")]] LaterReason {};
struct [[nodiscard("later reason")]] LaterReason {}; // expected-note {{'LaterReason' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}

ReasonStruct get_reason();
LaterReason get_later_reason();
[[nodiscard("another reason")]] int another_reason();
[[nodiscard("another reason")]] int another_reason(); // expected-note {{'another_reason' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}

[[nodiscard("conflicting reason")]] int conflicting_reason();
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}
[[nodiscard("special reason")]] int conflicting_reason();
[[nodiscard("special reason")]] int conflicting_reason(); // expected-note {{'conflicting_reason' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}

void cxx20_use() {
Expand All @@ -91,23 +92,23 @@ void cxx20_use() {
}

namespace p1771 {
struct[[nodiscard("Don't throw me away!")]] ConvertTo{};
struct[[nodiscard("Don't throw me away!")]] ConvertTo{}; // expected-note 3 {{'ConvertTo' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}
struct S {
[[nodiscard]] S();
[[nodiscard]] S(); // expected-note {{'S' has been explicitly marked nodiscard here}}
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
[[nodiscard("Don't let that S-Char go!")]] S(char);
[[nodiscard("Don't let that S-Char go!")]] S(char); // expected-note 2 {{'S' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}
S(int);
[[gnu::warn_unused_result]] S(double);
operator ConvertTo();
[[nodiscard]] operator int();
[[nodiscard]] operator int(); // expected-note 2 {{'operator int' has been explicitly marked nodiscard here}}
// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
[[nodiscard("Don't throw away as a double")]] operator double();
[[nodiscard("Don't throw away as a double")]] operator double(); // expected-note {{'operator double' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}
};

struct[[nodiscard("Don't throw me away either!")]] Y{};
struct[[nodiscard("Don't throw me away either!")]] Y{}; // expected-note {{'Y' has been explicitly marked nodiscard here}}
// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}}

void usage() {
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
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -std=c++1z -verify %s

namespace std_example {
struct [[nodiscard]] error_info{
struct [[nodiscard]] error_info{ // expected-note {{'error_info' has been explicitly marked nodiscard here}}
// ...
};

Expand Down
2 changes: 1 addition & 1 deletion clang/test/OpenMP/declare_variant_messages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ int score_and_cond_const();
template <int C>
int score_and_cond_const_inst();

__attribute__((pure)) int pure() { return 0; }
__attribute__((pure)) int pure() { return 0; } // expected-note {{'pure' has been explicitly marked pure here}}

#pragma omp declare variant(pure) match(user = {condition(1)}) // expected-warning {{ignoring return value of function declared with pure attribute}}
int unused_warning_after_specialization() { return foo(); }
Expand Down
12 changes: 6 additions & 6 deletions clang/test/Sema/c2x-nodiscard.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct [[nodiscard]] S1 { // ok
struct [[nodiscard, nodiscard]] S2 { // ok
int i;
};
struct [[nodiscard("Wrong")]] S3 {
struct [[nodiscard("Wrong")]] S3 { // expected-note {{'S3' has been explicitly marked nodiscard here}}
int i;
};

Expand All @@ -20,15 +20,15 @@ enum [[nodiscard]] E1 { One };

[[nodiscard]] int i; // expected-warning {{'nodiscard' attribute only applies to Objective-C methods, enums, structs, unions, classes, functions, function pointers, and typedefs}}

struct [[nodiscard]] S4 {
struct [[nodiscard]] S4 { // expected-note {{'S4' has been explicitly marked nodiscard here}}
int i;
};
struct S4 get_s(void);

enum [[nodiscard]] E2 { Two };
enum [[nodiscard]] E2 { Two }; // expected-note {{'E2' has been explicitly marked nodiscard here}}
enum E2 get_e(void);

[[nodiscard]] int get_i(void);
[[nodiscard]] int get_i(void); // expected-note {{'get_i' has been explicitly marked nodiscard here}}

void f2(void) {
get_s(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
Expand All @@ -43,7 +43,7 @@ void f2(void) {
(void)get_e();
}

struct [[nodiscard]] error_info{
struct [[nodiscard]] error_info{ // expected-note {{'error_info' has been explicitly marked nodiscard here}}
int i;
};

Expand All @@ -54,7 +54,7 @@ void test_missiles(void) {
launch_missiles();
}

[[nodiscard]] int f3();
[[nodiscard]] int f3(); // expected-note {{'f3' has been explicitly marked nodiscard here}}

void GH104391() {
#define M (unsigned int) f3()
Expand Down
10 changes: 5 additions & 5 deletions clang/test/Sema/unused-expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ void t4(int a) {
for (;;b < 1) {} // expected-warning{{relational comparison result unused}}
}

int t5f(void) __attribute__((warn_unused_result));
int t5f(void) __attribute__((warn_unused_result)); // expected-note {{'t5f' has been explicitly marked warn_unused_result here}}
void t5(void) {
t5f(); // expected-warning {{ignoring return value of function declared with 'warn_unused_result' attribute}}
}


int fn1(void) __attribute__ ((warn_unused_result));
int fn2() __attribute__ ((pure));
int fn3() __attribute__ ((__const));
int fn1(void) __attribute__ ((warn_unused_result)); // expected-note 3 {{'fn1' has been explicitly marked warn_unused_result here}}
int fn2() __attribute__ ((pure)); // expected-note 2 {{'fn2' has been explicitly marked pure here}}
int fn3() __attribute__ ((__const)); // expected-note {{'fn3' has been explicitly marked const here}}
int t6(void) {
if (fn1() < 0 || fn2(2,1) < 0 || fn3(2) < 0) // no warnings
return -1;
Expand All @@ -97,7 +97,7 @@ int t6(void) {
int t7 __attribute__ ((warn_unused_result)); // expected-warning {{'warn_unused_result' attribute only applies to Objective-C methods, enums, structs, unions, classes, functions, function pointers, and typedefs}}

// PR4010
int (*fn4)(void) __attribute__ ((warn_unused_result));
int (*fn4)(void) __attribute__ ((warn_unused_result)); // expected-note {{'fn4' has been explicitly marked warn_unused_result here}}
void t8(void) {
fn4(); // expected-warning {{ignoring return value of function declared with 'warn_unused_result' attribute}}
}
Expand Down
Loading
Loading