https://github.com/eleviant updated https://github.com/llvm/llvm-project/pull/197005
>From a57015a0753e896414993a2b0edebbc72bbcec8d Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 11 May 2026 19:39:23 +0200 Subject: [PATCH 01/13] [clang] Allow C-style casts in constexpr in MS compatible mode Patch allows folding constant expression if -fms-compatibility is given and the only problem found by evaluator is C-style cast. This makes it more permissive than MSVC, which treats this expression as constant: ``` (FIELD_OFFSET(S,y) + 3) % 5 ``` but doesn't do the same for this one: ``` (FIELD_OFFSET(S,y) + 3) ``` where FIELD_OFFSET is defined as: ``` ``` --- clang/include/clang/AST/ASTContext.h | 3 + clang/lib/AST/ASTContext.cpp | 6 + clang/lib/AST/Decl.cpp | 8 +- clang/lib/Sema/SemaExpr.cpp | 2 + clang/lib/Sema/SemaOverload.cpp | 3 +- clang/test/SemaCXX/microsoft-constexpr.cpp | 142 +++++++++++++++++++++ 6 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index c952b8d46974c..3f9d57284752a 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,6 +3884,9 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); + bool + shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes); + private: /// All OMPTraitInfo objects live in this collection, one per /// `pragma omp [begin] declare variant` directive. diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index a401a7471e6fc..8cf00713c2a72 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15633,3 +15633,9 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { if (FieldDecl *FD = Comp.getField(); isPFPField(FD)) PFPFieldsWithEvaluatedOffset.insert(FD); } + +bool ASTContext::shouldIgnoreNotesForConstEval( + SmallVectorImpl<PartialDiagnosticAt> &Notes) { + return getLangOpts().MSVCCompat && Notes.size() == 1 && + Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; +} diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index b797ebfa1a7e1..d0876834c913b 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2587,8 +2587,12 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, if (IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && - !Notes.empty()) - Result = false; + !Notes.empty()) { + if (!Ctx.shouldIgnoreNotesForConstEval(Notes)) + Result = false; + else + Notes.clear(); + } // Ensure the computed APValue is cleaned up later if evaluation succeeded, // or that it's empty (so that there's nothing to clean up) if evaluation diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index ad6e7183cb3a4..0769bc787e886 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18030,6 +18030,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then // this is a constant expression. + if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) + Notes.clear(); if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) { if (Result) *Result = EvalResult.Val.getInt(); diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index e11bbd7085798..768bd294d7b69 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6710,7 +6710,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Result = ExprError(); } else { Value = Eval.Val; - + if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) + Notes.clear(); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr.cpp b/clang/test/SemaCXX/microsoft-constexpr.cpp new file mode 100644 index 0000000000000..fb0a849e5ce7b --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr.cpp @@ -0,0 +1,142 @@ +// Some of this should fail in MSVC, but work in clang +// when -fms-compatibility is enabled. +// RUN: %clang -fsyntax-only -fms-compatibility -std=c++20 %s + +typedef long LONG; +typedef __int64 LONG_PTR, *PLONG_PTR; + +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +constexpr bool cb_eq = FIELD_OFFSET(S, y) == 4; +constexpr bool cb_ne = FIELD_OFFSET(S, y) != 0; +constexpr bool cb_lt = FIELD_OFFSET(S, y) < 8; +constexpr bool cb_le = FIELD_OFFSET(S, y) <= 4; +constexpr bool cb_gt = FIELD_OFFSET(S, y) > 0; +constexpr bool cb_ge = FIELD_OFFSET(S, y) >= 4; +constexpr bool cb_bool = FIELD_OFFSET(S, y); + +static_assert(FIELD_OFFSET(S, y) == 4); +static_assert(FIELD_OFFSET(S, y) != 0); +static_assert(FIELD_OFFSET(S, y) < 8); +static_assert(FIELD_OFFSET(S, y) <= 4); +static_assert(FIELD_OFFSET(S, y) > 0); +static_assert(FIELD_OFFSET(S, y) >= 4); +static_assert(FIELD_OFFSET(S, y)); + + +enum E { + enum_offset_y = FIELD_OFFSET(S, y), + enum_cmp_y = FIELD_OFFSET(S, y) == 4 +}; + +int arr_bound[FIELD_OFFSET(S, y)]; +int arr_bound_cmp[FIELD_OFFSET(S, y) == 4 ? 1 : -1]; + +struct BitField { + int bf1 : FIELD_OFFSET(S, y); + int bf2 : FIELD_OFFSET(S, y) == 4; +}; + +template<int N> +struct TplInt {}; + +template<bool B> +struct TplBool {}; + +TplInt<FIELD_OFFSET(S, y)> tpl_int; +TplBool<FIELD_OFFSET(S, y) == 4> tpl_bool; +TplBool<FIELD_OFFSET(S, y)> tpl_bool_conv; + +void f() noexcept(FIELD_OFFSET(S, y) == 4) {} + +template<class T> +void g() { + if constexpr (FIELD_OFFSET(S, y) == 4) { + } else { + } +} + +struct ExplicitCtor { + explicit(FIELD_OFFSET(S, y) == 4) ExplicitCtor(int) {} +}; + +alignas(FIELD_OFFSET(S,y)) int __g; + +constinit int constinit_offset = FIELD_OFFSET(S, y); +constinit bool constinit_bool = FIELD_OFFSET(S, y) == 4; + +constexpr int constexpr_offset = FIELD_OFFSET(S, y); +constexpr int constexpr_cmp_as_int = FIELD_OFFSET(S, y) == 4; +constexpr bool constexpr_bool = FIELD_OFFSET(S, y) == 4; + +int switch_test(int v) { + switch (v) { + case FIELD_OFFSET(S, y): + return 1; + case FIELD_OFFSET(S, x): + return 2; + default: + return 0; + } +} + +template<int N = FIELD_OFFSET(S, y)> +struct DefaultTplInt {}; + +template<bool B = FIELD_OFFSET(S, y) == 4> +struct DefaultTplBool {}; + +DefaultTplInt<> default_tpl_int; +DefaultTplBool<> default_tpl_bool; + +struct ArrayMember { + int a[FIELD_OFFSET(S, y)]; +}; + +union U { + char c; + int a[FIELD_OFFSET(S, y)]; +}; + +typedef char typedef_arr[FIELD_OFFSET(S, y)]; +using using_arr = char[FIELD_OFFSET(S, y)]; + +constexpr int ternary_offset = + FIELD_OFFSET(S, y) == 4 ? FIELD_OFFSET(S, y) : -1; + +constexpr bool logical_and = + FIELD_OFFSET(S, y) == 4 && FIELD_OFFSET(S, x) == 0; + +constexpr bool logical_or = + FIELD_OFFSET(S, y) == 4 || FIELD_OFFSET(S, x) == 123; + +constexpr bool logical_not = + !FIELD_OFFSET(S, x); + +constexpr int arithmetic_add = FIELD_OFFSET(S, y) + 1; +constexpr int arithmetic_sub = FIELD_OFFSET(S, y) - 1; +constexpr int arithmetic_mul = FIELD_OFFSET(S, y) * 2; +constexpr int arithmetic_div = FIELD_OFFSET(S, y) / 2; +constexpr int arithmetic_mod = FIELD_OFFSET(S, y) % 3; + +constexpr int bit_or = FIELD_OFFSET(S, y) | 1; +constexpr int bit_and = FIELD_OFFSET(S, y) & 7; +constexpr int bit_xor = FIELD_OFFSET(S, y) ^ 1; +constexpr int bit_shl = FIELD_OFFSET(S, y) << 1; +constexpr int bit_shr = FIELD_OFFSET(S, y) >> 1; + +constexpr int comma_expr = (0, FIELD_OFFSET(S, y)); + +constexpr int cast_int = (int)FIELD_OFFSET(S, y); +constexpr long cast_long = (long)FIELD_OFFSET(S, y); +constexpr bool cast_bool = (bool)FIELD_OFFSET(S, y); + +template<class T, int N> +struct DependentTpl {}; + +DependentTpl<S, FIELD_OFFSET(S, y)> dependent_tpl; >From 875536c0e1d81260c7fc83d2cb48a6f5ada26a47 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 16:52:07 +0200 Subject: [PATCH 02/13] Add SFINAE handling and test cases We don't want relaxed constant folding to happen during template parameter subsititution, to avoid unexpected instantiations. --- clang/lib/Sema/SemaExpr.cpp | 8 ++++++-- clang/lib/Sema/SemaOverload.cpp | 5 ++++- .../SemaCXX/microsoft-constexpr-SFINAE.cpp | 20 +++++++++++++++++++ .../SemaCXX/microsoft-constexpr-SFINAE2.cpp | 20 +++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 0769bc787e886..342ab881f0282 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18027,11 +18027,15 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, if (!isa<ConstantExpr>(E)) E = ConstantExpr::Create(Context, E, EvalResult.Val); + // For -fms-compatibility mode we relax some requirements + // for constant folding in non-SFINAE contexts + if (!isSFINAEContext() && + getASTContext().shouldIgnoreNotesForConstEval(Notes)) + Notes.clear(); + // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then // this is a constant expression. - if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) - Notes.clear(); if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) { if (Result) *Result = EvalResult.Val.getInt(); diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 768bd294d7b69..e71eebfd8f40e 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6710,7 +6710,10 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Result = ExprError(); } else { Value = Eval.Val; - if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) + // For -fms-compatibility mode we relax some requirements + // for constant folding in non-SFINAE contexts + if (!isSFINAEContext() && + getASTContext().shouldIgnoreNotesForConstEval(Notes)) Notes.clear(); if (Notes.empty()) { // It's a constant expression. diff --git a/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp new file mode 100644 index 0000000000000..271d1bcfad99e --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility -triple x86_64-windows-msvc %s + +typedef long long LONG_PTR; +typedef long LONG; +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +template<class T, LONG_PTR = FIELD_OFFSET(S, y)> +char probe(int); + +template<class> +long probe(...); + +static_assert(sizeof(probe<int>(0)) == sizeof(char), ""); +// expected-error@-1 {{static assertion failed due to requirement 'sizeof (probe<int>(0)) == sizeof(char)'}} +// expected-note@-2 {{expression evaluates to '4 == 1'}} diff --git a/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp b/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp new file mode 100644 index 0000000000000..b63ea73eaab28 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -fms-compatibility -triple x86_64-windows-msvc -verify %s + +typedef long long LONG_PTR; +typedef long LONG; +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +template<class T, bool = __builtin_choose_expr(FIELD_OFFSET(T, y) > 0, true, false)> +char probe(int); + +template<class> +long probe(...); + +static_assert(sizeof(probe<S>(0)) == sizeof(char), ""); +// expected-error@-1 {{static assertion failed due to requirement 'sizeof (probe<S>(0)) == sizeof(char)'}} +// expected-note@-2 {{expression evaluates to '4 == 1'}} >From 2337ddc90e5ee019c21c540b562ec281cdd49bb6 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 19:22:43 +0200 Subject: [PATCH 03/13] Add opt-in warning for MS relaxed constant folding --- clang/include/clang/AST/ASTContext.h | 3 +-- clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ++++ clang/include/clang/Basic/DiagnosticGroups.td | 2 ++ clang/lib/AST/ASTContext.cpp | 11 ++++++++--- clang/lib/AST/Decl.cpp | 4 +--- clang/lib/Sema/SemaExpr.cpp | 5 ++--- clang/lib/Sema/SemaOverload.cpp | 5 ++--- clang/test/SemaCXX/microsoft-constexpr2.cpp | 12 ++++++++++++ 8 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr2.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 3f9d57284752a..0387cef5b6a4d 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,8 +3884,7 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool - shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool maybeFoldMSConstexpr(SmallVectorImpl<PartialDiagnosticAt> &Notes); private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index bde418695f647..b7252902c969e 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1030,6 +1030,10 @@ def warn_npot_ms_struct : Warning< "ms_struct may not produce Microsoft-compatible layouts with fundamental " "data types with sizes that aren't a power of two">, DefaultError, InGroup<IncompatibleMSStruct>; +def warn_relaxed_constant_fold : Warning< + "folding this constant expression is a Microsoft extension">, + InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore; + def err_itanium_layout_unimplemented : Error< "Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 8031f99419bdc..fe2eadd7a3aa4 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1642,6 +1642,8 @@ def MicrosoftStringLiteralFromPredefined : DiagGroup< "microsoft-string-literal-from-predefined">; def MicrosoftInlineOnNonFunction : DiagGroup< "microsoft-inline-on-non-function">; +def MicrosoftRelaxedConstantFold : + DiagGroup<"relaxed-constant-fold">; // Aliases. def : DiagGroup<"msvc-include", [MicrosoftInclude]>; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 8cf00713c2a72..956e000a927c9 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15634,8 +15634,13 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { PFPFieldsWithEvaluatedOffset.insert(FD); } -bool ASTContext::shouldIgnoreNotesForConstEval( +bool ASTContext::maybeFoldMSConstexpr( SmallVectorImpl<PartialDiagnosticAt> &Notes) { - return getLangOpts().MSVCCompat && Notes.size() == 1 && - Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; + bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 && + Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; + if (Fold) { + getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold); + Notes.clear(); + } + return Fold; } diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index d0876834c913b..987167342e49d 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2588,10 +2588,8 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.shouldIgnoreNotesForConstEval(Notes)) + if (!Ctx.maybeFoldMSConstexpr(Notes)) Result = false; - else - Notes.clear(); } // Ensure the computed APValue is cleaned up later if evaluation succeeded, diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 342ab881f0282..f39a9e56e6535 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18029,9 +18029,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext() && - getASTContext().shouldIgnoreNotesForConstEval(Notes)) - Notes.clear(); + if (!isSFINAEContext()) + getASTContext().maybeFoldMSConstexpr(Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index e71eebfd8f40e..cf1dbea6c2379 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6712,9 +6712,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Value = Eval.Val; // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext() && - getASTContext().shouldIgnoreNotesForConstEval(Notes)) - Notes.clear(); + if (!isSFINAEContext()) + getASTContext().maybeFoldMSConstexpr(Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp new file mode 100644 index 0000000000000..25d4b3ed2b3f3 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility -Wrelaxed-constant-fold %s + +typedef long long LONG_PTR; +typedef long LONG; +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this constant expression is a Microsoft extension}} >From a524d313d5ce09e7c67cc8b6237a3e82f86b712c Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:00:59 +0200 Subject: [PATCH 04/13] Change definition from Warning to Extension --- clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index b7252902c969e..192eb85b019cf 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1030,9 +1030,9 @@ def warn_npot_ms_struct : Warning< "ms_struct may not produce Microsoft-compatible layouts with fundamental " "data types with sizes that aren't a power of two">, DefaultError, InGroup<IncompatibleMSStruct>; -def warn_relaxed_constant_fold : Warning< +def warn_relaxed_constant_fold : Extension< "folding this constant expression is a Microsoft extension">, - InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore; + InGroup<MicrosoftRelaxedConstantFold>; def err_itanium_layout_unimplemented : Error< >From 03359733c30849c248787df0ae3e051c153a243e Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:07:02 +0200 Subject: [PATCH 05/13] Don't allow folding constant expressions using dynamic_cast This can be done with -std=c++20, so we don't need to this in Microsoft compatibility mode. --- clang/include/clang/Basic/PartialDiagnostic.h | 8 ++++++++ clang/lib/AST/ASTContext.cpp | 7 +++++-- clang/test/SemaCXX/microsoft-constexpr3.cpp | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr3.cpp diff --git a/clang/include/clang/Basic/PartialDiagnostic.h b/clang/include/clang/Basic/PartialDiagnostic.h index 4bf6049d08fdb..7658bfa3795f4 100644 --- a/clang/include/clang/Basic/PartialDiagnostic.h +++ b/clang/include/clang/Basic/PartialDiagnostic.h @@ -189,6 +189,14 @@ class PartialDiagnostic : public StreamingDiagnostic { == DiagnosticsEngine::ak_std_string && "Not a string arg"); return DiagStorage->DiagArgumentsStr[I]; } + uint64_t getValueArg(unsigned I) { + assert(DiagStorage && "No diagnostic storage?"); + assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args"); + assert(DiagStorage->DiagArgumentsKind[I] != + DiagnosticsEngine::ak_std_string && + "Not an integer arg"); + return DiagStorage->DiagArgumentsVal[I]; + } }; inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB, diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 956e000a927c9..15099a3814e5c 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15636,8 +15636,11 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { bool ASTContext::maybeFoldMSConstexpr( SmallVectorImpl<PartialDiagnosticAt> &Notes) { - bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 && - Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; + bool Fold = + getLangOpts().MSVCCompat && Notes.size() == 1 && + Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast && + Notes[0].second.getValueArg(0) != diag::ConstexprInvalidCastKind::Dynamic; + if (Fold) { getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold); Notes.clear(); diff --git a/clang/test/SemaCXX/microsoft-constexpr3.cpp b/clang/test/SemaCXX/microsoft-constexpr3.cpp new file mode 100644 index 0000000000000..dd52bb8f81d32 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr3.cpp @@ -0,0 +1,18 @@ +// Ignore dynamic_cast when relaxing constant expression with -fms-compatibility +// However using dynamic_cast is still possible in c++20 and higher +// RUN: not %clang_cc1 -std=c++11 -fms-compatibility -fsyntax-only %s +// RUN: %clang_cc1 -std=c++20 -fms-compatibility -fsyntax-only %s + +struct B { + virtual ~B() {} +}; + +struct D : B { + int x = 123; +}; + +#define IsD(x) (dynamic_cast<const D*>(x) != 0) + +static const D od; + +constexpr bool is_d = IsD(&od); >From 2263d9f615579c9f3638e8f91c6b9ab5e127c51c Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 20:02:28 +0200 Subject: [PATCH 06/13] Improved diagnostics --- .../include/clang/Basic/DiagnosticASTKinds.td | 5 ++-- clang/include/clang/Basic/PartialDiagnostic.h | 2 +- clang/lib/AST/ASTContext.cpp | 30 +++++++++++++------ clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 ++- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 192eb85b019cf..7f0f0a503a8ba 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1031,10 +1031,11 @@ def warn_npot_ms_struct : Warning< "data types with sizes that aren't a power of two">, DefaultError, InGroup<IncompatibleMSStruct>; def warn_relaxed_constant_fold : Extension< - "folding this constant expression is a Microsoft extension">, + "folding constant expression involving " + "%select{reinterpret_cast|cast that performs the conversions of a reinterpret_cast}0" + " is a Microsoft extension">, InGroup<MicrosoftRelaxedConstantFold>; - def err_itanium_layout_unimplemented : Error< "Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">; diff --git a/clang/include/clang/Basic/PartialDiagnostic.h b/clang/include/clang/Basic/PartialDiagnostic.h index 7658bfa3795f4..7469e45f7d888 100644 --- a/clang/include/clang/Basic/PartialDiagnostic.h +++ b/clang/include/clang/Basic/PartialDiagnostic.h @@ -194,7 +194,7 @@ class PartialDiagnostic : public StreamingDiagnostic { assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args"); assert(DiagStorage->DiagArgumentsKind[I] != DiagnosticsEngine::ak_std_string && - "Not an integer arg"); + "Not a value arg"); return DiagStorage->DiagArgumentsVal[I]; } }; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 15099a3814e5c..38f292987722f 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15636,14 +15636,26 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { bool ASTContext::maybeFoldMSConstexpr( SmallVectorImpl<PartialDiagnosticAt> &Notes) { - bool Fold = - getLangOpts().MSVCCompat && Notes.size() == 1 && - Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast && - Notes[0].second.getValueArg(0) != diag::ConstexprInvalidCastKind::Dynamic; - - if (Fold) { - getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold); - Notes.clear(); + if (Notes.size() != 1 || !getLangOpts().MSVCCompat) + return false; + auto &PD = Notes[0].second; + if (PD.getDiagID() != diag::note_constexpr_invalid_cast) + return false; + unsigned CastID; + switch (PD.getValueArg(0)) { + case diag::ConstexprInvalidCastKind::Reinterpret: + CastID = 0; + break; + case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret: + if (!PD.getValueArg(1)) + return false; + CastID = 1; + break; + default: + return false; } - return Fold; + getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold) + << CastID; + Notes.clear(); + return true; } diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 25d4b3ed2b3f3..e756a0240d5fe 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -3,10 +3,12 @@ typedef long long LONG_PTR; typedef long LONG; #define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) +#define FIELD_OFFSET2(type, field) (reinterpret_cast<LONG_PTR>(&(((type *)0)->field))) struct S { int x; int y; }; -constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this constant expression is a Microsoft extension}} +constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} +constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}} >From 8b83a3dedfb359471ffeb1589c1c327d3ea79fe9 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 18 May 2026 13:50:18 +0200 Subject: [PATCH 07/13] Add note to distinguish ptr to int conversions --- .../include/clang/Basic/DiagnosticASTKinds.td | 4 ++++ clang/lib/AST/ASTContext.cpp | 18 +++--------------- clang/lib/AST/ExprConstant.cpp | 4 ++-- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 7f0f0a503a8ba..ce864bdb1fe8a 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -16,6 +16,10 @@ def note_constexpr_invalid_cast : Note< "of a reinterpret_cast}1}|%CastFrom{cast from %1}}0" " is not allowed in a constant expression" "%select{| in C++ standards before C++20||}0">; +def note_constexpr_invalid_cast_ptrtoint : Note< + "%select{reinterpret_cast||" + "%select{this conversion|cast that performs the conversions of a reinterpret_cast}1|" + "}0 is not allowed in a constant expression">; def note_constexpr_invalid_void_star_cast : Note< "cast from %0 is not allowed in a constant expression " "%select{in C++ standards before C++2c|because the pointed object " diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 38f292987722f..55effa4e2397e 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15639,23 +15639,11 @@ bool ASTContext::maybeFoldMSConstexpr( if (Notes.size() != 1 || !getLangOpts().MSVCCompat) return false; auto &PD = Notes[0].second; - if (PD.getDiagID() != diag::note_constexpr_invalid_cast) + if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) return false; - unsigned CastID; - switch (PD.getValueArg(0)) { - case diag::ConstexprInvalidCastKind::Reinterpret: - CastID = 0; - break; - case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret: - if (!PD.getValueArg(1)) - return false; - CastID = 1; - break; - default: - return false; - } getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold) - << CastID; + << !!PD.getValueArg(0); Notes.clear(); + return true; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index b1cea63a8ede6..75d268df7bf10 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8533,7 +8533,7 @@ class ExprEvaluatorBase } bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E) { - CCEDiag(E, diag::note_constexpr_invalid_cast) + CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint) << diag::ConstexprInvalidCastKind::Reinterpret; return static_cast<Derived*>(this)->VisitCastExpr(E); } @@ -19484,7 +19484,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { } case CK_PointerToIntegral: { - CCEDiag(E, diag::note_constexpr_invalid_cast) + CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint) << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret << Info.Ctx.getLangOpts().CPlusPlus << E->getSourceRange(); >From 361c530c42e5db9a453868541d7e6549243be230 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 19 May 2026 14:49:52 +0200 Subject: [PATCH 08/13] Don't allow LValue casts for constexpr --- clang/include/clang/AST/ASTContext.h | 3 ++- clang/lib/AST/ASTContext.cpp | 4 ++-- clang/lib/AST/Decl.cpp | 2 +- clang/lib/Sema/SemaExpr.cpp | 2 +- clang/lib/Sema/SemaOverload.cpp | 2 +- clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 +++- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 0387cef5b6a4d..8a1f55350b0ca 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,7 +3884,8 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool maybeFoldMSConstexpr(SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool maybeFoldMSConstexpr(APValue &Val, + SmallVectorImpl<PartialDiagnosticAt> &Notes); private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 55effa4e2397e..01a14f0ac2cb9 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15635,8 +15635,8 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { } bool ASTContext::maybeFoldMSConstexpr( - SmallVectorImpl<PartialDiagnosticAt> &Notes) { - if (Notes.size() != 1 || !getLangOpts().MSVCCompat) + APValue &Val, SmallVectorImpl<PartialDiagnosticAt> &Notes) { + if (Notes.size() != 1 || !getLangOpts().MSVCCompat || Val.isLValue()) return false; auto &PD = Notes[0].second; if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 987167342e49d..c96982d83c1d7 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2588,7 +2588,7 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.maybeFoldMSConstexpr(Notes)) + if (!Ctx.maybeFoldMSConstexpr(Eval->Evaluated, Notes)) Result = false; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index f39a9e56e6535..b114d30977600 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18030,7 +18030,7 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(Notes); + getASTContext().maybeFoldMSConstexpr(EvalResult.Val, Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index cf1dbea6c2379..575add96a51ea 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6713,7 +6713,7 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(Notes); + getASTContext().maybeFoldMSConstexpr(Value, Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index e756a0240d5fe..ad4faddc83b1d 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -8,7 +8,9 @@ typedef long LONG; struct S { int x; int y; -}; +} ob; constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}} +constexpr LONG_PTR b3 = (LONG_PTR)&ob; // expected-error {{constexpr variable 'b3' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} >From a5c2ebb979f9191fc09166d422b3933db3955546 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 25 May 2026 11:46:54 +0200 Subject: [PATCH 09/13] Rename a function The name MSConstexpr is already taken by class handling [[msvc::constexpr]] attribute --- clang/include/clang/AST/ASTContext.h | 4 ++-- clang/lib/AST/ASTContext.cpp | 2 +- clang/lib/AST/Decl.cpp | 2 +- clang/lib/Sema/SemaExpr.cpp | 2 +- clang/lib/Sema/SemaOverload.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 8a1f55350b0ca..71a167103d991 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,8 +3884,8 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool maybeFoldMSConstexpr(APValue &Val, - SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool maybeFoldConstexprWithCast(APValue &Val, + SmallVectorImpl<PartialDiagnosticAt> &Notes); private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 01a14f0ac2cb9..3eea213e0a2d5 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15634,7 +15634,7 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { PFPFieldsWithEvaluatedOffset.insert(FD); } -bool ASTContext::maybeFoldMSConstexpr( +bool ASTContext::maybeFoldConstexprWithCast( APValue &Val, SmallVectorImpl<PartialDiagnosticAt> &Notes) { if (Notes.size() != 1 || !getLangOpts().MSVCCompat || Val.isLValue()) return false; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index c96982d83c1d7..ad33b1d30fc07 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2588,7 +2588,7 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.maybeFoldMSConstexpr(Eval->Evaluated, Notes)) + if (!Ctx.maybeFoldConstexprWithCast(Eval->Evaluated, Notes)) Result = false; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index b114d30977600..7cb17fb9bf5b4 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18030,7 +18030,7 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(EvalResult.Val, Notes); + getASTContext().maybeFoldConstexprWithCast(EvalResult.Val, Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 575add96a51ea..bcb9b427ee123 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6713,7 +6713,7 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(Value, Notes); + getASTContext().maybeFoldConstexprWithCast(Value, Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); >From b369e5c18540c6c2c3ca5949e529489534da64c7 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 25 May 2026 16:03:26 +0200 Subject: [PATCH 10/13] Fix bugs in evaluator - Correctly emit note, when handling reinterpret_cast - Don't try to attempt folding constexpr having reinterpret_cast, if evaluator has failed (APValue is None). --- clang/lib/AST/Decl.cpp | 2 +- clang/lib/AST/ExprConstant.cpp | 4 +++- clang/test/SemaCXX/microsoft-constexpr2.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index ad33b1d30fc07..65b1cac985f6f 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2584,7 +2584,7 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, // a constant initializer if we produced notes. In that case, we can't keep // the result, because it may only be correct under the assumption that the // initializer is a constant context. - if (IsConstantInitialization && + if (Result && IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 75d268df7bf10..5326a8b3d4880 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8533,7 +8533,9 @@ class ExprEvaluatorBase } bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E) { - CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint) + bool IsPtrToInt = E->getCastKind() == CK_PointerToIntegral; + CCEDiag(E, IsPtrToInt ? diag::note_constexpr_invalid_cast_ptrtoint + : diag::note_constexpr_invalid_cast) << diag::ConstexprInvalidCastKind::Reinterpret; return static_cast<Derived*>(this)->VisitCastExpr(E); } diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index ad4faddc83b1d..c28e0f3d17574 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -14,3 +14,9 @@ constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant ex constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}} constexpr LONG_PTR b3 = (LONG_PTR)&ob; // expected-error {{constexpr variable 'b3' must be initialized by a constant expression}} // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} +constexpr int* b4 = reinterpret_cast<int*>(&ob); // expected-error {{constexpr variable 'b4' must be initialized by a constant expression}} + // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b5 = (42 - FIELD_OFFSET(S, y)) + // expected-error {{constexpr variable 'b5' must be initialized by a constant expression}} + (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} + // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} >From 78cbaa116620ba07ecb798adfe8f14f8ddab7a56 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 26 May 2026 15:20:17 +0200 Subject: [PATCH 11/13] Don't allow constexprs having a cast to contain an l-value --- clang/include/clang/AST/ASTContext.h | 4 ++-- clang/include/clang/AST/Expr.h | 4 ++++ clang/lib/AST/ASTContext.cpp | 4 ++-- clang/lib/AST/Decl.cpp | 5 ++--- clang/lib/AST/ExprConstant.cpp | 10 +++++++--- clang/lib/Sema/SemaExpr.cpp | 4 ++-- clang/lib/Sema/SemaOverload.cpp | 4 ++-- clang/test/SemaCXX/microsoft-constexpr2.cpp | 2 ++ 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 71a167103d991..884ec336090fc 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,8 +3884,8 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool maybeFoldConstexprWithCast(APValue &Val, - SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool + maybeFoldConstexprWithCast(SmallVectorImpl<PartialDiagnosticAt> &Notes) const; private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index b91bf4a5375fb..d786a8964b7f8 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -619,6 +619,10 @@ class Expr : public ValueStmt { /// Likewise, INT_MAX + 1 can be folded to INT_MIN, but has UB. bool HasUndefinedBehavior = false; + /// Whether part of expression is an LValue. + /// Used when evaluating constant expression with Microsoft extensions. + bool HasLValue = false; + /// Diag - If this is non-null, it will be filled in with a stack of notes /// indicating why evaluation failed (or why it failed to produce a constant /// expression). diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 3eea213e0a2d5..dc0fdea473645 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15635,8 +15635,8 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { } bool ASTContext::maybeFoldConstexprWithCast( - APValue &Val, SmallVectorImpl<PartialDiagnosticAt> &Notes) { - if (Notes.size() != 1 || !getLangOpts().MSVCCompat || Val.isLValue()) + SmallVectorImpl<PartialDiagnosticAt> &Notes) const { + if (Notes.size() != 1 || !getLangOpts().MSVCCompat) return false; auto &PD = Notes[0].second; if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 65b1cac985f6f..d880924e2d6ab 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2584,12 +2584,11 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, // a constant initializer if we produced notes. In that case, we can't keep // the result, because it may only be correct under the assumption that the // initializer is a constant context. - if (Result && IsConstantInitialization && + if (IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.maybeFoldConstexprWithCast(Eval->Evaluated, Notes)) - Result = false; + Result = false; } // Ensure the computed APValue is cleaned up later if evaluation succeeded, diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 5326a8b3d4880..0f8e2c6b45266 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -19495,6 +19495,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { return false; if (LV.getLValueBase()) { + Info.EvalStatus.HasLValue = true; // Only allow based lvalue casts if they are lossless. // FIXME: Allow a larger integer size than the pointer size, and allow // narrowing back down to pointer width in subsequent integral casts. @@ -21543,9 +21544,12 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, llvm_unreachable("Unhandled cleanup; missing full expression marker?"); } - return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, - ConstantExprKind::Normal) && - CheckMemoryLeaks(Info); + bool Checked = CheckConstantExpression(Info, DeclLoc, DeclTy, Value, + ConstantExprKind::Normal) && + CheckMemoryLeaks(Info); + if (Checked && !Info.EvalStatus.HasLValue) + Ctx.maybeFoldConstexprWithCast(Notes); + return Checked; } bool VarDecl::evaluateDestruction( diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 7cb17fb9bf5b4..35951d66c58d2 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18029,8 +18029,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext()) - getASTContext().maybeFoldConstexprWithCast(EvalResult.Val, Notes); + if (!isSFINAEContext() && !EvalResult.HasLValue) + getASTContext().maybeFoldConstexprWithCast(Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index bcb9b427ee123..f4bf26ee85fb6 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6712,8 +6712,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Value = Eval.Val; // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext()) - getASTContext().maybeFoldConstexprWithCast(Value, Notes); + if (!isSFINAEContext() && !Eval.HasLValue) + getASTContext().maybeFoldConstexprWithCast(Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index c28e0f3d17574..6aff24aa36a44 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -20,3 +20,5 @@ constexpr LONG_PTR b5 = (42 - FIELD_OFFSET(S, y)) + // expected-error {{co (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} +constexpr long b7[2] = { FIELD_OFFSET(S, y), (long)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} >From c7a98611a1d88464ccbdfd51b0caf733332aeb0e Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 26 May 2026 21:35:51 +0200 Subject: [PATCH 12/13] Fix failing test on Windows --- clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 6aff24aa36a44..fd83b08ce9a3d 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -20,5 +20,5 @@ constexpr LONG_PTR b5 = (42 - FIELD_OFFSET(S, y)) + // expected-error {{co (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} -constexpr long b7[2] = { FIELD_OFFSET(S, y), (long)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} - // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b7[2] = { FIELD_OFFSET(S, y), (LONG_PTR)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} >From 835fede090c43ad58077cdc5197ea9b87f4e7992 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 1 Jun 2026 14:41:39 +0200 Subject: [PATCH 13/13] Limit notes passed during constexpr evaluation This patch permits only note_constexpr_invalid_cast_ptrtoint followed by note_constexpr_null_subobject to be ignored by the constexpr evaluator in -fms-compatibility mode. This restricts the range of allowed expressions to offsetof-like cases only. --- clang/lib/AST/ByteCode/State.cpp | 18 ++++++++++++++++++ clang/lib/AST/ByteCode/State.h | 2 ++ clang/test/SemaCXX/microsoft-constexpr2.cpp | 2 ++ 3 files changed, 22 insertions(+) diff --git a/clang/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp index 00e3b1a331172..75d8fc4fefb1a 100644 --- a/clang/lib/AST/ByteCode/State.cpp +++ b/clang/lib/AST/ByteCode/State.cpp @@ -18,6 +18,23 @@ using namespace clang::interp; State::~State() {} +// With -fms-compatibility we allow pointer to integer casts +// followed by nullptr casts. +void State::clearDiagIfNeeded(diag::kind DiagId) { + switch (DiagId) { + case diag::note_constexpr_invalid_cast_ptrtoint: + case diag::note_constexpr_null_subobject: + return; + } + + auto *Diag = EvalStatus.Diag; + if (!Ctx.getLangOpts().MSVCCompat || !Diag || Diag->size() != 1 || + (*Diag)[0].second.getDiagID() != + diag::note_constexpr_invalid_cast_ptrtoint) + return; + Diag->clear(); +} + OptionalDiagnostic State::FFDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { return diag(Loc, DiagId, ExtraNotes, false); @@ -41,6 +58,7 @@ OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { + clearDiagIfNeeded(DiagId); // Don't override a previous diagnostic. Don't bother collecting // diagnostics if we're evaluating for overflow. if (!EvalStatus.Diag || !EvalStatus.Diag->empty()) { diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index a720033c7914b..289c024f417e3 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -93,6 +93,8 @@ class State { ASTContext &getASTContext() const { return Ctx; } const LangOptions &getLangOpts() const { return Ctx.getLangOpts(); } + void clearDiagIfNeeded(diag::kind DiagId); + /// Note that we have had a side-effect, and determine whether we should /// keep evaluating. bool noteSideEffect() const { diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index fd83b08ce9a3d..1b917e635d66f 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -22,3 +22,5 @@ constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{co // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} constexpr LONG_PTR b7[2] = { FIELD_OFFSET(S, y), (LONG_PTR)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b8 = (LONG_PTR)((char*)1 + FIELD_OFFSET(S, y)); // expected-error {{constexpr variable 'b8' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
