Skip to content

Conversation

@sdkrystian
Copy link
Member

Fixes llvm#175426

Copy link
Collaborator

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTemplateInstantiationPattern should dig down until it finds the pattern as written by the user, it would be pretty inconvenient if we relied on this for some cases but not others.

Can we just fix getTemplateInstantiationPattern instead?

@vinniefalco
Copy link
Member


name: Fix GH175426 Crash
overview: Fix an assertion failure in IsAtLeastAsConstrained when comparing defaulted comparison operators for local classes inside generic lambdas by properly iterating through nested member specialization chains.
todos:

  • id: fix-iteration
    content: Change if-statement to while-loop in getMoreConstrainedFunction in SemaTemplateDeduction.cpp
    status: completed
  • id: add-test
    content: Create regression test clang/test/SemaTemplate/GH175426.cpp
    status: completed

Fix GH175426: Assertion Failure in Constraint Partial Ordering

Problem Analysis

The crash occurs when:

  1. A function template contains a generic lambda
  2. The lambda contains a local class with a defaulted operator<=>
  3. The synthesized operator< triggers constraint partial ordering comparison

The failing assertion in SemaConcept.cpp:2470 checks that function declarations passed to IsAtLeastAsConstrained have TemplatedKind of either TK_NonTemplate or TK_FunctionTemplate. However, when dealing with nested instantiations (local class inside generic lambda inside function template), getTemplateInstantiationPattern(false) only traverses one level of the member specialization chain.

Root Cause

In SemaTemplateDeduction.cpp getMoreConstrainedFunction:

FunctionDecl *F1 = FD1;
if (FunctionDecl *P = FD1->getTemplateInstantiationPattern(false))
  F1 = P;

This only goes one level back. For a local class inside a generic lambda, the instantiation chain may have multiple levels:

  • Instantiated comparison operator -> local class pattern's operator -> ...

The pattern retrieved may still be TK_MemberSpecialization, causing the assertion to fail.

Compare to SemaLambda.cpp getPatternFunctionDecl which correctly iterates:

if (FD->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization) {
  while (FD->getInstantiatedFromMemberFunction())
    FD = FD->getInstantiatedFromMemberFunction();
  return FD;
}

Solution

Modify getMoreConstrainedFunction to iterate through all levels of the instantiation chain by using a while loop instead of a single if:

FunctionDecl *F1 = FD1;
while (FunctionDecl *P = F1->getTemplateInstantiationPattern(false))
  F1 = P;

FunctionDecl *F2 = FD2;
while (FunctionDecl *P = F2->getTemplateInstantiationPattern(false))
  F2 = P;

Files to Modify

Test Case

The test should verify no crash occurs and the expected error (missing <compare> header) is properly emitted:

// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s

template <typename T1> auto fwd2(const T1 &t1) {
  return [](auto &&...ts1) {
    struct LocalType {
      int data;
      auto operator<=>(const LocalType &other) const = default; // expected-error {{cannot default}}
    };
    LocalType x{42}, y{42};
    static_assert(noexcept(x < y), "Noexcept check failed");
  }();
}

int main() { return fwd2(1); }
@sdkrystian
Copy link
Member Author

@mizvekov

That's a good point. Let me examine FunctionDecl::getTemplateInstantiationPattern to understand why it doesn't iterate through the full chain like VarDecl::getTemplateInstantiationPattern does.

[2 tools called]

You're right. Looking at the two implementations:

VarDecl::getTemplateInstantiationPattern iterates through the chain:

  if (MemberSpecializationInfo *MSInfo = VD->getMemberSpecializationInfo()) {
    if (isTemplateInstantiation(MSInfo->getTemplateSpecializationKind())) {
      VD = VD->getInstantiatedFromStaticDataMember();
      while (auto *NewVD = VD->getInstantiatedFromStaticDataMember())
        VD = NewVD;
    }
  }

FunctionDecl::getTemplateInstantiationPattern only goes one level:

  if (MemberSpecializationInfo *Info = FD->getMemberSpecializationInfo()) {
    if (ForDefinition &&
        !clang::isTemplateInstantiation(Info->getTemplateSpecializationKind()))
      return nullptr;
    return getDefinitionOrSelf(cast<FunctionDecl>(Info->getInstantiatedFrom()));
  }

This inconsistency does seem like an oversight. The fix in FunctionDecl::getTemplateInstantiationPattern would be to add a similar iteration loop:

if (MemberSpecializationInfo *Info = FD->getMemberSpecializationInfo()) {
  if (ForDefinition &&
      !clang::isTemplateInstantiation(Info->getTemplateSpecializationKind()))
    return nullptr;
  FunctionDecl *Result = FD->getInstantiatedFromMemberFunction();
  while (FunctionDecl *Next = Result->getInstantiatedFromMemberFunction())
    Result = Next;
  return getDefinitionOrSelf(Result);
}

This would be a more principled fix because:

  1. It matches the behavior of VarDecl::getTemplateInstantiationPattern
  2. All callers of getTemplateInstantiationPattern would benefit from consistent behavior
  3. It avoids having to add iteration loops in multiple call sites like getMoreConstrainedFunction

Would you like to switch to Agent mode so I can implement this fix in clang/lib/AST/Decl.cpp instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants