llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Rex Technology (RexTechnology1) <details> <summary>Changes</summary> Fixes #<!-- -->202956 `TemplateDeclInstantiator::VisitVarTemplatePartialSpecializationDecl` assumes the primary member variable template has already been instantiated into the current instantiation (`Owner`) — normally true, because the primary is declared before its partial specializations and is therefore visited first while the enclosing class is instantiated. When the enclosing class template's pattern is deserialized from a **preamble PCH built with compiler errors** (`-fallow-pch-with-compiler-errors`, which clangd always uses for preambles), the primary may not have been materialized into `Owner` by the time a partial specialization is visited. The lookup then returns empty, tripping `assert(!Found.empty() && "Instantiation found nothing?")` in assertions builds and dereferencing a null `Found.front()` (SIGSEGV) in release builds. This is hit in practice by clangd on code using `std::expected<T, E>` from libstdc++ 15.x (its `__cons_from_expected` member variable template has a `<…, bool>` partial specialization), whenever the preamble contains an unresolved `#include` — a routine state in interactive editing (e.g. not-yet-generated headers). A five-line reproducer (3-line header + 2-line main file) is in the linked issue; it crashes deterministically, and removing only the unresolved-include line makes it disappear. All released clang versions tested (20, 21, 22) and current `main` are affected — the crashing function is byte-identical at tip `53ae585a9` (2026-06-10). **Fix:** when the lookup is empty, instantiate the primary member variable template on demand and look it up again, restoring the invariant the assertions expect. The original invariant assertions are kept (so a genuinely-missing primary is still flagged in +Asserts builds), with a `nullptr` return as a release-safe guard. The common (non-deserialized) path is unchanged. **Verification:** on an assertions build of trunk, the reproducer no longer crashes and the retained assertions do not fire. Instrumentation confirms the on-demand instantiation succeeds, is taken exactly once per affected instantiation, and creates no duplicate declarations (the member-instantiation loop does not visit the primary again — counted via instrumentation on `Owner->addDecl`). `SemaTemplate`, `PCH`, `Modules`, and `CXX/temp` show no regressions (the only failures are identical on the unpatched baseline). ### Test The reproducer is tiny (3-line header + 2-line main file) but currently fires only through clangd's in-process `PrecompiledPreamble` with real libstdc++ 15 headers: a CLI `-emit-pch`/`-include-pch` split does not reproduce, even with `-Xclang -fallow-pch-with-compiler-errors` and `-Xclang -preamble-bytes=N,1`, and a self-contained mock of the `<expected>` structure does not trigger it. If there is precedent for an errorful-preamble fixture (e.g. a clangd `TestTU`-style unittest), I'm happy to add a regression test on top of it — guidance welcome. --- <sub>Developed with assistance from an AI coding tool (Claude); reviewed by me, and I can speak to the analysis and trade-offs. The commit carries `Assisted-by: Claude (Anthropic)`.</sub> --- Full diff: https://github.com/llvm/llvm-project/pull/202958.diff 1 Files Affected: - (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+24) ``````````diff diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index aa381f09138de..c11312843b18e 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2558,10 +2558,34 @@ Decl *TemplateDeclInstantiator::VisitVarTemplatePartialSpecializationDecl( // Lookup the already-instantiated declaration and return that. DeclContext::lookup_result Found = Owner->lookup(VarTemplate->getDeclName()); + + // Normally the primary member variable template has already been instantiated + // into Owner, because it is declared before its partial specializations and + // so is visited first while instantiating the enclosing class. However, when + // the class template pattern is deserialized from a module or precompiled + // preamble, the primary may not have been materialized into Owner yet, + // leaving the lookup empty. Previously this asserted (and crashed release + // builds with a null dereference). Instantiate the primary on demand and look + // it up again. + if (Found.empty()) { + if (Decl *InstPrimary = Visit(VarTemplate)) + if (auto *InstVTD = dyn_cast<VarTemplateDecl>(InstPrimary)) + Found = Owner->lookup(InstVTD->getDeclName()); + } + + // After the on-demand instantiation above the primary must be present. Keep + // the original invariant assertions so a genuinely-missing primary is still + // flagged in +Asserts builds, with a null return as a release-safe guard so a + // stray case degrades gracefully instead of dereferencing an empty lookup + // result. assert(!Found.empty() && "Instantiation found nothing?"); + if (Found.empty()) + return nullptr; VarTemplateDecl *InstVarTemplate = dyn_cast<VarTemplateDecl>(Found.front()); assert(InstVarTemplate && "Instantiation did not find a variable template?"); + if (!InstVarTemplate) + return nullptr; if (VarTemplatePartialSpecializationDecl *Result = InstVarTemplate->findPartialSpecInstantiatedFromMember(D)) `````````` </details> https://github.com/llvm/llvm-project/pull/202958 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
