[clang] Fix MSVC template parsing error in SerializationFormat (PR #196571)
Romaric Jodin via cfe-commits
cfe-commits at lists.llvm.org
Sat May 9 10:26:18 PDT 2026
https://github.com/rjodinchr updated https://github.com/llvm/llvm-project/pull/196571
>From 97d98f922be0f3ccf1fec99daef82cf40992531e Mon Sep 17 00:00:00 2001
From: Romaric Jodin <rjodin at google.com>
Date: Fri, 8 May 2026 16:14:06 +0200
Subject: [PATCH] Fix MSVC template parsing error in SerializationFormat
This commit fixes a hard compilation error on Windows (when building with
Clang's MSVC compatibility mode) and a subsequent access violation that
occurred during Windows CI testing.
Root Causes:
1. When compiling with `-fms-compatibility`, Clang's two-phase template
lookup fails to resolve function-local static variables (`SavedSerialize`
and `SavedDeserialize`) captured by a local class (`ConcreteCodec`) inside
an uninstantiated template. It incorrectly assumes they are members of a
dependent base class.
2. Originally, `TypedSerializerFn` and `DeserializerFn` were typed as
`llvm::function_ref`. Storing these in static variables created dangling
pointers, as `function_ref` is a non-owning wrapper that only referenced
the temporaries decaying on the constructor's stack, causing an 0xC0000005
access violation on x64 Windows.
The Fix:
* Hoisted `SavedSerialize` and `SavedDeserialize` out of the constructor
scope to be `static inline` members of the `Add` class template. This allows
Clang's Phase 1 parser to correctly resolve the symbols.
* Redefined `TypedSerializerFn` and `DeserializerFn` to raw function pointers
instead of `llvm::function_ref`. This ensures the static registry variables
safely capture the persistent addresses of the global functions being
registered, eliminating the dangling pointers.
Safety note: The original concern regarding `dlopen` symbol visibility on
Linux is preserved. `ConcreteCodec` continues to store the functions strictly
as instance members, snapshotting the plugin's local copy of the static
variables at instantiation.
---
.../Core/Serialization/SerializationFormat.h | 30 +++++++++++--------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h
index fd261c6d9a723..080628a700ba1 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h
@@ -111,7 +111,7 @@ class SerializationFormat {
FormatT, llvm::function_ref<SerRet(const AnalysisResult &, SerArgs...)>,
llvm::function_ref<DesRet(DesArgs...)>> {
- using DeserializerFn = llvm::function_ref<DesRet(DesArgs...)>;
+ using DeserializerFn = DesRet (*)(DesArgs...);
public:
/// Abstract base type stored in \c llvm::Registry<Codec>.
@@ -130,8 +130,10 @@ class SerializationFormat {
};
template <class AnalysisResultT> struct Add {
- using TypedSerializerFn =
- llvm::function_ref<SerRet(const AnalysisResultT &, SerArgs...)>;
+ using TypedSerializerFn = SerRet (*)(const AnalysisResultT &, SerArgs...);
+
+ static inline TypedSerializerFn SavedSerialize;
+ static inline DeserializerFn SavedDeserialize;
/// Takes the plugin's typed serializer and the deserializer, and
/// inserts them into \c llvm::Registry<Codec>.
@@ -147,15 +149,19 @@ class SerializationFormat {
Registered = true;
/// The plugin's serializer and deserializer are captured in
- /// function-local statics so that the \c ConcreteCodec default
- /// constructor (required by \c llvm::Registry) can read them.
- /// They are stored as instance members of \c ConcreteCodec rather
- /// than \c static \c inline class members to avoid symbol
- /// visibility issues across shared library boundaries on Linux
- /// (where \c dlopen with \c RTLD_LOCAL can give the host and
- /// plugin separate copies of \c static \c inline members).
- static TypedSerializerFn SavedSerialize = TypedSerialize;
- static DeserializerFn SavedDeserialize = Deserialize;
+ /// static inline members of the Add template so that the
+ /// \c ConcreteCodec default constructor (required by \c llvm::Registry)
+ /// can read them. They use raw function pointers to prevent dangling
+ /// references to temporary stack variables during registration.
+ ///
+ /// Once read by the constructor, they are stored as instance members
+ /// of \c ConcreteCodec rather than directly executed from the \c static
+ /// \c inline class members. This prevents symbol visibility issues
+ /// across shared library boundaries on Linux (where \c dlopen with \c
+ /// RTLD_LOCAL can give the host and plugin separate copies of \c static
+ /// \c inline members).
+ SavedSerialize = TypedSerialize;
+ SavedDeserialize = Deserialize;
/// Concrete subclass of \c Codec for \c AnalysisResultT.
/// The \c serialize() override performs the downcast from
More information about the cfe-commits
mailing list