Skip to content

Commit a63bbf2

Browse files
authored
[clang] Diagnose [[nodiscard]] return types in Objective-C++ (#142541)
My solution was to copy-paste getUnusedResultAttr and hasUnusedResultAttr from CallExpr into ObjCMessageExpr too. Fixes #141504
1 parent 5dc9937 commit a63bbf2

File tree

9 files changed

+126
-27
lines changed

9 files changed

+126
-27
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ Bug Fixes to Compiler Builtins
139139
Bug Fixes to Attribute Support
140140
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141141

142+
- ``[[nodiscard]]`` is now respected on Objective-C and Objective-C++ methods.
143+
(#GH141504)
144+
142145
Bug Fixes to C++ Support
143146
^^^^^^^^^^^^^^^^^^^^^^^^
144147
- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665)

clang/include/clang/AST/Expr.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "clang/AST/APNumericStorage.h"
1717
#include "clang/AST/APValue.h"
1818
#include "clang/AST/ASTVector.h"
19+
#include "clang/AST/Attr.h"
1920
#include "clang/AST/ComputeDependence.h"
2021
#include "clang/AST/Decl.h"
2122
#include "clang/AST/DeclAccessPair.h"
@@ -262,6 +263,12 @@ class Expr : public ValueStmt {
262263
SourceRange &R1, SourceRange &R2,
263264
ASTContext &Ctx) const;
264265

266+
/// Returns the WarnUnusedResultAttr that is declared on the callee
267+
/// or its return type declaration, together with a NamedDecl that
268+
/// refers to the declaration the attribute is attached to.
269+
static std::pair<const NamedDecl *, const WarnUnusedResultAttr *>
270+
getUnusedResultAttrImpl(const Decl *Callee, QualType ReturnType);
271+
265272
/// isLValue - True if this expression is an "l-value" according to
266273
/// the rules of the current language. C and C++ give somewhat
267274
/// different rules for this concept, but in general, the result of
@@ -3190,11 +3197,13 @@ class CallExpr : public Expr {
31903197
/// type.
31913198
QualType getCallReturnType(const ASTContext &Ctx) const;
31923199

3193-
/// Returns the WarnUnusedResultAttr that is either declared on the called
3194-
/// function, or its return type declaration, together with a NamedDecl that
3195-
/// refers to the declaration the attribute is attached onto.
3196-
std::pair<const NamedDecl *, const Attr *>
3197-
getUnusedResultAttr(const ASTContext &Ctx) const;
3200+
/// Returns the WarnUnusedResultAttr that is declared on the callee
3201+
/// or its return type declaration, together with a NamedDecl that
3202+
/// refers to the declaration the attribute is attached to.
3203+
std::pair<const NamedDecl *, const WarnUnusedResultAttr *>
3204+
getUnusedResultAttr(const ASTContext &Ctx) const {
3205+
return getUnusedResultAttrImpl(getCalleeDecl(), getCallReturnType(Ctx));
3206+
}
31983207

31993208
/// Returns true if this call expression should warn on unused results.
32003209
bool hasUnusedResultAttr(const ASTContext &Ctx) const {

clang/include/clang/AST/ExprObjC.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifndef LLVM_CLANG_AST_EXPROBJC_H
1414
#define LLVM_CLANG_AST_EXPROBJC_H
1515

16+
#include "clang/AST/Attr.h"
1617
#include "clang/AST/ComputeDependence.h"
1718
#include "clang/AST/Decl.h"
1819
#include "clang/AST/DeclObjC.h"
@@ -1234,6 +1235,19 @@ class ObjCMessageExpr final
12341235
/// of `instancetype` (in that case it's an expression type).
12351236
QualType getCallReturnType(ASTContext &Ctx) const;
12361237

1238+
/// Returns the WarnUnusedResultAttr that is declared on the callee
1239+
/// or its return type declaration, together with a NamedDecl that
1240+
/// refers to the declaration the attribute is attached to.
1241+
std::pair<const NamedDecl *, const WarnUnusedResultAttr *>
1242+
getUnusedResultAttr(ASTContext &Ctx) const {
1243+
return getUnusedResultAttrImpl(getMethodDecl(), getCallReturnType(Ctx));
1244+
}
1245+
1246+
/// Returns true if this message send should warn on unused results.
1247+
bool hasUnusedResultAttr(ASTContext &Ctx) const {
1248+
return getUnusedResultAttr(Ctx).second != nullptr;
1249+
}
1250+
12371251
/// Source range of the receiver.
12381252
SourceRange getReceiverRange() const;
12391253

clang/lib/AST/Expr.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,20 +1629,20 @@ QualType CallExpr::getCallReturnType(const ASTContext &Ctx) const {
16291629
return FnType->getReturnType();
16301630
}
16311631

1632-
std::pair<const NamedDecl *, const Attr *>
1633-
CallExpr::getUnusedResultAttr(const ASTContext &Ctx) const {
1632+
std::pair<const NamedDecl *, const WarnUnusedResultAttr *>
1633+
Expr::getUnusedResultAttrImpl(const Decl *Callee, QualType ReturnType) {
16341634
// If the callee is marked nodiscard, return that attribute
1635-
if (const Decl *D = getCalleeDecl())
1636-
if (const auto *A = D->getAttr<WarnUnusedResultAttr>())
1635+
if (Callee != nullptr)
1636+
if (const auto *A = Callee->getAttr<WarnUnusedResultAttr>())
16371637
return {nullptr, A};
16381638

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

1645-
for (const auto *TD = getCallReturnType(Ctx)->getAs<TypedefType>(); TD;
1645+
for (const auto *TD = ReturnType->getAs<TypedefType>(); TD;
16461646
TD = TD->desugar()->getAs<TypedefType>())
16471647
if (const auto *A = TD->getDecl()->getAttr<WarnUnusedResultAttr>())
16481648
return {TD->getDecl(), A};
@@ -2844,12 +2844,11 @@ bool Expr::isUnusedResultAWarning(const Expr *&WarnE, SourceLocation &Loc,
28442844
return true;
28452845
}
28462846

2847-
if (const ObjCMethodDecl *MD = ME->getMethodDecl())
2848-
if (MD->hasAttr<WarnUnusedResultAttr>()) {
2849-
WarnE = this;
2850-
Loc = getExprLoc();
2851-
return true;
2852-
}
2847+
if (ME->hasUnusedResultAttr(Ctx)) {
2848+
WarnE = this;
2849+
Loc = getExprLoc();
2850+
return true;
2851+
}
28532852

28542853
return false;
28552854
}

clang/lib/AST/ExprObjC.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "clang/AST/ExprObjC.h"
1414
#include "clang/AST/ASTContext.h"
15+
#include "clang/AST/Attr.h"
1516
#include "clang/AST/ComputeDependence.h"
1617
#include "clang/AST/SelectorLocationsKind.h"
1718
#include "clang/AST/Type.h"

clang/lib/Sema/SemaStmt.cpp

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,7 @@ void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
295295
return;
296296

297297
auto [OffendingDecl, A] = CE->getUnusedResultAttr(S.Context);
298-
if (DiagnoseNoDiscard(S, OffendingDecl,
299-
cast_or_null<WarnUnusedResultAttr>(A), Loc, R1, R2,
298+
if (DiagnoseNoDiscard(S, OffendingDecl, A, Loc, R1, R2,
300299
/*isCtor=*/false))
301300
return;
302301

@@ -344,13 +343,11 @@ void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
344343
S.Diag(Loc, diag::err_arc_unused_init_message) << R1;
345344
return;
346345
}
347-
const ObjCMethodDecl *MD = ME->getMethodDecl();
348-
if (MD) {
349-
if (DiagnoseNoDiscard(S, nullptr, MD->getAttr<WarnUnusedResultAttr>(),
350-
Loc, R1, R2,
351-
/*isCtor=*/false))
352-
return;
353-
}
346+
347+
auto [OffendingDecl, A] = ME->getUnusedResultAttr(S.Context);
348+
if (DiagnoseNoDiscard(S, OffendingDecl, A, Loc, R1, R2,
349+
/*isCtor=*/false))
350+
return;
354351
} else if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) {
355352
const Expr *Source = POE->getSyntacticForm();
356353
// Handle the actually selected call of an OpenMP specialized call.

clang/test/SemaCXX/warn-unused-result.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,31 @@ void id_print_name() {
365365
}
366366
} // namespace GH117975
367367

368+
namespace inheritance {
369+
// Test that [[nodiscard]] is not inherited by derived class types,
370+
// but is inherited by member functions
371+
struct [[nodiscard]] E {
372+
[[nodiscard]] explicit E(int);
373+
explicit E(const char*);
374+
[[nodiscard]] int f();
375+
};
376+
struct F : E {
377+
using E::E;
378+
};
379+
E e();
380+
F f();
381+
void test() {
382+
e(); // expected-warning {{ignoring return value of type 'E' declared with 'nodiscard' attribute}}
383+
f(); // no warning: derived class type does not inherit the attribute
384+
E(1); // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute}}
385+
E("x"); // expected-warning {{ignoring temporary of type 'E' declared with 'nodiscard' attribute}}
386+
F(1); // no warning: inherited constructor does not inherit the attribute either
387+
F("x"); // no warning
388+
e().f(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
389+
f().f(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
390+
}
391+
} // namespace inheritance
392+
368393
namespace BuildStringOnClangScope {
369394

370395
[[clang::warn_unused_result("Discarded result")]]
@@ -381,4 +406,4 @@ void doGccThings() {
381406
makeGccTrue(); // expected-warning {{ignoring return value of function declared with 'gnu::warn_unused_result' attribute}}
382407
}
383408

384-
}
409+
} // namespace BuildStringOnClangScope

clang/test/SemaObjC/attr-nodiscard.m

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
3+
struct [[nodiscard]] expected {};
4+
5+
typedef struct expected E;
6+
7+
@interface INTF
8+
- (int) a [[nodiscard]];
9+
+ (int) b [[nodiscard]];
10+
- (struct expected) c;
11+
+ (struct expected) d;
12+
- (E) e;
13+
+ (E) f;
14+
- (void) g [[nodiscard]]; // expected-warning {{attribute 'nodiscard' cannot be applied to Objective-C method without return value}}
15+
@end
16+
17+
void foo(INTF *a) {
18+
[a a]; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
19+
[INTF b]; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
20+
[a c]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}}
21+
[INTF d]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}}
22+
[a e]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}}
23+
[INTF f]; // expected-warning {{ignoring return value of type 'expected' declared with 'nodiscard' attribute}}
24+
[a g];
25+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
3+
template<class T>
4+
struct [[nodiscard]] expected {};
5+
6+
using E = expected<int>;
7+
8+
@interface INTF
9+
- (int) a [[nodiscard]];
10+
+ (int) b [[nodiscard]];
11+
- (expected<int>) c;
12+
+ (expected<int>) d;
13+
- (E) e;
14+
+ (E) f;
15+
- (void) g [[nodiscard]]; // expected-warning {{attribute 'nodiscard' cannot be applied to Objective-C method without return value}}
16+
@end
17+
18+
void foo(INTF *a) {
19+
[a a]; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
20+
[INTF b]; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
21+
[a c]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
22+
[INTF d]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
23+
[a e]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
24+
[INTF f]; // expected-warning {{ignoring return value of type 'expected<int>' declared with 'nodiscard' attribute}}
25+
[a g];
26+
}

0 commit comments

Comments
 (0)