Skip to content
Merged
12 changes: 12 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,18 @@ C23 Feature Support
treated as if the compound literal were within the body rather than at file
scope.

C11 Feature Support
^^^^^^^^^^^^^^^^^^^
- Implemented `WG14 N1285 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1285.htm>`_
which introduces the notion of objects with a temporary lifetime. When an
expression resulting in an rvalue with structure or union type and that type
contains a member of array type, the expression result is an automatic storage
duration object with temporary lifetime which begins when the expression is
evaluated and ends at the evaluation of the containing full expression. This
functionality is also implemented for earlier C language modes because the
C99 semantics will never be implemented (it would require dynamic allocations
of memory which leaks, which users would not appreciate).

Non-comprehensive list of changes in this release
-------------------------------------------------

Expand Down
11 changes: 9 additions & 2 deletions clang/lib/CodeGen/CGDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2269,11 +2269,18 @@ void CodeGenFunction::pushDestroy(QualType::DestructionKind dtorKind,
cleanupKind & EHCleanup);
}

void CodeGenFunction::pushLifetimeExtendedDestroy(
QualType::DestructionKind dtorKind, Address addr, QualType type) {
CleanupKind cleanupKind = getCleanupKind(dtorKind);
pushLifetimeExtendedDestroy(cleanupKind, addr, type, getDestroyer(dtorKind),
cleanupKind & EHCleanup);
}

void CodeGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
QualType type, Destroyer *destroyer,
bool useEHCleanupForArray) {
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type,
destroyer, useEHCleanupForArray);
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type, destroyer,
useEHCleanupForArray);
}

// Pushes a destroy and defers its deactivation until its
Expand Down
86 changes: 40 additions & 46 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,55 +390,49 @@ pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
}
}

CXXDestructorDecl *ReferenceTemporaryDtor = nullptr;
if (const RecordType *RT =
E->getType()->getBaseElementTypeUnsafe()->getAs<RecordType>()) {
// Get the destructor for the reference temporary.
auto *ClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (!ClassDecl->hasTrivialDestructor())
ReferenceTemporaryDtor = ClassDecl->getDestructor();
}
QualType::DestructionKind DK = E->getType().isDestructedType();
if (DK != QualType::DK_none) {
switch (M->getStorageDuration()) {
case SD_Static:
case SD_Thread: {
CXXDestructorDecl *ReferenceTemporaryDtor = nullptr;
if (const RecordType *RT =
E->getType()->getBaseElementTypeUnsafe()->getAs<RecordType>()) {
// Get the destructor for the reference temporary.
if (auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl());
ClassDecl && !ClassDecl->hasTrivialDestructor())
ReferenceTemporaryDtor = ClassDecl->getDestructor();
}

if (!ReferenceTemporaryDtor)
return;
if (!ReferenceTemporaryDtor)
return;

// Call the destructor for the temporary.
switch (M->getStorageDuration()) {
case SD_Static:
case SD_Thread: {
llvm::FunctionCallee CleanupFn;
llvm::Constant *CleanupArg;
if (E->getType()->isArrayType()) {
CleanupFn = CodeGenFunction(CGF.CGM).generateDestroyHelper(
ReferenceTemporary, E->getType(),
CodeGenFunction::destroyCXXObject, CGF.getLangOpts().Exceptions,
dyn_cast_or_null<VarDecl>(M->getExtendingDecl()));
CleanupArg = llvm::Constant::getNullValue(CGF.Int8PtrTy);
} else {
CleanupFn = CGF.CGM.getAddrAndTypeOfCXXStructor(
GlobalDecl(ReferenceTemporaryDtor, Dtor_Complete));
CleanupArg = cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(CGF));
llvm::FunctionCallee CleanupFn;
llvm::Constant *CleanupArg;
if (E->getType()->isArrayType()) {
CleanupFn = CodeGenFunction(CGF.CGM).generateDestroyHelper(
ReferenceTemporary, E->getType(), CodeGenFunction::destroyCXXObject,
CGF.getLangOpts().Exceptions,
dyn_cast_or_null<VarDecl>(M->getExtendingDecl()));
CleanupArg = llvm::Constant::getNullValue(CGF.Int8PtrTy);
} else {
CleanupFn = CGF.CGM.getAddrAndTypeOfCXXStructor(
GlobalDecl(ReferenceTemporaryDtor, Dtor_Complete));
CleanupArg =
cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(CGF));
}
CGF.CGM.getCXXABI().registerGlobalDtor(
CGF, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
} break;
case SD_FullExpression:
CGF.pushDestroy(DK, ReferenceTemporary, E->getType());
break;
case SD_Automatic:
CGF.pushLifetimeExtendedDestroy(DK, ReferenceTemporary, E->getType());
break;
case SD_Dynamic:
llvm_unreachable("temporary cannot have dynamic storage duration");
}
CGF.CGM.getCXXABI().registerGlobalDtor(
CGF, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
break;
}

case SD_FullExpression:
CGF.pushDestroy(NormalAndEHCleanup, ReferenceTemporary, E->getType(),
CodeGenFunction::destroyCXXObject,
CGF.getLangOpts().Exceptions);
break;

case SD_Automatic:
CGF.pushLifetimeExtendedDestroy(NormalAndEHCleanup,
ReferenceTemporary, E->getType(),
CodeGenFunction::destroyCXXObject,
CGF.getLangOpts().Exceptions);
break;

case SD_Dynamic:
llvm_unreachable("temporary cannot have dynamic storage duration");
}
}

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,8 @@ class CodeGenFunction : public CodeGenTypeCache {
void pushLifetimeExtendedDestroy(CleanupKind kind, Address addr,
QualType type, Destroyer *destroyer,
bool useEHCleanupForArray);
void pushLifetimeExtendedDestroy(QualType::DestructionKind dtorKind,
Address addr, QualType type);
void pushCallObjectDeleteCleanup(const FunctionDecl *OperatorDelete,
llvm::Value *CompletePtr,
QualType ElementType);
Expand Down
7 changes: 5 additions & 2 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7650,11 +7650,14 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary,
}

