[clang] [clang] Forbid reinterpret_cast of function pointers in constexpr. (PR #150557)
Eli Friedman via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 25 12:56:18 PDT 2025
https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/150557
>From f111a98680ba2301ee93a45a3c30630c71925562 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at quicinc.com>
Date: Thu, 24 Jul 2025 17:22:27 -0700
Subject: [PATCH 1/3] [clang] Forbid reinterpret_cast of function pointers in
constexpr.
This has been explicitly forbidden since C++11, but somehow the edge
case of converting a function pointer to void* using a cast like
`(void*)f` wasn't handled.
---
clang/lib/AST/ExprConstant.cpp | 17 +++++++++++++----
clang/test/CXX/expr/expr.const/p2-0x.cpp | 5 +++++
clang/test/Sema/constexpr-void-cast.c | 6 ++++--
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9808298a1b1d0..993b64b2752e9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -9741,10 +9741,19 @@ bool PointerExprEvaluator::VisitCastExpr(const CastExpr *E) {
case CK_AddressSpaceConversion:
if (!Visit(SubExpr))
return false;
- // Bitcasts to cv void* are static_casts, not reinterpret_casts, so are
- // permitted in constant expressions in C++11. Bitcasts from cv void* are
- // also static_casts, but we disallow them as a resolution to DR1312.
- if (!E->getType()->isVoidPointerType()) {
+ if (E->getType()->isFunctionPointerType() ||
+ SubExpr->getType()->isFunctionPointerType()) {
+ // Casting between two function pointer types, or between a function
+ // pointer and an object pointer, is always a reinterpret_cast.
+ CCEDiag(E, diag::note_constexpr_invalid_cast)
+ << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret
+ << Info.Ctx.getLangOpts().CPlusPlus;
+ Result.Designator.setInvalid();
+ } else if (!E->getType()->isVoidPointerType()) {
+ // Bitcasts to cv void* are static_casts, not reinterpret_casts, so are
+ // permitted in constant expressions in C++11. Bitcasts from cv void* are
+ // also static_casts, but we disallow them as a resolution to DR1312.
+ //
// In some circumstances, we permit casting from void* to cv1 T*, when the
// actual pointee object is actually a cv2 T.
bool HasValidResult = !Result.InvalidBase && !Result.Designator.Invalid &&
diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp
index 910c8635f7353..8401d3033eda9 100644
--- a/clang/test/CXX/expr/expr.const/p2-0x.cpp
+++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp
@@ -438,6 +438,11 @@ namespace ReinterpretCast {
struct U {
int m : (long)(S*)6; // expected-warning {{constant expression}} expected-note {{reinterpret_cast}}
};
+ void f();
+ constexpr void* fp1 = (void*)f; // expected-error {{constant expression}} expected-note {{reinterpret_cast}}
+ constexpr int* fp2 = (int*)f; // expected-error {{constant expression}} expected-note {{reinterpret_cast}}
+ constexpr int (*fp3)() = (int(*)())f; // expected-error {{constant expression}} expected-note {{reinterpret_cast}}
+ constexpr int (&fp4)() = (int(&)())f; // expected-error {{constant expression}} expected-note {{reinterpret_cast}}
}
// - a pseudo-destructor call (5.2.4);
diff --git a/clang/test/Sema/constexpr-void-cast.c b/clang/test/Sema/constexpr-void-cast.c
index 2ffc59f509b4b..ffaed9a263ace 100644
--- a/clang/test/Sema/constexpr-void-cast.c
+++ b/clang/test/Sema/constexpr-void-cast.c
@@ -3,8 +3,8 @@
// RUN: %clang_cc1 -x c -fsyntax-only %s -verify=c -std=c11 -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -x c -fsyntax-only %s -pedantic -verify=c-pedantic -std=c11 -fexperimental-new-constant-interpreter
//
-// RUN: %clang_cc1 -x c++ -fsyntax-only %s -verify=cxx
-// RUN: %clang_cc1 -x c++ -fsyntax-only %s -pedantic -verify=cxx-pedantic
+// RUN: %clang_cc1 -x c++ -fsyntax-only %s -verify=cxx-nointerpreter
+// RUN: %clang_cc1 -x c++ -fsyntax-only %s -pedantic -verify=cxx-pedantic,cxx-nointerpreter
// RUN: %clang_cc1 -x c++ -fsyntax-only %s -verify=cxx -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -x c++ -fsyntax-only %s -pedantic -verify=cxx-pedantic -fexperimental-new-constant-interpreter
@@ -15,4 +15,6 @@ void f(void);
struct S {char c;} s;
_Static_assert(&s != (void *)&f, ""); // c-pedantic-warning {{not an integer constant expression}} \
// c-pedantic-note {{this conversion is not allowed in a constant expression}} \
+ // cxx-nointerpreter-error {{static assertion expression is not an integral constant expression}} \
+ // cxx-nointerpreter-note {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} \
// cxx-pedantic-warning {{'_Static_assert' is a C11 extension}}
>From 48f84b3ecbc356f4c8c224aafb0ef8597be9ab9d Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at quicinc.com>
Date: Fri, 25 Jul 2025 12:35:51 -0700
Subject: [PATCH 2/3] Add interpreter support.
---
clang/lib/AST/ByteCode/Compiler.cpp | 10 +++++++---
clang/lib/AST/ByteCode/Interp.h | 8 ++++++++
clang/lib/AST/ByteCode/Opcodes.td | 2 ++
clang/test/Sema/constexpr-void-cast.c | 11 +++++------
4 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 63ac536c2b445..69d75bcc7634a 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -457,13 +457,17 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) {
assert(isPtrType(*FromT));
assert(isPtrType(*ToT));
if (FromT == ToT) {
- if (CE->getType()->isVoidPointerType())
+ if (CE->getType()->isVoidPointerType() &&
+ !SubExprTy->isFunctionPointerType()) {
return this->delegate(SubExpr);
+ }
if (!this->visit(SubExpr))
return false;
- if (CE->getType()->isFunctionPointerType())
- return true;
+ if (CE->getType()->isFunctionPointerType() ||
+ SubExprTy->isFunctionPointerType()) {
+ return this->emitFnPtrCast(CE);
+ }
if (FromT == PT_Ptr)
return this->emitPtrPtrCast(SubExprTy->isVoidPointerType(), CE);
return true;
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 9012442943d59..5087425af0e33 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2688,6 +2688,14 @@ static inline bool CastFixedPointIntegral(InterpState &S, CodePtr OpPC) {
return true;
}
+static inline bool FnPtrCast(InterpState &S, CodePtr OpPC) {
+ const SourceInfo &E = S.Current->getSource(OpPC);
+ S.CCEDiag(E, diag::note_constexpr_invalid_cast)
+ << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret
+ << S.getLangOpts().CPlusPlus << S.Current->getRange(OpPC);
+ return true;
+}
+
static inline bool PtrPtrCast(InterpState &S, CodePtr OpPC, bool SrcIsVoidPtr) {
const auto &Ptr = S.Stk.peek<Pointer>();
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index abfed77750f87..54abf22e59393 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -735,6 +735,8 @@ def PtrPtrCast : Opcode {
}
+def FnPtrCast : Opcode;
+
def DecayPtr : Opcode {
let Types = [PtrTypeClass, PtrTypeClass];
let HasGroup = 1;
diff --git a/clang/test/Sema/constexpr-void-cast.c b/clang/test/Sema/constexpr-void-cast.c
index ffaed9a263ace..cac671e292f56 100644
--- a/clang/test/Sema/constexpr-void-cast.c
+++ b/clang/test/Sema/constexpr-void-cast.c
@@ -3,18 +3,17 @@
// RUN: %clang_cc1 -x c -fsyntax-only %s -verify=c -std=c11 -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -x c -fsyntax-only %s -pedantic -verify=c-pedantic -std=c11 -fexperimental-new-constant-interpreter
//
-// RUN: %clang_cc1 -x c++ -fsyntax-only %s -verify=cxx-nointerpreter
-// RUN: %clang_cc1 -x c++ -fsyntax-only %s -pedantic -verify=cxx-pedantic,cxx-nointerpreter
+// RUN: %clang_cc1 -x c++ -fsyntax-only %s -verify=cxx
+// RUN: %clang_cc1 -x c++ -fsyntax-only %s -pedantic -verify=cxx,cxx-pedantic
// RUN: %clang_cc1 -x c++ -fsyntax-only %s -verify=cxx -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -x c++ -fsyntax-only %s -pedantic -verify=cxx-pedantic -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -x c++ -fsyntax-only %s -pedantic -verify=cxx,cxx-pedantic -fexperimental-new-constant-interpreter
// c-no-diagnostics
-// cxx-no-diagnostics
void f(void);
struct S {char c;} s;
_Static_assert(&s != (void *)&f, ""); // c-pedantic-warning {{not an integer constant expression}} \
// c-pedantic-note {{this conversion is not allowed in a constant expression}} \
- // cxx-nointerpreter-error {{static assertion expression is not an integral constant expression}} \
- // cxx-nointerpreter-note {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} \
+ // cxx-error {{static assertion expression is not an integral constant expression}} \
+ // cxx-note {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} \
// cxx-pedantic-warning {{'_Static_assert' is a C11 extension}}
>From e1783e4cb09588f3ff888be73982b9ac74488d42 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at quicinc.com>
Date: Fri, 25 Jul 2025 12:56:00 -0700
Subject: [PATCH 3/3] Fix test.
---
clang/test/AST/ByteCode/functions.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/AST/ByteCode/functions.cpp b/clang/test/AST/ByteCode/functions.cpp
index b5e6f5bd7ece2..7545762d39a8d 100644
--- a/clang/test/AST/ByteCode/functions.cpp
+++ b/clang/test/AST/ByteCode/functions.cpp
@@ -660,6 +660,7 @@ namespace FunctionCast {
typedef double (*DoubleFn)();
typedef int (*IntFn)();
int a[(int)DoubleFn(f)()]; // both-error {{variable length array}} \
+ // expected-note {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} \
// both-warning {{are a Clang extension}}
int b[(int)IntFn(f)()]; // ok
}
More information about the cfe-commits
mailing list