Skip to content

Sema::SubstDecl crashes in InitMethodInstantiation on constructor templates of instantiated class templates (clang 22, --buggy-substitute-default-template-args) #34

@Fedr

Description

@Fedr

Summary

Sema::SubstDecl call at src/parser/main.cpp:2815 (inside the --buggy-substitute-default-template-args code path) crashes inside clang::TemplateDeclInstantiator::InitMethodInstantiation with STATUS_ACCESS_VIOLATION (0xC0000005) on clang 22.1.4, when the visitor processes a constructor template member of an already-instantiated class template specialization.

This is the next-layer crash after #33 (which guarded InstantiateDefaultArgument and InstantiateFunctionDefinition on primary templates). With #33 merged the boost::multiprecision parse still segfaults on some builds; this issue tracks that remaining crash.

Trigger

Real-world: a Windows MSVC-target build that includes <boost/multiprecision/cpp_int.hpp> and passes --buggy-substitute-default-template-args. mrbind invocation (full args in the CI log linked below):

mrbind.exe --format=json --buggy-substitute-default-template-args ...
           meshlib.combined.hpp -o meshlib.generated.json
           -- -std=c++23 --target=x86_64-pc-windows-msvc
              -resource-dir=C:/msys64/clang64/lib/clang/22
              -isystem .../mrbind-pybind11/include
              -isystem .../parallel-hashmap
              -I C:/vcpkg/installed/x64-windows-vs2019-meshlib/include
              ...

(meshlib.combined.hpp is a generated header that #includes all MeshLib public headers, which transitively pull in <boost/multiprecision/cpp_int.hpp>.)

The crash is flaky on some CI runs and reliable on others — the symptom is the same access violation in the same code path, but timing/cache state seems to influence whether it actually fires.

Stack trace

mrbind is built RelWithDebInfo; libclang-cpp.dll is stripped (msys2 default), so the libclang frames only have function names. Captured via the LLVM signal handler installed in main() (PR #33 second commit).

Exception Code: 0xC0000005

 #0 clang::TemplateDeclInstantiator::InitMethodInstantiation(
       CXXMethodDecl *NewMethod, CXXMethodDecl *PatternMethod)            <-- CRASH
 #1 clang::TemplateDeclInstantiator::VisitCXXMethodDecl(
       CXXMethodDecl*, TemplateParameterList*,
       TemplateDeclInstantiator::RewriteKind)
 #2 clang::Sema::SubstDecl(Decl*, DeclContext*,
       MultiLevelTemplateArgumentList const&)::$_0
 #3 clang::StackExhaustionHandler::runWithSufficientStackSpace
 #4 clang::Sema::runWithSufficientStackSpace
 #5 clang::Sema::SubstDecl(Decl*, DeclContext*,
       MultiLevelTemplateArgumentList const&)
 #6 mrbind::ClangAstVisitor_InstTypesAndCollectNewTypes::VisitFunctionDecl
       src/parser/main.cpp:2815:41                                        <-- our call site
 #7 RecursiveASTVisitor::TraverseCXXConstructorDecl(CXXConstructorDecl*)  <-- ctor template
 #8 RecursiveASTVisitor::TraverseDecl(Decl*)
 #9 RecursiveASTVisitor::TraverseFunctionTemplateDecl(FunctionTemplateDecl*)
#10 RecursiveASTVisitor::TraverseDecl(Decl*)
#11 RecursiveASTVisitor::TraverseDeclContextHelper(DeclContext*)
#12 RecursiveASTVisitor::TraverseClassTemplateSpecializationDecl(
       ClassTemplateSpecializationDecl*)
#13 mrbind::ClangAstVisitor_InstTypesAndCollectNewTypes::
       FinishRecordEvenIfRejected(RecordDecl*) src/parser/main.cpp:2992:13
#14 mrbind::ClangAstVisitor_InstTypesAndCollectNewTypes::
       TraverseClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl*)
       src/parser/main.cpp:3025:13
