https://github.com/memehdi14 updated https://github.com/llvm/llvm-project/pull/228212
>From 2e2accca8d8df05aaeddb01ec6f0377c07f753b7 Mon Sep 17 00:00:00 2001 From: MeMehdi14 <[email protected]> Date: Thu, 1 Oct 2026 19:26:45 +0000 Subject: [PATCH 1/3] [Clang][Sema] Diagnose type mismatch on redeclaration after redefinition error When a variable redefinition error occurs, the rejected declaration is marked invalid. In Sema::MergeVarDecl, this invalid declaration was passed to MergeVarDeclTypes, which returns early when either declaration is invalid. As a result, subsequent redeclarations with conflicting types were never diagnosed. Later, constant evaluation of expressions reading the variable would walk the redeclaration chain and locate the original initializer with a mismatched type, causing an assertion failure in IntExprEvaluator::Success. Fix this by merging types against the most recent valid declaration in the redeclaration chain, ensuring type incompatibility errors are emitted and invalid declarations do not bypass semantic type checking. Also verified that related issue #114174 does not crash. Fixes #227733 --- clang/docs/ReleaseNotes.md | 3 +++ clang/lib/Sema/SemaDecl.cpp | 18 +++++++++++++++--- .../Sema/redefinition-then-different-type.c | 10 ++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 clang/test/Sema/redefinition-then-different-type.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 32116077622b58..0b0d6503fe99af 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -618,6 +618,9 @@ features cannot lower the translation-unit ABI level; ### Bug Fixes in This Version +- Fixed an assertion failure in the constant evaluator after a variable redefinition + error followed by a redeclaration with a different type. (#227733) + - Fixed incorrect handling of C++ import preprocessing token when a digraph character after import. (#GH190693) - Fixed a crash when emitting RTTI for a `dllexport` class, or the fundamental type descriptors for `__cxxabiv1::__fundamental_type_info`, under `-fvisibility=hidden`. (#GH207963) - Fixed an assertion failure when passing a wide string literal to `__builtin_nan`. (#GH212108) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 1459b71326375a..c744c69aeb999f 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -4814,15 +4814,27 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) { } // Merge the types. - VarDecl *MostRecent = Old->getMostRecentDecl(); - if (MostRecent != Old) { + VarDecl *TypeOld = Old; + if (TypeOld->isInvalidDecl()) { + for (VarDecl *Prev = Old->getPreviousDecl(); Prev; + Prev = Prev->getPreviousDecl()) { + if (!Prev->isInvalidDecl()) { + TypeOld = Prev; + break; + } + } + } + + VarDecl *MostRecent = TypeOld->getMostRecentDecl(); + if (MostRecent != TypeOld) { MergeVarDeclTypes(New, MostRecent, mergeTypeWithPrevious(*this, New, MostRecent, Previous)); if (New->isInvalidDecl()) return; } - MergeVarDeclTypes(New, Old, mergeTypeWithPrevious(*this, New, Old, Previous)); + MergeVarDeclTypes(New, TypeOld, + mergeTypeWithPrevious(*this, New, TypeOld, Previous)); if (New->isInvalidDecl()) return; diff --git a/clang/test/Sema/redefinition-then-different-type.c b/clang/test/Sema/redefinition-then-different-type.c new file mode 100644 index 00000000000000..f859ad69aa6e89 --- /dev/null +++ b/clang/test/Sema/redefinition-then-different-type.c @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s + +// Previously crashed with an assertion in IntExprEvaluator::Success. +// https://github.com/llvm/llvm-project/issues/227733 + +const char e = 1; // expected-note 2 {{previous definition is here}} +const char e = 1; // expected-error {{redefinition of 'e'}} +const unsigned e; // expected-error {{redefinition of 'e' with a different type: 'const unsigned int' vs 'const char'}} +int x = e; >From 08f1d84310e2253ba798018753ceb929fbfded2b Mon Sep 17 00:00:00 2001 From: MeMehdi14 <[email protected]> Date: Mon, 12 Oct 2026 18:03:02 +0000 Subject: [PATCH 2/3] [Clang][Sema] Fix #227733: handle all-invalid decl chains and mismatched initializer types Address reviewer feedback on PR #228212: 1. SemaDecl.cpp (MergeVarDecl): If the entire redeclaration chain is invalid (i.e. no valid previous declaration exists), propagate invalidity to the new declaration via setInvalidDecl(). Previously, the loop fell through and TypeOld remained invalid, causing MergeVarDeclTypes to silently skip type-checking and leave the new declaration in an inconsistent valid state. Also guard MostRecent from being used when it is itself invalid. 2. ExprConstant.cpp (evaluateVarDeclInit): After getAnyInitializer() walks the redeclaration chain and returns an initializer attached to InitDecl (which may differ from VD), verify two things before proceeding: - InitDecl is not invalid. - InitDecl->getType() and VD->getType() are the same or compatible (canonical, unqualified). A mismatch means the initializer was written for a declaration with a different type (e.g. the original const char, while VD is const unsigned), and using it would produce an APSInt with the wrong signedness/width, triggering the assertion in IntExprEvaluator::Success. Return false cleanly in both cases so the evaluator treats the variable as non-constant rather than crashing. 3. ByteCode/Compiler.cpp (visitDeclRef): Bail out with emitError() if the resolved VarDecl is invalid, preventing the new constant interpreter from attempting to evaluate references to declarations that are in an invalid state. 4. Test updates (redefinition-then-different-type.c): - Add -fexperimental-new-constant-interpreter RUN lines (C and C++). - Add coverage for the all-invalid-chain case (two bad-type declarations followed by a valid one). Fixes #227733 --- clang/lib/AST/ByteCode/Compiler.cpp | 2 +- clang/lib/AST/ExprConstant.cpp | 17 ++++++++++++++++- clang/lib/Sema/SemaDecl.cpp | 5 ++++- .../Sema/redefinition-then-different-type.c | 8 ++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e0f2a29860d093..7b695b65ccfca2 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -8567,7 +8567,7 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) { // Try to lazily visit (or emit dummy pointers for) declarations // we haven't seen yet. const auto *VD = dyn_cast<VarDecl>(D); - if (!VD) + if (!VD || VD->isInvalidDecl()) return this->emitError(E); // For C. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index d5b8d0ed889d12..e0fbacb58a4b50 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3560,7 +3560,8 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // Dig out the initializer, and use the declaration which it's attached to. // FIXME: We should eventually check whether the variable has a reachable // initializing declaration. - const Expr *Init = VD->getAnyInitializer(VD); + const VarDecl *InitDecl = VD; + const Expr *Init = VD->getAnyInitializer(InitDecl); // P2280R4 struck the restriction that variable of reference type should have // a preceding initialization. // Used to be C++20 [expr.const]p5.12: @@ -3576,6 +3577,20 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, return false; } + if (Init) { + if (InitDecl->isInvalidDecl()) + return false; + + QualType InitType = + Info.Ctx.getCanonicalType(InitDecl->getType()).getUnqualifiedType(); + QualType VDType = + Info.Ctx.getCanonicalType(VD->getType()).getUnqualifiedType(); + if (InitType != VDType && !Info.Ctx.typesAreCompatible(InitType, VDType)) + return false; + + VD = InitDecl; + } + // P2280R4 struck the initialization requirement for variables of reference // type so we can no longer assume we have an Init. // Used to be C++20 [expr.const]p5.12: diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index c744c69aeb999f..71248a73ac93c7 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -4825,8 +4825,11 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) { } } + if (TypeOld->isInvalidDecl()) + return New->setInvalidDecl(); + VarDecl *MostRecent = TypeOld->getMostRecentDecl(); - if (MostRecent != TypeOld) { + if (MostRecent != TypeOld && !MostRecent->isInvalidDecl()) { MergeVarDeclTypes(New, MostRecent, mergeTypeWithPrevious(*this, New, MostRecent, Previous)); if (New->isInvalidDecl()) diff --git a/clang/test/Sema/redefinition-then-different-type.c b/clang/test/Sema/redefinition-then-different-type.c index f859ad69aa6e89..d6d2f99b2d22cb 100644 --- a/clang/test/Sema/redefinition-then-different-type.c +++ b/clang/test/Sema/redefinition-then-different-type.c @@ -1,5 +1,7 @@ // RUN: %clang_cc1 -fsyntax-only -verify %s // RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -fsyntax-only -verify -fexperimental-new-constant-interpreter -x c++ %s // Previously crashed with an assertion in IntExprEvaluator::Success. // https://github.com/llvm/llvm-project/issues/227733 @@ -8,3 +10,9 @@ const char e = 1; // expected-note 2 {{previous definition is here}} const char e = 1; // expected-error {{redefinition of 'e'}} const unsigned e; // expected-error {{redefinition of 'e' with a different type: 'const unsigned int' vs 'const char'}} int x = e; + +// Test when all previous declarations in the redeclaration chain are invalid. +invalid_type_1 a; // expected-error {{unknown type name 'invalid_type_1'}} +invalid_type_2 a; // expected-error {{unknown type name 'invalid_type_2'}} +int a; +int y = a; >From 502019553d931345284f2430c3d5a02c07efd229 Mon Sep 17 00:00:00 2001 From: MeMehdi14 <[email protected]> Date: Tue, 13 Oct 2026 13:45:33 +0000 Subject: [PATCH 3/3] [Clang][Sema] Address review feedback on #228212: simplify checks and expand tests Address reviewer feedback on PR #228212: 1. SemaDecl.cpp (MergeVarDecl): - Switch to iterating over Old->redecls() to locate the most recent valid declaration in the chain, with a detailed explanatory comment. - When all prior declarations in the chain are invalid, skip type merging instead of prematurely bailing out with setInvalidDecl(), allowing downstream redefinition diagnostics to run normally. 2. ExprConstant.cpp (evaluateVarDeclInit): - Remove redundant type-compatibility check. If InitDecl is invalid, bail out early. If valid, update VD = InitDecl so subsequent evaluation resolves against the declaration owning the initializer. 3. Test updates (redefinition-then-different-type.c): - Pass -std=c23 and use constexpr to force constant evaluation in both C and C++. - Add test coverage for all-invalid declaration chains and valid-invalid-valid-use sequences. --- clang/lib/AST/ExprConstant.cpp | 10 +---- clang/lib/Sema/SemaDecl.cpp | 42 ++++++++++-------- .../Sema/redefinition-then-different-type.c | 44 +++++++++++-------- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index e0fbacb58a4b50..1115ed940d717d 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3577,17 +3577,11 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, return false; } + // If the declaration holding the initializer is itself invalid, bail out + // rather than evaluating a potentially bogus value. if (Init) { if (InitDecl->isInvalidDecl()) return false; - - QualType InitType = - Info.Ctx.getCanonicalType(InitDecl->getType()).getUnqualifiedType(); - QualType VDType = - Info.Ctx.getCanonicalType(VD->getType()).getUnqualifiedType(); - if (InitType != VDType && !Info.Ctx.typesAreCompatible(InitType, VDType)) - return false; - VD = InitDecl; } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 71248a73ac93c7..4f3ff08fc83b46 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -4814,33 +4814,37 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) { } // Merge the types. - VarDecl *TypeOld = Old; - if (TypeOld->isInvalidDecl()) { - for (VarDecl *Prev = Old->getPreviousDecl(); Prev; - Prev = Prev->getPreviousDecl()) { - if (!Prev->isInvalidDecl()) { - TypeOld = Prev; - break; - } + // + // If Old or any prior declaration in the redeclaration chain is invalid, + // MergeVarDeclTypes will return early without diagnosing a type conflict. + // Walk the chain (using redecls(), which goes newest-to-oldest) to find the + // most recent declaration that is valid so we can still detect type + // mismatches on New. If every prior declaration is invalid, skip the type + // merge entirely to avoid false positives; the redefinition logic below will + // still run and emit the appropriate diagnostic. + VarDecl *TypeOld = nullptr; + for (VarDecl *Prev : Old->redecls()) { + if (!Prev->isInvalidDecl()) { + TypeOld = Prev; + break; } } - if (TypeOld->isInvalidDecl()) - return New->setInvalidDecl(); + if (TypeOld) { + VarDecl *MostRecent = TypeOld->getMostRecentDecl(); + if (MostRecent != TypeOld && !MostRecent->isInvalidDecl()) { + MergeVarDeclTypes(New, MostRecent, + mergeTypeWithPrevious(*this, New, MostRecent, Previous)); + if (New->isInvalidDecl()) + return; + } - VarDecl *MostRecent = TypeOld->getMostRecentDecl(); - if (MostRecent != TypeOld && !MostRecent->isInvalidDecl()) { - MergeVarDeclTypes(New, MostRecent, - mergeTypeWithPrevious(*this, New, MostRecent, Previous)); + MergeVarDeclTypes(New, TypeOld, + mergeTypeWithPrevious(*this, New, TypeOld, Previous)); if (New->isInvalidDecl()) return; } - MergeVarDeclTypes(New, TypeOld, - mergeTypeWithPrevious(*this, New, TypeOld, Previous)); - if (New->isInvalidDecl()) - return; - diag::kind PrevDiag; SourceLocation OldLocation; std::tie(PrevDiag, OldLocation) = diff --git a/clang/test/Sema/redefinition-then-different-type.c b/clang/test/Sema/redefinition-then-different-type.c index d6d2f99b2d22cb..42e1082ecc9608 100644 --- a/clang/test/Sema/redefinition-then-different-type.c +++ b/clang/test/Sema/redefinition-then-different-type.c @@ -1,18 +1,26 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s -// RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s -// RUN: %clang_cc1 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s -// RUN: %clang_cc1 -fsyntax-only -verify -fexperimental-new-constant-interpreter -x c++ %s - -// Previously crashed with an assertion in IntExprEvaluator::Success. -// https://github.com/llvm/llvm-project/issues/227733 - -const char e = 1; // expected-note 2 {{previous definition is here}} -const char e = 1; // expected-error {{redefinition of 'e'}} -const unsigned e; // expected-error {{redefinition of 'e' with a different type: 'const unsigned int' vs 'const char'}} -int x = e; - -// Test when all previous declarations in the redeclaration chain are invalid. -invalid_type_1 a; // expected-error {{unknown type name 'invalid_type_1'}} -invalid_type_2 a; // expected-error {{unknown type name 'invalid_type_2'}} -int a; -int y = a; +// RUN: %clang_cc1 -fsyntax-only -verify -std=c23 %s +// RUN: %clang_cc1 -fsyntax-only -verify -fexperimental-new-constant-interpreter -std=c23 %s +// RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -verify -fexperimental-new-constant-interpreter -x c++ %s + +// Previously crashed with an assertion in IntExprEvaluator::Success. +// https://github.com/llvm/llvm-project/issues/227733 + +constexpr char e = 1; // expected-note 2 {{previous definition is here}} +constexpr char e = 1; // expected-error {{redefinition of 'e'}} +constexpr unsigned e; // expected-error {{redefinition of 'e' with a different type: 'const unsigned int' vs 'const char'}} +int x = e; + +// Test when all previous declarations in the redeclaration chain are invalid: +// nothing should crash and the redefinition chain should still compile cleanly. +invalid_type_1 a; // expected-error {{unknown type name 'invalid_type_1'}} +invalid_type_2 a; // expected-error {{unknown type name 'invalid_type_2'}} +extern int a; +void test_a(void) { int y = a; } + +// Test: valid declaration, followed by an invalid one, followed by a valid one, +// followed by a use. +extern int g; +invalid_type_3 g; // expected-error {{unknown type name 'invalid_type_3'}} +extern int g; +void test_g(void) { int z = g; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
