diff options
author | cor3ntin <corentinjabot@gmail.com> | 2024-12-18 10:44:42 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-18 10:44:42 +0100 |
commit | db93ef14aef9c572e02bc842762bc4d0278148f9 (patch) | |
tree | 079fa17b0bc8869f1e0613d179dbbef94e58edd7 /clang/lib/Sema | |
parent | 66bdbfbaa08fa3d8e64a7fe136a8fb717f5cdbb7 (diff) | |
download | llvm-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.cpp | 67 | ||||
-rw-r--r-- | clang/lib/Sema/SemaOverload.cpp | 11 | ||||
-rw-r--r-- | clang/lib/Sema/SemaStmt.cpp | 117 |
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) { |