#15-#17 RecursiveASTVisitor::TraverseClassTemplateDecl, ::TraverseTemplateInstantiations
#21 mrbind::ClangAstVisitor_InstTypesAndCollectNewTypes::TraverseNamespaceDecl
       src/parser/main.cpp:3035:13
...
#27 mrbind::ClangAstConsumer::HandleTranslationUnit src/parser/main.cpp:3099:13
#28 clang::ParseAST(clang::Sema&, bool, bool)

The crashing decl is a CXXConstructorDecl that's a member of a ClassTemplateSpecializationDecl reached via TraverseTemplateInstantiations. mrbind passes the constructor's templated decl to Sema::SubstDecl(templated_decl, templated_decl->getParent(), ml_targs). Clang's InitMethodInstantiation deref-faults on something the API doesn't validate.

Where mrbind calls into the bug

// src/parser/main.cpp:2691..2826 (inside VisitFunctionDecl)
if (params->buggy_substitute_default_template_args && decl->isTemplated()
    && !ShouldRejectFunction(...)) {
    if (auto templ = decl->getDescribedTemplate()) {
        auto func_templ = llvm::dyn_cast<clang::FunctionTemplateDecl>(templ);
        ...
        // Compute ml_targs from default template arguments.
        ...
        clang::Sema::SFINAETrap trap(ci->getSema());
        if (!trap.hasErrorOccurred()) {
            ...
            auto ret = cast_or_null<clang::FunctionDecl>(
                ci->getSema().SubstDecl(templated_decl,                  // ← line 2815
                                        templated_decl->getParent(),
                                        ml_targs));
            ...
        }
    }
}

When decl is a constructor template's templated decl whose enclosing class is a ClassTemplateSpecializationDecl, the SubstDecl call routes through VisitCXXMethodDeclInitMethodInstantiation and segfaults.

Reproduction sources

I attempted several synthetic reproducers (Wrap<T> class templates with constructor templates in various shapes, including some that mirror boost::multiprecision::number's signature) on the same mrbind+toolchain locally and could not get them to fire this exact code path. The smallest reliable repro I have remains the full meshlib.combined.hpp parse from the linked CI run. Suggestions for what makes boost's ctor templates special here are very welcome — likely candidates: SFINAE in default args, enable_if chains, ctors that take other specializations of the same class template, or interaction with forward-declared template params.

Suggested directions for a fix

This is the same family as #33: mrbind drives a clang Sema entry point with inputs that don't match the API's implicit preconditions, and clang null-derefs instead of erroring cleanly. Two ways forward, not mutually exclusive:

  1. mrbind-side guard. Skip the SubstDecl call when the templated decl is a CXXConstructorDecl (or any CXXMethodDecl) whose lexical parent is a ClassTemplateSpecializationDecl. Constructors of an instantiated class template don't need their primary template re-substituted by mrbind — clang already produced the per-spec ctor instantiations. If we can identify the trigger more precisely than "any ctor template inside any spec," the guard can be narrower. (This is the same shape of fix as in Parser: skip primary function templates in Sema instantiation calls #33.)

  2. Upstream LLVM hardening. InitMethodInstantiation should be defensive against the inputs SubstDecl reaches it with on this path — at minimum assert with a clear message instead of null-deref. I plan to file a companion upstream issue against llvm/llvm-project if/when we have a reduced repro that crashes plain clang -fsyntax-only (haven't gotten there yet).

Workarounds

  • Remove --buggy-substitute-default-template-args from mrbind_flags.txt. Untested for behavioral side-effects on the generated bindings, but it bypasses this exact path.
  • Pin clang to a version that doesn't trip on the same input (issue Parser: skip primary function templates in Sema instantiation calls #33's investigation showed the same Sema bug class is at least as old as clang 18, but its exact trigger may differ per version).
  • Filter boost::multiprecision out via --skip-mentions-of / --ignore (best effort; ours already filters some boost types but not multiprecision).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions