aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/Sema
diff options
context:
space:
mode:
authorcor3ntin <corentinjabot@gmail.com>2024-12-18 10:44:42 +0100
committerGitHub <noreply@github.com>2024-12-18 10:44:42 +0100
commitdb93ef14aef9c572e02bc842762bc4d0278148f9 (patch)
tree079fa17b0bc8869f1e0613d179dbbef94e58edd7 /clang/lib/Sema
parent66bdbfbaa08fa3d8e64a7fe136a8fb717f5cdbb7 (diff)
downloadllvm-db93ef14aef9c572e02bc842762bc4d0278148f9.zip
llvm-db93ef14aef9c572e02bc842762bc4d0278148f9.tar.gz
llvm-db93ef14aef9c572e02bc842762bc4d0278148f9.tar.bz2
[Clang] Implement CWG2813: Class member access with prvalues (#120223)
This is a rebase of #95112 with my own feedback apply as @MitalAshok has been inactive for a while. It's fairly important this makes clang 20 as it is a blocker for #107451 --- [CWG2813](https://cplusplus.github.io/CWG/issues/2813.html) prvalue.member_fn(expression-list) now will not materialize a temporary for prvalue if member_fn is an explicit object member function, and prvalue will bind directly to the object parameter. The E1 in E1.static_member is now a discarded-value expression, so if E1 was a call to a [[nodiscard]] function, there will now be a warning. This also affects C++98 with [[gnu::warn_unused_result]] functions. This should not affect C where TemporaryMaterializationConversion is a no-op. Closes #100314 Fixes #100341 --------- Co-authored-by: Mital Ashok <mital@mitalashok.co.uk>
Diffstat (limited to 'clang/lib/Sema')
-rw-r--r--clang/lib/Sema/SemaExprMember.cpp67
-rw-r--r--clang/lib/Sema/SemaOverload.cpp11
-rw-r--r--clang/lib/Sema/SemaStmt.cpp117
3 files changed, 129 insertions, 66 deletions
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 85d5dfcb..bcc1b92 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1003,15 +1003,6 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
: !isDependentScopeSpecifier(SS) || computeDeclContext(SS)) &&
"dependent lookup context that isn't the current instantiation?");
- // C++1z [expr.ref]p2:
- // For the first option (dot) the first expression shall be a glvalue [...]
- if (!IsArrow && BaseExpr && BaseExpr->isPRValue()) {
- ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
- if (Converted.isInvalid())
- return ExprError();
- BaseExpr = Converted.get();
- }
-
const DeclarationNameInfo &MemberNameInfo = R.getLookupNameInfo();
DeclarationName MemberName = MemberNameInfo.getName();
SourceLocation MemberLoc = MemberNameInfo.getLoc();
@@ -1128,26 +1119,68 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
BaseExpr = BuildCXXThisExpr(Loc, BaseExprType, /*IsImplicit=*/true);
}
+ // C++17 [expr.ref]p2, per CWG2813:
+ // For the first option (dot), if the id-expression names a static member or
+ // an enumerator, the first expression is a discarded-value expression; if
+ // the id-expression names a non-static data member, the first expression
+ // shall be a glvalue.
+ auto ConvertBaseExprToDiscardedValue = [&] {
+ assert(getLangOpts().CPlusPlus &&
+ "Static member / member enumerator outside of C++");
+ if (IsArrow)
+ return false;
+ ExprResult Converted = IgnoredValueConversions(BaseExpr);
+ if (Converted.isInvalid())
+ return true;
+ BaseExpr = Converted.get();
+ DiagnoseDiscardedExprMarkedNodiscard(BaseExpr);
+ return false;
+ };
+ auto ConvertBaseExprToGLValue = [&] {
+ if (IsArrow || !BaseExpr->isPRValue())
+ return false;
+ ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
+ if (Converted.isInvalid())
+ return true;
+ BaseExpr = Converted.get();
+ return false;
+ };
+
// Check the use of this member.
if (DiagnoseUseOfDecl(MemberDecl, MemberLoc))
return ExprError();
- if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl))
+ if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl)) {
+ if (ConvertBaseExprToGLValue())
+ return ExprError();
return BuildFieldReferenceExpr(BaseExpr, IsArrow, OpLoc, SS, FD, FoundDecl,
MemberNameInfo);
+ }
- if (MSPropertyDecl *PD = dyn_cast<MSPropertyDecl>(MemberDecl))
+ if (MSPropertyDecl *PD = dyn_cast<MSPropertyDecl>(MemberDecl)) {
+ // No temporaries are materialized for property references yet.
+ // They might be materialized when this is transformed into a member call.
+ // Note that this is slightly different behaviour from MSVC which doesn't
+ // implement CWG2813 yet: MSVC might materialize an extra temporary if the
+ // getter or setter function is an explicit object member function.
return BuildMSPropertyRefExpr(*this, BaseExpr, IsArrow, SS, PD,
MemberNameInfo);
+ }
- if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl))
+ if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl)) {
+ if (ConvertBaseExprToGLValue())
+ return ExprError();
// We may have found a field within an anonymous union or struct
// (C++ [class.union]).
return BuildAnonymousStructUnionMemberReference(SS, MemberLoc, FD,
FoundDecl, BaseExpr,
OpLoc);
+ }
+ // Static data member
if (VarDecl *Var = dyn_cast<VarDecl>(MemberDecl)) {
+ if (ConvertBaseExprToDiscardedValue())
+ return ExprError();
return BuildMemberExpr(BaseExpr, IsArrow, OpLoc,
SS.getWithLocInContext(Context), TemplateKWLoc, Var,
FoundDecl, /*HadMultipleCandidates=*/false,
@@ -1161,7 +1194,13 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
if (MemberFn->isInstance()) {
valueKind = VK_PRValue;
type = Context.BoundMemberTy;
+ if (MemberFn->isImplicitObjectMemberFunction() &&
+ ConvertBaseExprToGLValue())
+ return ExprError();
} else {
+ // Static member function
+ if (ConvertBaseExprToDiscardedValue())
+ return ExprError();
valueKind = VK_LValue;
type = MemberFn->getType();
}
@@ -1174,6 +1213,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
assert(!isa<FunctionDecl>(MemberDecl) && "member function not C++ method?");
if (EnumConstantDecl *Enum = dyn_cast<EnumConstantDecl>(MemberDecl)) {
+ if (ConvertBaseExprToDiscardedValue())
+ return ExprError();
return BuildMemberExpr(
BaseExpr, IsArrow, OpLoc, SS.getWithLocInContext(Context),
TemplateKWLoc, Enum, FoundDecl, /*HadMultipleCandidates=*/false,
@@ -1181,6 +1222,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
}
if (VarTemplateDecl *VarTempl = dyn_cast<VarTemplateDecl>(MemberDecl)) {
+ if (ConvertBaseExprToDiscardedValue())
+ return ExprError();
if (!TemplateArgs) {
diagnoseMissingTemplateArguments(
SS, /*TemplateKeyword=*/TemplateKWLoc.isValid(), VarTempl, MemberLoc);
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 3dabe36..fff49b7 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -5933,7 +5933,9 @@ ExprResult Sema::PerformImplicitObjectArgumentInitialization(
DestType = ImplicitParamRecordType;
FromClassification = From->Classify(Context);
- // When performing member access on a prvalue, materialize a temporary.
+ // CWG2813 [expr.call]p6:
+ // If the function is an implicit object member function, the object
+ // expression of the class member access shall be a glvalue [...]
if (From->isPRValue()) {
From = CreateMaterializeTemporaryExpr(FromRecordType, From,
Method->getRefQualifier() !=
@@ -6464,11 +6466,6 @@ static Expr *GetExplicitObjectExpr(Sema &S, Expr *Obj,
VK_LValue, OK_Ordinary, SourceLocation(),
/*CanOverflow=*/false, FPOptionsOverride());
}
- if (Obj->Classify(S.getASTContext()).isPRValue()) {
- Obj = S.CreateMaterializeTemporaryExpr(
- ObjType, Obj,
- !Fun->getParamDecl(0)->getType()->isRValueReferenceType());
- }
return Obj;
}
@@ -15584,8 +15581,6 @@ ExprResult Sema::BuildCallToMemberFunction(Scope *S, Expr *MemExprE,
CurFPFeatureOverrides(), Proto->getNumParams());
} else {
// Convert the object argument (for a non-static member function call).
- // We only need to do this if there was actually an overload; otherwise
- // it was done at lookup.
ExprResult ObjectArg = PerformImplicitObjectArgumentInitialization(
MemExpr->getBase(), Qualifier, FoundDecl, Method);
if (ObjectArg.isInvalid())
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 0e5c6cd..d9149f7 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -226,17 +226,18 @@ static bool DiagnoseNoDiscard(Sema &S, const NamedDecl *OffendingDecl,
return S.Diag(Loc, diag::warn_unused_result) << A << true << Msg << R1 << R2;
}
-void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
- if (const LabelStmt *Label = dyn_cast_or_null<LabelStmt>(S))
- return DiagnoseUnusedExprResult(Label->getSubStmt(), DiagID);
+namespace {
- const Expr *E = dyn_cast_or_null<Expr>(S);
- if (!E)
- return;
+// Diagnoses unused expressions that call functions marked [[nodiscard]],
+// [[gnu::warn_unused_result]] and similar.
+// Additionally, a DiagID can be provided to emit a warning in additional
+// contexts (such as for an unused LHS of a comma expression)
+void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
+ bool NoDiscardOnly = !DiagID.has_value();
// If we are in an unevaluated expression context, then there can be no unused
// results because the results aren't expected to be used in the first place.
- if (isUnevaluatedContext())
+ if (S.isUnevaluatedContext())
return;
SourceLocation ExprLoc = E->IgnoreParenImpCasts()->getExprLoc();
@@ -245,30 +246,31 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
// expression is a call to a function with the warn_unused_result attribute,
// we warn no matter the location. Because of the order in which the various
// checks need to happen, we factor out the macro-related test here.
- bool ShouldSuppress =
- SourceMgr.isMacroBodyExpansion(ExprLoc) ||
- SourceMgr.isInSystemMacro(ExprLoc);
+ bool ShouldSuppress = S.SourceMgr.isMacroBodyExpansion(ExprLoc) ||
+ S.SourceMgr.isInSystemMacro(ExprLoc);
const Expr *WarnExpr;
SourceLocation Loc;
SourceRange R1, R2;
- if (!E->isUnusedResultAWarning(WarnExpr, Loc, R1, R2, Context))
- return;
-
- // If this is a GNU statement expression expanded from a macro, it is probably
- // unused because it is a function-like macro that can be used as either an
- // expression or statement. Don't warn, because it is almost certainly a
- // false positive.
- if (isa<StmtExpr>(E) && Loc.isMacroID())
+ if (!E->isUnusedResultAWarning(WarnExpr, Loc, R1, R2, S.Context))
return;
- // Check if this is the UNREFERENCED_PARAMETER from the Microsoft headers.
- // That macro is frequently used to suppress "unused parameter" warnings,
- // but its implementation makes clang's -Wunused-value fire. Prevent this.
- if (isa<ParenExpr>(E->IgnoreImpCasts()) && Loc.isMacroID()) {
- SourceLocation SpellLoc = Loc;
- if (findMacroSpelling(SpellLoc, "UNREFERENCED_PARAMETER"))
+ if (!NoDiscardOnly) {
+ // If this is a GNU statement expression expanded from a macro, it is
+ // probably unused because it is a function-like macro that can be used as
+ // either an expression or statement. Don't warn, because it is almost
+ // certainly a false positive.
+ if (isa<StmtExpr>(E) && Loc.isMacroID())
return;
+
+ // Check if this is the UNREFERENCED_PARAMETER from the Microsoft headers.
+ // That macro is frequently used to suppress "unused parameter" warnings,
+ // but its implementation makes clang's -Wunused-value fire. Prevent this.
+ if (isa<ParenExpr>(E->IgnoreImpCasts()) && Loc.isMacroID()) {
+ SourceLocation SpellLoc = Loc;
+ if (S.findMacroSpelling(SpellLoc, "UNREFERENCED_PARAMETER"))
+ return;
+ }
}
// Okay, we have an unused result. Depending on what the base expression is,
@@ -279,7 +281,7 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (const CXXBindTemporaryExpr *TempExpr = dyn_cast<CXXBindTemporaryExpr>(E))
E = TempExpr->getSubExpr();
- if (DiagnoseUnusedComparison(*this, E))
+ if (DiagnoseUnusedComparison(S, E))
return;
E = WarnExpr;
@@ -293,8 +295,8 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (E->getType()->isVoidType())
return;
- auto [OffendingDecl, A] = CE->getUnusedResultAttr(Context);
- if (DiagnoseNoDiscard(*this, OffendingDecl,
+ auto [OffendingDecl, A] = CE->getUnusedResultAttr(S.Context);
+ if (DiagnoseNoDiscard(S, OffendingDecl,
cast_or_null<WarnUnusedResultAttr>(A), Loc, R1, R2,
/*isCtor=*/false))
return;
@@ -307,11 +309,11 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (ShouldSuppress)
return;
if (FD->hasAttr<PureAttr>()) {
- Diag(Loc, diag::warn_unused_call) << R1 << R2 << "pure";
+ S.Diag(Loc, diag::warn_unused_call) << R1 << R2 << "pure";
return;
}
if (FD->hasAttr<ConstAttr>()) {
- Diag(Loc, diag::warn_unused_call) << R1 << R2 << "const";
+ S.Diag(Loc, diag::warn_unused_call) << R1 << R2 << "const";
return;
}
}
@@ -323,15 +325,15 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
OffendingDecl = Ctor->getParent();
A = OffendingDecl->getAttr<WarnUnusedResultAttr>();
}
- if (DiagnoseNoDiscard(*this, OffendingDecl, A, Loc, R1, R2,
+ if (DiagnoseNoDiscard(S, 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, TD->getAttr<WarnUnusedResultAttr>(), Loc,
- R1, R2, /*isCtor=*/false))
+ if (DiagnoseNoDiscard(S, TD, TD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
+ R2, /*isCtor=*/false))
return;
}
} else if (ShouldSuppress)
@@ -339,23 +341,24 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
E = WarnExpr;
if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E)) {
- if (getLangOpts().ObjCAutoRefCount && ME->isDelegateInitCall()) {
- Diag(Loc, diag::err_arc_unused_init_message) << R1;
+ if (S.getLangOpts().ObjCAutoRefCount && ME->isDelegateInitCall()) {
+ S.Diag(Loc, diag::err_arc_unused_init_message) << R1;
return;
}
const ObjCMethodDecl *MD = ME->getMethodDecl();
if (MD) {
- if (DiagnoseNoDiscard(*this, nullptr, MD->getAttr<WarnUnusedResultAttr>(),
- Loc, R1, R2, /*isCtor=*/false))
+ if (DiagnoseNoDiscard(S, nullptr, MD->getAttr<WarnUnusedResultAttr>(),
+ Loc, R1, R2,
+ /*isCtor=*/false))
return;
}
} else if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) {
const Expr *Source = POE->getSyntacticForm();
// Handle the actually selected call of an OpenMP specialized call.
- if (LangOpts.OpenMP && isa<CallExpr>(Source) &&
+ if (S.LangOpts.OpenMP && isa<CallExpr>(Source) &&
POE->getNumSemanticExprs() == 1 &&
isa<CallExpr>(POE->getSemanticExpr(0)))
- return DiagnoseUnusedExprResult(POE->getSemanticExpr(0), DiagID);
+ return DiagnoseUnused(S, POE->getSemanticExpr(0), DiagID);
if (isa<ObjCSubscriptRefExpr>(Source))
DiagID = diag::warn_unused_container_subscript_expr;
else if (isa<ObjCPropertyRefExpr>(Source))
@@ -372,17 +375,21 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (!RD->getAttr<WarnUnusedAttr>())
return;
}
+
+ if (NoDiscardOnly)
+ return;
+
// Diagnose "(void*) blah" as a typo for "(void) blah".
- else if (const CStyleCastExpr *CE = dyn_cast<CStyleCastExpr>(E)) {
+ if (const CStyleCastExpr *CE = dyn_cast<CStyleCastExpr>(E)) {
TypeSourceInfo *TI = CE->getTypeInfoAsWritten();
QualType T = TI->getType();
// We really do want to use the non-canonical type here.
- if (T == Context.VoidPtrTy) {
+ if (T == S.Context.VoidPtrTy) {
PointerTypeLoc TL = TI->getTypeLoc().castAs<PointerTypeLoc>();
- Diag(Loc, diag::warn_unused_voidptr)
- << FixItHint::CreateRemoval(TL.getStarLoc());
+ S.Diag(Loc, diag::warn_unused_voidptr)
+ << FixItHint::CreateRemoval(TL.getStarLoc());
return;
}
}
@@ -391,16 +398,34 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
// isn't an array.
if (E->isGLValue() && E->getType().isVolatileQualified() &&
!E->getType()->isArrayType()) {
- Diag(Loc, diag::warn_unused_volatile) << R1 << R2;
+ S.Diag(Loc, diag::warn_unused_volatile) << R1 << R2;
return;
}
// Do not diagnose use of a comma operator in a SFINAE context because the
// type of the left operand could be used for SFINAE, so technically it is
// *used*.
- if (DiagID != diag::warn_unused_comma_left_operand || !isSFINAEContext())
- DiagIfReachable(Loc, S ? llvm::ArrayRef(S) : llvm::ArrayRef<Stmt *>(),
- PDiag(DiagID) << R1 << R2);
+ if (DiagID == diag::warn_unused_comma_left_operand && S.isSFINAEContext())
+ return;
+
+ S.DiagIfReachable(Loc, llvm::ArrayRef<const Stmt *>(E),
+ S.PDiag(*DiagID) << R1 << R2);
+}
+} // namespace
+
+void Sema::DiagnoseDiscardedExprMarkedNodiscard(const Expr *E) {
+ DiagnoseUnused(*this, E, std::nullopt);
+}
+
+void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
+ if (const LabelStmt *Label = dyn_cast_if_present<LabelStmt>(S))
+ S = Label->getSubStmt();
+
+ const Expr *E = dyn_cast_if_present<Expr>(S);
+ if (!E)
+ return;
+
+ DiagnoseUnused(*this, E, DiagID);
}
void Sema::ActOnStartOfCompoundStmt(bool IsStmtExpr) {