-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[clang++-19][regression] "error: type constraint differs in template redeclaration" #110231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
A repro can be found here: https://godbolt.org/z/6brEd3de6. It also fails on "clang (trunk)," but it works fine on the other compilers mentioned. |
@llvm/issue-subscribers-clang-frontend Author: Mateusz Pusz (mpusz)
I get the following error while compiling on clang-19:
Here are the offending declarations:
The same code works fine on clang-17, clang-18, gcc-13, and gcc-14. |
I came to open an issue exactly about this with llvm-19.1.0 via homebrew on Apple M1 Pro CPU. |
Looks like this started in clang-19: https://godbolt.org/z/oxPf3oWjM I would like a reduction to understand what is going on better though. |
Confirmed on 19 and trunk. Note several declarations between struct array {
int __elems_[4] = {1, 2, 3, 4};
};
template < typename, template < typename > typename >
int is_specialization_of;
template < typename, template < auto... > typename >
constexpr bool is_specialization_of_v = 0;
template < auto... Params, template < auto... > typename Type >
constexpr bool is_specialization_of_v< Type< Params... >, Type > = 1;
constexpr auto get_first_of(int n) {
auto g = [&]{};
int __elems_[4] = {1, 2, 3, 4};
int* first = __elems_;
for (;;++first)
if (n % (*first))
return *first;
return 0;
}
consteval int find_first_factor(int n) {
constexpr auto basis = array{};
auto k = get_first_of(n);
return k;
}
template < typename T >
concept PowerVBase = true;
template < PowerVBase auto, int >
struct power_v {
static constexpr auto base = 0;
};
template < typename T >
concept MagnitudeSpec = is_specialization_of_v< T, power_v >;
template < MagnitudeSpec auto... >
struct magnitude;
template < typename T >
concept Magnitude = is_specialization_of_v< T, magnitude >;
template < auto V >
consteval auto power_v_or_T() {
return power_v< V, 0 >{};
}
template < typename >
struct magnitude_base {};
template < MagnitudeSpec auto... Ms >
struct magnitude : magnitude_base< magnitude< Ms... > > {
template < Magnitude M >
friend consteval auto operator*(magnitude m1, M) {
return m1;
}
friend auto operator/(magnitude, auto r) {
return pow(r);
}
friend auto pow(magnitude) {
return magnitude< power_v_or_T< Ms >()... >{};
}
};
template < int N >
struct prime_factorization {
static constexpr int first_base = find_first_factor(N);
static constexpr int remainder = N / first_base;
static constexpr auto value = magnitude< power_v_or_T< first_base >() >{} * prime_factorization< remainder >::value;
};
template <>
struct prime_factorization< 0 > {
static magnitude<> value;
};
template < int N >
auto prime_factorization_v = prime_factorization< N >::value;
template < auto, auto >
concept NestedQuantityKindSpecOf = true;
template < typename T, auto >
concept QuantitySpecOf = NestedQuantityKindSpecOf< 0, T{} >;
template < Magnitude auto >
struct scaled_unit {};
template < typename T >
concept Reference = true;
template < typename T, auto >
concept PointOriginFor = true;
struct unit_interface {};
template < typename M >
consteval auto operator*(M, unit_interface) -> scaled_unit< M{} > {
return scaled_unit< M{} >{};
}
template < auto >
struct named_unit;
template < auto U >
requires(true)
struct named_unit< U > {};
template < Reference auto R, PointOriginFor< R > auto >
struct quantity_point;
auto mag_ratio = prime_factorization_v< 0 > / prime_factorization_v< 1000 >;
template class named_unit< mag_ratio * unit_interface{} >;
template < Reference auto R, PointOriginFor< R > auto >
struct quantity_point; Clang 20 output:
|
CC @mizvekov because I saw several TTPs are at play (where the situation could be horrible) |
Somewhat surprisingly, when I locally ran Clang 19.1 binary that CE uses against the reduction, I got a crash:
|
When I feed a local debug build of trunk with the reduction, I consistently get the following diagnostics:
|
On godbolt using It |
I guess it runs out of stack like it did for me several comments above |
Bisected to #92425 |
Also see bc62fb9 which introduced CC @zygoloid who implemented that patch |
…ck argument This fixes a regression introduced in #96023, reported in #110231 (comment)
That's a separate regression introduced by a recent commit, should be fixed by #110963 |
…ck argument This fixes a regression introduced in #96023, reported in #110231 (comment)
…ck argument (#110963) This fixes a regression introduced in #96023, reported in #110231 (comment)
ping @sdkrystian |
@shafik Edit: It's still broken |
This looks fixed on trunk can you confirm @mpusz |
@shafik It's still failing intermittently on trunk (but not assertions trunk) |
@shafik, I confirm that it seems to be fixed on trunk: https://godbolt.org/z/rf5dTe9hh. Now we wait for the next clang-19 release (or patch). |
Is it failing the same way? Which reproducer? Do you only see it locally or also on godbolt? |
Yes, you just need to recompile a few times.
|
I was able to see this now. That is bad, we need to understand what is going on. |
Looks like UB in the compiler somewhere |
Combination of ASan and UBSan didn't find anything. |
Maybe actually using the library could help reproduce. |
I failed to get a build of Clang with MSan that doesn't produce false-positives in the driver, which is likely caused by uninstrumented runtime despite my best efforts to provide one and make sure it's used. I know we have an MSan buildbot, but I sank too much time into this over the weekend to continue. |
Hi, is there any update on this issue? I am being asked by my customers why clang-19 does not work with mp-units 😢 If you have problems with reproduction, you can follow @JohelEGP's suggestion to try to compile all the mp-units directly. It fails every single time. With Conan, you can reproduce the problem with just a simple single command:
More info can be found in our documentation if needed. |
BTW, I am attending the ISO C++ Committee meeting this week in Wrocław. If someone from the Clang team is here this week as well, we can work together to find the source of this issue. |
Hello, Following the stack overflow hint, I think I have managed to reduce further: template < typename >
concept Reference = true;
template < typename , auto >
concept PointOriginFor = true;
template < Reference auto R, PointOriginFor< R > auto>
struct quantity_point; I can reproduce the (random!) stack overflow in the
|
Hello again, I believe the problem is somewhere between Basically, the NTTP As the parser advances, Now, the hashing is not stable, and the iteration does not depend on the order of insertion. Later on, when looking again inside
While this explains the random stack overflow, I am not clear how this influences the false diagnostics mentioned above. However, I think there may be some unexpected iterator invalidation at some point: i.e., while traversing the set, we recurse, land in the same set again (an insertion?), and invalidate the iterator. A very dirty hack that prevents the recursion (simply a boolean added to the node to make sure we don't land on it again) resolves the stack overflow (or, at least, doesn't reproduce after 1000 runs), which seems to confirm this is what is happening. Now, I do not know how to solve this because, in isolation, each step makes sense. |
I think I have got the other one figured out, but since I am not too confident about how this part of the code works, I am not going to open a PR, not yet, at least:
This goes away (does not reproduce after several thousand of runs), with this patch (I have left an assert I used to track the issue, which states my assumption) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 14fbadbc35ae..a769817d07b3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -6295,6 +6295,9 @@ QualType ASTContext::getAutoTypeInternal(
!TypeConstraintConcept && !IsDependent)
return getAutoDeductType();
+ IsDependent =
+ IsDependent || (!DeducedType.isNull() && DeducedType->isDependentType());
+
// Look in the folding set for an existing type.
void *InsertPos = nullptr;
llvm::FoldingSetNodeID ID;
@@ -6334,6 +6337,10 @@ QualType ASTContext::getAutoTypeInternal(
(IsPack ? TypeDependence::UnexpandedPack : TypeDependence::None),
Canon, TypeConstraintConcept, TypeConstraintArgs);
Types.push_back(AT);
+
+ llvm::FoldingSetNodeID AUX;
+ AT->Profile(AUX, *this);
+ assert(AUX.ComputeHash() == ID.ComputeHash());
AutoTypes.InsertNode(AT, InsertPos);
return QualType(AT, 0);
} My understanding is that // In ASTContext::getAutoTypeInternal
llvm::FoldingSetNodeID ID;
AutoType::Profile(ID, *this, DeducedType, Keyword, IsDependent,
TypeConstraintConcept, TypeConstraintArgs); Does not match the FoldingSetNodeID of the actually inserted |
...in `ASTContext::getAutoTypeInternal` Given ```cpp template < typename > concept C1 = true; template < typename , auto > concept C2 = true; template < C1 auto V, C2< V > auto> struct S; ``` Both `C1 auto V` and `C2<V> auto` end on the set `AutoType`, the former being a template parameter for the latter. Since the hashing is not deterministic (i.e., pointers are hashed), every now and then, both will end on the same bucket. Given that `FoldingSet` recomputes the `FoldingSetID` for each node in the target bucket on lookup, this triggers an infinite recursion: 1. Look for `X` in `AutoTypes` 2. Let's assume it would be in bucket N, so it iterates over nodes in that bucket. Let's assume the first is `C2<V> auto`. 3. Computes the `FoldingSetID` for this one, which requires the profile of its template parameters, so they are visited. 4. In some frames below, we end on the same `FoldingSet`, and, by chance, `C1 auto V` would be in bucket N too. 5. But the first node in the bucket is `C2<V> auto` for which we need to profile `C1 auto V` 6. ... stack overflow! No step individually does anything wrong, but in general, `FoldingSet` seems not to be re-entrant, and this fact is hidden behind many nested calls. With this change, we store the `AutoType`s inside a `DenseMap` instead. The `FoldingSetID` is computed once only and then kept as the map's key, avoiding the need to do recursive lookups. We also now make sure the key for the inserted `AutoType` is the same as the key used for lookup. Before, this was not the case, and it caused also non-deterministic parsing errors. Fixes llvm#110231
...in `ASTContext::getAutoTypeInternal` Given ```cpp template < typename > concept C1 = true; template < typename , auto > concept C2 = true; template < C1 auto V, C2< V > auto> struct S; ``` Both `C1 auto V` and `C2<V> auto` end on the set `AutoType`, the former being a template parameter for the latter. Since the hashing is not deterministic (i.e., pointers are hashed), every now and then, both will end on the same bucket. Given that `FoldingSet` recomputes the `FoldingSetID` for each node in the target bucket on lookup, this triggers an infinite recursion: 1. Look for `X` in `AutoTypes` 2. Let's assume it would be in bucket N, so it iterates over nodes in that bucket. Let's assume the first is `C2<V> auto`. 3. Computes the `FoldingSetID` for this one, which requires the profile of its template parameters, so they are visited. 4. In some frames below, we end on the same `FoldingSet`, and, by chance, `C1 auto V` would be in bucket N too. 5. But the first node in the bucket is `C2<V> auto` for which we need to profile `C1 auto V` 6. ... stack overflow! No step individually does anything wrong, but in general, `FoldingSet` seems not to be re-entrant, and this fact is hidden behind many nested calls. With this change, we store the `AutoType`s inside a `DenseMap` instead. The `FoldingSetID` is computed once only and then kept as the map's key, avoiding the need to do recursive lookups. We also now make sure the key for the inserted `AutoType` is the same as the key used for lookup. Before, this was not the case, and it caused also non-deterministic parsing errors. Fixes llvm#110231 Fix formatting
I get the following error while compiling on clang-19:
Here are the offending declarations:
The same code works fine on clang-17, clang-18, gcc-13, and gcc-14.
The text was updated successfully, but these errors were encountered: