Skip to content

Commit c945dc4

Browse files
committed
PR48587: is_constant_evaluated() should not evaluate to true during a
variable's destruction if it didn't do so during construction. The standard doesn't give any guidance as to what to do here, but this approach seems reasonable and conservative, and has been proposed to the standard committee.
1 parent 3e837e1 commit c945dc4

File tree

2 files changed

+106
-5
lines changed

2 files changed

+106
-5
lines changed

clang/lib/AST/ExprConstant.cpp

+14-5
Original file line numberDiff line numberDiff line change
@@ -14799,11 +14799,14 @@ bool Expr::EvaluateAsLValue(EvalResult &Result, const ASTContext &Ctx,
1479914799

1480014800
static bool EvaluateDestruction(const ASTContext &Ctx, APValue::LValueBase Base,
1480114801
APValue DestroyedValue, QualType Type,
14802-
SourceLocation Loc, Expr::EvalStatus &EStatus) {
14803-
EvalInfo Info(Ctx, EStatus, EvalInfo::EM_ConstantExpression);
14802+
SourceLocation Loc, Expr::EvalStatus &EStatus,
14803+
bool IsConstantDestruction) {
14804+
EvalInfo Info(Ctx, EStatus,
14805+
IsConstantDestruction ? EvalInfo::EM_ConstantExpression
14806+
: EvalInfo::EM_ConstantFold);
1480414807
Info.setEvaluatingDecl(Base, DestroyedValue,
1480514808
EvalInfo::EvaluatingDeclKind::Dtor);
14806-
Info.InConstantContext = true;
14809+
Info.InConstantContext = IsConstantDestruction;
1480714810

1480814811
LValue LVal;
1480914812
LVal.set(Base);
@@ -14857,7 +14860,8 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx,
1485714860
// If this is a class template argument, it's required to have constant
1485814861
// destruction too.
1485914862
if (Kind == ConstantExprKind::ClassTemplateArgument &&
14860-
(!EvaluateDestruction(Ctx, Base, Result.Val, T, getBeginLoc(), Result) ||
14863+
(!EvaluateDestruction(Ctx, Base, Result.Val, T, getBeginLoc(), Result,
14864+
true) ||
1486114865
Result.HasSideEffects)) {
1486214866
// FIXME: Prefix a note to indicate that the problem is lack of constant
1486314867
// destruction.
@@ -14923,6 +14927,10 @@ bool VarDecl::evaluateDestruction(
1492314927
Expr::EvalStatus EStatus;
1492414928
EStatus.Diag = &Notes;
1492514929

14930+
// Only treat the destruction as constant destruction if we formally have
14931+
// constant initialization (or are usable in a constant expression).
14932+
bool IsConstantDestruction = hasConstantInitialization();
14933+
1492614934
// Make a copy of the value for the destructor to mutate, if we know it.
1492714935
// Otherwise, treat the value as default-initialized; if the destructor works
1492814936
// anyway, then the destruction is constant (and must be essentially empty).
@@ -14933,7 +14941,8 @@ bool VarDecl::evaluateDestruction(
1493314941
return false;
1493414942

1493514943
if (!EvaluateDestruction(getASTContext(), this, std::move(DestroyedValue),
14936-
getType(), getLocation(), EStatus) ||
14944+
getType(), getLocation(), EStatus,
14945+
IsConstantDestruction) ||
1493714946
EStatus.HasSideEffects)
1493814947
return false;
1493914948

clang/test/CodeGenCXX/builtin-is-constant-evaluated.cpp

+92
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// RUN: FileCheck -check-prefix=CHECK-DYN -input-file=%t.ll %s
55
// RUN: FileCheck -check-prefix=CHECK-ARR -input-file=%t.ll %s
66
// RUN: FileCheck -check-prefix=CHECK-FOLD -input-file=%t.ll %s
7+
// RUN: FileCheck -check-prefix=CHECK-DTOR -input-file=%t.ll %s
78

89
using size_t = decltype(sizeof(int));
910

@@ -131,3 +132,94 @@ void test_ref_to_static_var() {
131132
// CHECK-FOLD: store i32* @_ZZ22test_ref_to_static_varvE10i_constant, i32** %r,
132133
int &r = __builtin_is_constant_evaluated() ? i_constant : i_non_constant;
133134
}
135+
136+
int not_constexpr;
137+
138+
// __builtin_is_constant_evaluated() should never evaluate to true during
139+
// destruction if it would not have done so during construction.
140+
//
141+
// FIXME: The standard doesn't say that it should ever return true when
142+
// evaluating a destructor call, even for a constexpr variable. That seems
143+
// obviously wrong.
144+
struct DestructorBCE {
145+
int n;
146+
constexpr DestructorBCE(int n) : n(n) {}
147+
constexpr ~DestructorBCE() {
148+
if (!__builtin_is_constant_evaluated())
149+
not_constexpr = 1;
150+
}
151+
};
152+
153+
// CHECK-DTOR-NOT: @_ZN13DestructorBCED{{.*}}@global_dtor_bce_1
154+
DestructorBCE global_dtor_bce_1(101);
155+
156+
// CHECK-DTOR: load i32, i32* @not_constexpr
157+
// CHECK-DTOR: call {{.*}} @_ZN13DestructorBCEC1Ei({{.*}} @global_dtor_bce_2, i32
158+
// CHECK-DTOR: atexit{{.*}} @_ZN13DestructorBCED{{.*}} @global_dtor_bce_2
159+
// CHECK-DTOR: }
160+
DestructorBCE global_dtor_bce_2(not_constexpr);
161+
162+
// CHECK-DTOR-NOT: @_ZN13DestructorBCED{{.*}}@global_dtor_bce_3
163+
constexpr DestructorBCE global_dtor_bce_3(103);
164+
165+
// CHECK-DTOR-LABEL: define {{.*}} @_Z15test_dtor_bce_1v(
166+
void test_dtor_bce_1() {
167+
// Variable is neither constant initialized (because it has automatic storage
168+
// duration) nor usable in constant expressions, so BCE should not return
169+
// true during destruction. It would be OK if we replaced the constructor
170+
// call with a direct store, but we should emit the destructor call.
171+
172+
// CHECK-DTOR: call {{.*}} @_ZN13DestructorBCEC1Ei({{.*}}, i32 201)
173+
DestructorBCE local(201);
174+
// CHECK-DTOR: call {{.*}} @_ZN13DestructorBCED
175+
// CHECK-DTOR: }
176+
}
177+
178+
// CHECK-DTOR-LABEL: define {{.*}} @_Z15test_dtor_bce_2v(
179+
void test_dtor_bce_2() {
180+
// Non-constant init => BCE is false in destructor.
181+
182+
// CHECK-DTOR: call {{.*}} @_ZN13DestructorBCEC1Ei({{.*}}
183+
DestructorBCE local(not_constexpr);
184+
// CHECK-DTOR: call {{.*}} @_ZN13DestructorBCED
185+
// CHECK-DTOR: }
186+
}
187+
188+
// CHECK-DTOR-LABEL: define {{.*}} @_Z15test_dtor_bce_3v(
189+
void test_dtor_bce_3() {
190+
// Should never call dtor for a constexpr variable.
191+
192+
// CHECK-DTOR-NOT: call {{.*}} @_ZN13DestructorBCEC1Ei(
193+
constexpr DestructorBCE local(203);
194+
// CHECK-DTOR-NOT: @_ZN13DestructorBCED
195+
// CHECK-DTOR: }
196+
}
197+
198+
// CHECK-DTOR-LABEL: define {{.*}} @_Z22test_dtor_bce_static_1v(
199+
void test_dtor_bce_static_1() {
200+
// Variable is constant initialized, so BCE returns true during constant
201+
// destruction.
202+
203+
// CHECK: store i32 301
204+
// CHECK-DTOR-NOT: @_ZN13DestructorBCEC1Ei({{.*}}
205+
static DestructorBCE local(301);
206+
// CHECK-DTOR-NOT: @_ZN13DestructorBCED
207+
// CHECK-DTOR: }
208+
}
209+
210+
// CHECK-DTOR-LABEL: define {{.*}} @_Z22test_dtor_bce_static_2v(
211+
void test_dtor_bce_static_2() {
212+
// CHECK-DTOR: call {{.*}} @_ZN13DestructorBCEC1Ei({{.*}}
213+
static DestructorBCE local(not_constexpr);
214+
// CHECK-DTOR: call {{.*}}atexit{{.*}} @_ZN13DestructorBCED
215+
// CHECK-DTOR: }
216+
}
217+
218+
// CHECK-DTOR-LABEL: define {{.*}} @_Z22test_dtor_bce_static_3v(
219+
void test_dtor_bce_static_3() {
220+
// CHECK: store i32 303
221+
// CHECK-DTOR-NOT: @_ZN13DestructorBCEC1Ei({{.*}}
222+
static constexpr DestructorBCE local(303);
223+
// CHECK-DTOR-NOT: @_ZN13DestructorBCED
224+
// CHECK-DTOR: }
225+
}

0 commit comments

Comments
 (0)