Skip to content

Commit 5c8ba28

Browse files
authored
[C11] Implement WG14 N1285 (temporary lifetimes) (#133472)
This feature largely models the same behavior as in C++11. It is technically a breaking change between C99 and C11, so the paper is not being backported to older language modes. One difference between C++ and C is that things which are rvalues in C are often lvalues in C++ (such as the result of a ternary operator or a comma operator). Fixes #96486
1 parent 71f629f commit 5c8ba28

File tree

10 files changed

+388
-67
lines changed

10 files changed

+388
-67
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ C23 Feature Support
162162
- Fixed a bug where you could not cast a null pointer constant to type
163163
``nullptr_t``. Fixes #GH133644.
164164

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

clang/lib/CodeGen/CGDecl.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2265,11 +2265,18 @@ void CodeGenFunction::pushDestroy(QualType::DestructionKind dtorKind,
22652265
cleanupKind & EHCleanup);
22662266
}
22672267

2268+
void CodeGenFunction::pushLifetimeExtendedDestroy(
2269+
QualType::DestructionKind dtorKind, Address addr, QualType type) {
2270+
CleanupKind cleanupKind = getCleanupKind(dtorKind);
2271+
pushLifetimeExtendedDestroy(cleanupKind, addr, type, getDestroyer(dtorKind),
2272+
cleanupKind & EHCleanup);
2273+
}
2274+
22682275
void CodeGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
22692276
QualType type, Destroyer *destroyer,
22702277
bool useEHCleanupForArray) {
2271-
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type,
2272-
destroyer, useEHCleanupForArray);
2278+
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type, destroyer,
2279+
useEHCleanupForArray);
22732280
}
22742281

22752282
// Pushes a destroy and defers its deactivation until its

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 40 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -393,55 +393,49 @@ pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
393393
}
394394
}
395395

396-
CXXDestructorDecl *ReferenceTemporaryDtor = nullptr;
397-
if (const RecordType *RT =
398-
E->getType()->getBaseElementTypeUnsafe()->getAs<RecordType>()) {
399-
// Get the destructor for the reference temporary.
400-
auto *ClassDecl = cast<CXXRecordDecl>(RT->getDecl());
401-
if (!ClassDecl->hasTrivialDestructor())
402-
ReferenceTemporaryDtor = ClassDecl->getDestructor();
403-
}
396+
QualType::DestructionKind DK = E->getType().isDestructedType();
397+
if (DK != QualType::DK_none) {
398+
switch (M->getStorageDuration()) {
399+
case SD_Static:
400+
case SD_Thread: {
401+
CXXDestructorDecl *ReferenceTemporaryDtor = nullptr;
402+
if (const RecordType *RT =
403+
E->getType()->getBaseElementTypeUnsafe()->getAs<RecordType>()) {
404+
// Get the destructor for the reference temporary.
405+
if (auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl());
406+
ClassDecl && !ClassDecl->hasTrivialDestructor())
407+
ReferenceTemporaryDtor = ClassDecl->getDestructor();
408+
}
404409

405-
if (!ReferenceTemporaryDtor)
406-
return;
410+
if (!ReferenceTemporaryDtor)
411+
return;
407412

408-
// Call the destructor for the temporary.
409-
switch (M->getStorageDuration()) {
410-
case SD_Static:
411-
case SD_Thread: {
412-
llvm::FunctionCallee CleanupFn;
413-
llvm::Constant *CleanupArg;
414-
if (E->getType()->isArrayType()) {
415-
CleanupFn = CodeGenFunction(CGF.CGM).generateDestroyHelper(
416-
ReferenceTemporary, E->getType(),
417-
CodeGenFunction::destroyCXXObject, CGF.getLangOpts().Exceptions,
418-
dyn_cast_or_null<VarDecl>(M->getExtendingDecl()));
419-
CleanupArg = llvm::Constant::getNullValue(CGF.Int8PtrTy);
420-
} else {
421-
CleanupFn = CGF.CGM.getAddrAndTypeOfCXXStructor(
422-
GlobalDecl(ReferenceTemporaryDtor, Dtor_Complete));
423-
CleanupArg = cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(CGF));
413+
llvm::FunctionCallee CleanupFn;
414+
llvm::Constant *CleanupArg;
415+
if (E->getType()->isArrayType()) {
416+
CleanupFn = CodeGenFunction(CGF.CGM).generateDestroyHelper(
417+
ReferenceTemporary, E->getType(), CodeGenFunction::destroyCXXObject,
418+
CGF.getLangOpts().Exceptions,
419+
dyn_cast_or_null<VarDecl>(M->getExtendingDecl()));
420+
CleanupArg = llvm::Constant::getNullValue(CGF.Int8PtrTy);
421+
} else {
422+
CleanupFn = CGF.CGM.getAddrAndTypeOfCXXStructor(
423+
GlobalDecl(ReferenceTemporaryDtor, Dtor_Complete));
424+
CleanupArg =
425+
cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(CGF));
426+
}
427+
CGF.CGM.getCXXABI().registerGlobalDtor(
428+
CGF, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
429+
} break;
430+
case SD_FullExpression:
431+
CGF.pushDestroy(DK, ReferenceTemporary, E->getType());
432+
break;
433+
case SD_Automatic:
434+
CGF.pushLifetimeExtendedDestroy(DK, ReferenceTemporary, E->getType());
435+
break;
436+
case SD_Dynamic:
437+
llvm_unreachable("temporary cannot have dynamic storage duration");
424438
}
425-
CGF.CGM.getCXXABI().registerGlobalDtor(
426-
CGF, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
427-
break;
428-
}
429-
430-
case SD_FullExpression:
431-
CGF.pushDestroy(NormalAndEHCleanup, ReferenceTemporary, E->getType(),
432-
CodeGenFunction::destroyCXXObject,
433-
CGF.getLangOpts().Exceptions);
434-
break;
435-
436-
case SD_Automatic:
437-
CGF.pushLifetimeExtendedDestroy(NormalAndEHCleanup,
438-
ReferenceTemporary, E->getType(),
439-
CodeGenFunction::destroyCXXObject,
440-
CGF.getLangOpts().Exceptions);
441-
break;
442-
443-
case SD_Dynamic:
444-
llvm_unreachable("temporary cannot have dynamic storage duration");
445439
}
446440
}
447441

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,6 +2249,8 @@ class CodeGenFunction : public CodeGenTypeCache {
22492249
void pushLifetimeExtendedDestroy(CleanupKind kind, Address addr,
22502250
QualType type, Destroyer *destroyer,
22512251
bool useEHCleanupForArray);
2252+
void pushLifetimeExtendedDestroy(QualType::DestructionKind dtorKind,
2253+
Address addr, QualType type);
22522254
void pushCallObjectDeleteCleanup(const FunctionDecl *OperatorDelete,
22532255
llvm::Value *CompletePtr,
22542256
QualType ElementType);

clang/lib/Sema/SemaInit.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7650,11 +7650,14 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary,
76507650
}
76517651

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

76607663
// C++1z [conv.rval]/1: T shall be a complete type.

clang/test/C/C11/n1285.c

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,82 @@
1-
// RUN: %clang_cc1 -verify=wrong -std=c99 %s
2-
// RUN: %clang_cc1 -verify=wrong -std=c11 %s
3-
// RUN: %clang_cc1 -verify=cpp -std=c++11 -x c++ %s
1+
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c -std=c99 %s
2+
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c -std=c11 %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cpp -std=c++11 -x c++ %s
44

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

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

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

18-
int foo(void) {
19-
// FIXME: This diagnostic should be issued in C11 as well (though not in C99,
20-
// as this paper was a breaking change between C99 and C11).
21-
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}}
22-
return *p;
28+
union U { int a[10]; double d; };
29+
union U g(void);
30+
31+
void sink(int *);
32+
33+
int func_return(void) {
34+
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}}
35+
p = f().a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
36+
p = g().a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
37+
sink(f().a); // Ok
38+
return *f().a; // Ok
39+
}
40+
41+
int ternary(void) {
42+
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}}
43+
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}}
44+
p = (1 ? (struct X){ 0 } : f()).a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
45+
sink((1 ? (struct X){ 0 } : f()).a); // Ok
46+
47+
// This intentionally gets one diagnostic in C and two in C++. In C, the
48+
// compound literal results in an lvalue, not an rvalue as it does in C++. So
49+
// only one branch results in a temporary in C but both branches do in C++.
50+
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}} \
51+
cpp-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
52+
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}} \
53+
cpp-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
54+
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}} \
55+
cpp-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
56+
sink(1 ? (struct X){ 0 }.a : f().a); // Ok
57+
return *(1 ? (struct X){ 0 }.a : f().a); // Ok
2358
}
2459

60+
int comma(void) {
61+
struct X x;
62+
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}}
63+
p = ((void)0, x).a; // c-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
64+
sink(((void)0, x).a); // Ok
65+
return *(((void)0, x).a);// Ok
66+
}
67+
68+
int cast(void) {
69+
struct X x;
70+
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}}
71+
p = ((struct X)x).a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
72+
sink(((struct X)x).a); // Ok
73+
return *(((struct X)x).a); // Ok
74+
}
75+
76+
int assign(void) {
77+
struct X x, s;
78+
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}}
79+
p = (x = s).a; // c-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
80+
sink((x = s).a); // Ok
81+
return *((x = s).a); // Ok
82+
}

0 commit comments

Comments
 (0)