[clang] Forbid reinterpret_cast of function pointers in constexpr. (#150557)

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.

Fixes #150340 .
This commit is contained in:
Eli Friedman 2025-07-30 18:15:17 -07:00 committed by GitHub
parent d3f500f2d9
commit 0bbe1b30fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 58 additions and 15 deletions

View File

@ -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;

View File

@ -2682,6 +2682,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>();

View File

@ -735,6 +735,8 @@ def PtrPtrCast : Opcode {
}
def FnPtrCast : Opcode;
def DecayPtr : Opcode {
let Types = [PtrTypeClass, PtrTypeClass];
let HasGroup = 1;

View File

@ -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 &&

View File

@ -5,6 +5,8 @@
// RUN: %clang_cc1 -pedantic -std=c++14 -verify=ref,both %s
// RUN: %clang_cc1 -pedantic -std=c++20 -verify=ref,both %s
#define fold(x) (__builtin_constant_p(0) ? (x) : (x))
constexpr void doNothing() {}
constexpr int gimme5() {
doNothing();
@ -654,14 +656,26 @@ namespace {
}
namespace FunctionCast {
// When folding, we allow functions to be cast to different types. Such
// cast functions cannot be called, even if they're constexpr.
// When folding, we allow functions to be cast to different types. We only
// allow calls if the dynamic type of the pointer matches the type of the
// call.
constexpr int f() { return 1; }
constexpr void* f2() { return nullptr; }
constexpr int f3(int a) { return a; }
typedef double (*DoubleFn)();
typedef int (*IntFn)();
int a[(int)DoubleFn(f)()]; // both-error {{variable length array}} \
// both-warning {{are a Clang extension}}
int b[(int)IntFn(f)()]; // ok
typedef int* (*IntPtrFn)();
constexpr int test1 = (int)DoubleFn(f)(); // both-error {{constant expression}} both-note {{reinterpret_cast}}
// FIXME: We should print a note explaining the error.
constexpr int test2 = (int)fold(DoubleFn(f))(); // both-error {{constant expression}}
constexpr int test3 = (int)IntFn(f)(); // no-op cast
constexpr int test4 = fold(IntFn(DoubleFn(f)))();
constexpr int test5 = IntFn(fold(DoubleFn(f)))(); // both-error {{constant expression}} \
// both-note {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}}
// FIXME: Interpreter is less strict here.
constexpr int test6 = fold(IntPtrFn(f2))() == nullptr; // ref-error {{constant expression}}
// FIXME: The following crashes interpreter
// constexpr int test6 = fold(IntFn(f3)());
}
#if __cplusplus >= 202002L

View File

@ -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);

View File

@ -4,15 +4,16 @@
// 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 -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-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}}