ExprResult Sema::TemporaryMaterializationConversion(Expr *E) {
// In C++98, we don't want to implicitly create an xvalue.
// In C++98, we don't want to implicitly create an xvalue. C11 added the
// same rule, but C99 is broken without this behavior and so we treat the
// change as applying to all C language modes.
// FIXME: This means that AST consumers need to deal with "prvalues" that
// denote materialized temporaries. Maybe we should add another ValueKind
// for "xvalue pretending to be a prvalue" for C++98 support.
if (!E->isPRValue() || !getLangOpts().CPlusPlus11)
if (!E->isPRValue() ||
(!getLangOpts().CPlusPlus11 && getLangOpts().CPlusPlus))
return E;

// C++1z [conv.rval]/1: T shall be a complete type.
Expand Down
84 changes: 71 additions & 13 deletions clang/test/C/C11/n1285.c
Original file line number Diff line number Diff line change
@@ -1,24 +1,82 @@
// RUN: %clang_cc1 -verify=wrong -std=c99 %s
// RUN: %clang_cc1 -verify=wrong -std=c11 %s
// RUN: %clang_cc1 -verify=cpp -std=c++11 -x c++ %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c -std=c99 %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c -std=c11 %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cpp -std=c++11 -x c++ %s

/* WG14 N1285: No
/* WG14 N1285: Clang 21
* Extending the lifetime of temporary objects (factored approach)
*
* NB: we do not properly materialize temporary expressions in situations where
* it would be expected; that is why the "no-diagnostics" marking is named
* "wrong". We do issue the expected diagnostic in C++ mode.
* This paper introduced the notion of an object with a temporary lifetime. Any
* operation resulting in an rvalue of structure or union type which contains
* an array results in an object with temporary lifetime.
*
* Even though this is a change for C11, we treat it as a DR and apply it
* retroactively to earlier C language modes.
*/

// wrong-no-diagnostics
// C11 6.2.4p8: A non-lvalue expression with structure or union type, where the
// structure or union contains a member with array type (including,
// recursively, members of all contained structures and unions) refers to an
// object with automatic storage duration and temporary lifetime. Its lifetime
// begins when the expression is evaluated and its initial value is the value
// of the expression. Its lifetime ends when the evaluation of the containing
// full expression or full declarator ends. Any attempt to modify an object
// with temporary lifetime results in undefined behavior.

struct X { int a[5]; };
struct X f(void);

int foo(void) {
// FIXME: This diagnostic should be issued in C11 as well (though not in C99,
// as this paper was a breaking change between C99 and C11).
int *p = f().a; // cpp-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
return *p;
union U { int a[10]; double d; };
union U g(void);

void sink(int *);

int func_return(void) {
int *p = f().a; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = f().a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
p = g().a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink(f().a); // Ok
return *f().a; // Ok
}

int ternary(void) {
int *p = (1 ? (struct X){ 0 } : f()).a; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
int *r = (1 ? (union U){ 0 } : g()).a; // expected-warning {{temporary whose address is used as value of local variable 'r' will be destroyed at the end of the full-expression}}
p = (1 ? (struct X){ 0 } : f()).a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink((1 ? (struct X){ 0 } : f()).a); // Ok

// This intentionally gets one diagnostic in C and two in C++. In C, the
// compound literal results in an lvalue, not an rvalue as it does in C++. So
// only one branch results in a temporary in C but both branches do in C++.
int *q = 1 ? (struct X){ 0 }.a : f().a; // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}} \
cpp-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
q = 1 ? (struct X){ 0 }.a : f().a; // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}} \
cpp-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
q = 1 ? (struct X){ 0 }.a : g().a; // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}} \
cpp-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
sink(1 ? (struct X){ 0 }.a : f().a); // Ok
return *(1 ? (struct X){ 0 }.a : f().a); // Ok
}

int comma(void) {
struct X x;
int *p = ((void)0, x).a; // c-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = ((void)0, x).a; // c-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink(((void)0, x).a); // Ok
return *(((void)0, x).a);// Ok
}

int cast(void) {
struct X x;
int *p = ((struct X)x).a; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = ((struct X)x).a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink(((struct X)x).a); // Ok
return *(((struct X)x).a); // Ok
}

int assign(void) {
struct X x, s;
int *p = (x = s).a; // c-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = (x = s).a; // c-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink((x = s).a); // Ok
return *((x = s).a); // Ok
}
Loading
Loading