Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Support pack expansion for Clang Thread Safety attributes #137477

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

Merged
merged 1 commit into from
Apr 30, 2025

Conversation

aaronpuchert
Copy link
Member

Support for attribute parameter packs was added some time ago in commit ead1690. But template substitution didn't expand the packs yet. For now expansion can only happen within a VariadicExprArgument: i.e. in try_acquire_capability, which takes a regular and a variadic argument, the template can't have a single pack that then expands to cover both arguments.

This is a prerequisite for #42000.

Support for attribute parameter packs was added some time ago in commit
ead1690. But template substitution
didn't expand the packs yet. For now expansion can only happen within a
`VariadicExprArgument`: i.e. in `try_acquire_capability`, which takes a
regular and a variadic argument, the template can't have a single pack
that then expands to cover both arguments.

This is a prerequisite for llvm#42000.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Apr 26, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 26, 2025

@llvm/pr-subscribers-clang

Author: Aaron Puchert (aaronpuchert)

Changes

Support for attribute parameter packs was added some time ago in commit ead1690. But template substitution didn't expand the packs yet. For now expansion can only happen within a VariadicExprArgument: i.e. in try_acquire_capability, which takes a regular and a variadic argument, the template can't have a single pack that then expands to cover both arguments.

This is a prerequisite for #42000.


Full diff: https://github.com/llvm/llvm-project/pull/137477.diff

4 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+8)
  • (modified) clang/test/SemaCXX/warn-thread-safety-analysis.cpp (+47)
  • (modified) clang/test/SemaCXX/warn-thread-safety-parsing.cpp (+145-2)
  • (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+14-16)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index dcdcff8c46fe2..2548e60312a0d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3856,6 +3856,7 @@ def AssertCapability : InheritableAttr {
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
   let Args = [VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
                     [Clang<"assert_shared_capability", 0>,
                      GNU<"assert_shared_lock">]>];
@@ -3873,6 +3874,7 @@ def AcquireCapability : InheritableAttr {
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
   let Args = [VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
                     [Clang<"acquire_shared_capability", 0>,
                      GNU<"shared_lock_function">]>];
@@ -3890,6 +3892,7 @@ def TryAcquireCapability : InheritableAttr {
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
   let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
                     [Clang<"try_acquire_shared_capability", 0>,
                      GNU<"shared_trylock_function">]>];
@@ -3907,6 +3910,7 @@ def ReleaseCapability : InheritableAttr {
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
   let Args = [VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
                     [Clang<"release_shared_capability", 0>]>,
                    Accessor<"isGeneric",
@@ -3921,6 +3925,7 @@ def RequiresCapability : InheritableAttr {
                    Clang<"requires_shared_capability", 0>,
                    Clang<"shared_locks_required", 0>];
   let Args = [VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let LateParsed = LateAttrParseStandard;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
@@ -3963,6 +3968,7 @@ def PtGuardedBy : InheritableAttr {
 def AcquiredAfter : InheritableAttr {
   let Spellings = [GNU<"acquired_after">];
   let Args = [VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let LateParsed = LateAttrParseExperimentalExt;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
@@ -3974,6 +3980,7 @@ def AcquiredAfter : InheritableAttr {
 def AcquiredBefore : InheritableAttr {
   let Spellings = [GNU<"acquired_before">];
   let Args = [VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let LateParsed = LateAttrParseExperimentalExt;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
@@ -3995,6 +4002,7 @@ def LockReturned : InheritableAttr {
 def LocksExcluded : InheritableAttr {
   let Spellings = [GNU<"locks_excluded">];
   let Args = [VariadicExprArgument<"Args">];
+  let AcceptsExprPack = 1;
   let LateParsed = LateAttrParseStandard;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 2a04e820eb095..4c17294d11e79 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -57,6 +57,20 @@ class SCOPED_LOCKABLE DoubleMutexLock {
   ~DoubleMutexLock() UNLOCK_FUNCTION();
 };
 
+template<typename Mu>
+class SCOPED_LOCKABLE TemplateMutexLock {
+public:
+  TemplateMutexLock(Mu *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
+  ~TemplateMutexLock() UNLOCK_FUNCTION();
+};
+
+template<typename... Mus>
+class SCOPED_LOCKABLE VariadicMutexLock {
+public:
+  VariadicMutexLock(Mus *...mus) EXCLUSIVE_LOCK_FUNCTION(mus...);
+  ~VariadicMutexLock() UNLOCK_FUNCTION();
+};
+
 // The universal lock, written "*", allows checking to be selectively turned
 // off for a particular piece of code.
 void beginNoWarnOnReads()  SHARED_LOCK_FUNCTION("*");
@@ -1821,6 +1835,18 @@ struct TestScopedLockable {
     a = b + 1;
     b = a + 1;
   }
+
+  void foo6() {
+    TemplateMutexLock<Mutex> mulock1(&mu1), mulock2(&mu2);
+    a = b + 1;
+    b = a + 1;
+  }
+
+  void foo7() {
+    VariadicMutexLock<Mutex, Mutex> mulock(&mu1, &mu2);
+    a = b + 1;
+    b = a + 1;
+  }
 };
 
 } // end namespace test_scoped_lockable
@@ -3114,6 +3140,16 @@ class SCOPED_LOCKABLE ReaderMutexUnlock {
   void Unlock() EXCLUSIVE_LOCK_FUNCTION();
 };
 
+template<typename... Mus>
+class SCOPED_LOCKABLE VariadicMutexUnlock {
+public:
+  VariadicMutexUnlock(Mus *...mus) EXCLUSIVE_UNLOCK_FUNCTION(mus...);
+  ~VariadicMutexUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
+
+  void Lock() EXCLUSIVE_UNLOCK_FUNCTION();
+  void Unlock() EXCLUSIVE_LOCK_FUNCTION();
+};
+
 Mutex mu;
 int x GUARDED_BY(mu);
 bool c;
@@ -3176,6 +3212,17 @@ void doubleUnlock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
   scope.Unlock(); // expected-warning {{releasing mutex 'mu' that was not held}}
 }
 
+Mutex mu2;
+int y GUARDED_BY(mu2);
+
+void variadic() EXCLUSIVE_LOCKS_REQUIRED(mu, mu2) {
+  VariadicMutexUnlock<Mutex, Mutex> scope(&mu, &mu2);
+  x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
+  y = 3; // expected-warning {{writing variable 'y' requires holding mutex 'mu2' exclusively}}
+  scope.Lock();
+  x = y = 4;
+}
+
 class SCOPED_LOCKABLE MutexLockUnlock {
 public:
   MutexLockUnlock(Mutex *mu1, Mutex *mu2) EXCLUSIVE_UNLOCK_FUNCTION(mu1) EXCLUSIVE_LOCK_FUNCTION(mu2);
diff --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
index 752803e4a0543..bad71f7cdd07c 100644
--- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety %s
 // RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++98 %s
-// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++11 %s -D CPP11
+// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++11 %s
 
 #define LOCKABLE            __attribute__ ((lockable))
 #define SCOPED_LOCKABLE     __attribute__ ((scoped_lockable))
@@ -643,6 +643,24 @@ int elf_function_bad_6(Mutex x, Mutex y) EXCLUSIVE_LOCK_FUNCTION(0); // \
 int elf_function_bad_7() EXCLUSIVE_LOCK_FUNCTION(0); // \
   // expected-error {{'exclusive_lock_function' attribute parameter 1 is out of bounds: no parameters to index into}}
 
+template<typename Mu>
+int elf_template(Mu& mu) EXCLUSIVE_LOCK_FUNCTION(mu) {}
+
+template int elf_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int elf_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int elf_variadic_template(Mus&... mus) EXCLUSIVE_LOCK_FUNCTION(mus...) {}
+
+template int elf_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int elf_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
+
 
 //-----------------------------------------//
 //  Shared Lock Function (slf)
@@ -719,6 +737,24 @@ int slf_function_bad_6(Mutex x, Mutex y) SHARED_LOCK_FUNCTION(0); // \
 int slf_function_bad_7() SHARED_LOCK_FUNCTION(0); // \
   // expected-error {{'shared_lock_function' attribute parameter 1 is out of bounds: no parameters to index into}}
 
+template<typename Mu>
+int slf_template(Mu& mu) SHARED_LOCK_FUNCTION(mu) {}
+
+template int slf_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int slf_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int slf_variadic_template(Mus&... mus) SHARED_LOCK_FUNCTION(mus...) {}
+
+template int slf_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int slf_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
+
 
 //-----------------------------------------//
 //  Exclusive TryLock Function (etf)
@@ -796,6 +832,24 @@ int etf_function_bad_5() EXCLUSIVE_TRYLOCK_FUNCTION(1, muDoublePointer); // \
 int etf_function_bad_6() EXCLUSIVE_TRYLOCK_FUNCTION(1, umu); // \
   // expected-warning {{'exclusive_trylock_function' attribute requires arguments whose type is annotated with 'capability' attribute}}
 
+template<typename Mu>
+int etf_template(Mu& mu) EXCLUSIVE_TRYLOCK_FUNCTION(1, mu) {}
+
+template int etf_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int etf_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int etf_variadic_template(Mus&... mus) EXCLUSIVE_TRYLOCK_FUNCTION(1, mus...) {}
+
+template int etf_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int etf_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
+
 
 //-----------------------------------------//
 //  Shared TryLock Function (stf)
@@ -874,6 +928,24 @@ int stf_function_bad_5() SHARED_TRYLOCK_FUNCTION(1, muDoublePointer); // \
 int stf_function_bad_6() SHARED_TRYLOCK_FUNCTION(1, umu); // \
   // expected-warning {{'shared_trylock_function' attribute requires arguments whose type is annotated with 'capability' attribute}}
 
+template<typename Mu>
+int stf_template(Mu& mu) SHARED_TRYLOCK_FUNCTION(1, mu) {}
+
+template int stf_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int stf_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int stf_variadic_template(Mus&... mus) SHARED_TRYLOCK_FUNCTION(1, mus...) {}
+
+template int stf_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int stf_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
+
 
 //-----------------------------------------//
 //  Unlock Function (uf)
@@ -953,6 +1025,24 @@ int uf_function_bad_6(Mutex x, Mutex y) UNLOCK_FUNCTION(0); // \
 int uf_function_bad_7() UNLOCK_FUNCTION(0); // \
   // expected-error {{'unlock_function' attribute parameter 1 is out of bounds: no parameters to index into}}
 
+template<typename Mu>
+int uf_template(Mu& mu) UNLOCK_FUNCTION(mu) {}
+
+template int uf_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int uf_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int uf_variadic_template(Mus&... mus) UNLOCK_FUNCTION(mus...) {}
+
+template int uf_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int uf_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
+
 
 //-----------------------------------------//
 //  Lock Returned (lr)
@@ -1088,6 +1178,23 @@ int le_function_bad_3() LOCKS_EXCLUDED(muDoublePointer); // \
 int le_function_bad_4() LOCKS_EXCLUDED(umu); // \
   // expected-warning {{'locks_excluded' attribute requires arguments whose type is annotated with 'capability' attribute}}
 
+template<typename Mu>
+int le_template(Mu& mu) LOCKS_EXCLUDED(mu) {}
+
+template int le_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int le_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int le_variadic_template(Mus&... mus) LOCKS_EXCLUDED(mus...) {}
+
+template int le_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int le_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
 
 
 //-----------------------------------------//
@@ -1156,6 +1263,24 @@ int elr_function_bad_3() EXCLUSIVE_LOCKS_REQUIRED(muDoublePointer); // \
 int elr_function_bad_4() EXCLUSIVE_LOCKS_REQUIRED(umu); // \
   // expected-warning {{'exclusive_locks_required' attribute requires arguments whose type is annotated with 'capability' attribute}}
 
+template<typename Mu>
+int elr_template(Mu& mu) EXCLUSIVE_LOCKS_REQUIRED(mu) {}
+
+template int elr_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int elr_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int elr_variadic_template(Mus&... mus) EXCLUSIVE_LOCKS_REQUIRED(mus...) {}
+
+template int elr_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int elr_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
+
 
 
 
@@ -1225,6 +1350,24 @@ int slr_function_bad_3() SHARED_LOCKS_REQUIRED(muDoublePointer); // \
 int slr_function_bad_4() SHARED_LOCKS_REQUIRED(umu); // \
   // expected-warning {{'shared_locks_required' attribute requires arguments whose type is annotated with 'capability' attribute}}
 
+template<typename Mu>
+int slr_template(Mu& mu) SHARED_LOCKS_REQUIRED(mu) {}
+
+template int slr_template<Mutex>(Mutex&);
+// FIXME: warn on template instantiation.
+template int slr_template<UnlockableMu>(UnlockableMu&);
+
+#if __cplusplus >= 201103
+
+template<typename... Mus>
+int slr_variadic_template(Mus&... mus) SHARED_LOCKS_REQUIRED(mus...) {}
+
+template int slr_variadic_template<Mutex, Mutex>(Mutex&, Mutex&);
+// FIXME: warn on template instantiation.
+template int slr_variadic_template<Mutex, UnlockableMu>(Mutex&, UnlockableMu&);
+
+#endif
+
 
 //-----------------------------------------//
 //  Regression tests for unusual cases.
@@ -1582,7 +1725,7 @@ class Foo {
 }  // end namespace FunctionAttributesInsideClass_ICE_Test
 
 
-#ifdef CPP11
+#if __cplusplus >= 201103
 namespace CRASH_POST_R301735 {
   class SomeClass {
    public:
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 00df99d04d873..b7f7f8f0b2fe2 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -1329,28 +1329,26 @@ namespace {
 
     void writeTemplateInstantiationArgs(raw_ostream &OS) const override {
       OS << "tempInst" << getUpperName() << ", "
-         << "A->" << getLowerName() << "_size()";
+         << "numTempInst" << getUpperName();
     }
 
     void writeTemplateInstantiation(raw_ostream &OS) const override {
-      OS << "      auto *tempInst" << getUpperName()
-         << " = new (C, 16) " << getType()
-         << "[A->" << getLowerName() << "_size()];\n";
+      OS << "      size_t numTempInst" << getUpperName() << ";\n";
+      OS << "      " << getType() << "*tempInst" << getUpperName() << ";\n";
       OS << "      {\n";
       OS << "        EnterExpressionEvaluationContext "
          << "Unevaluated(S, Sema::ExpressionEvaluationContext::Unevaluated);\n";
-      OS << "        " << getType() << " *TI = tempInst" << getUpperName()
-         << ";\n";
-      OS << "        " << getType() << " *I = A->" << getLowerName()
-         << "_begin();\n";
-      OS << "        " << getType() << " *E = A->" << getLowerName()
-         << "_end();\n";
-      OS << "        for (; I != E; ++I, ++TI) {\n";
-      OS << "          ExprResult Result = S.SubstExpr(*I, TemplateArgs);\n";
-      OS << "          if (Result.isInvalid())\n";
-      OS << "            return nullptr;\n";
-      OS << "          *TI = Result.get();\n";
-      OS << "        }\n";
+      OS << "        ArrayRef<" << getType() << "> ArgsToInstantiate(A->"
+         << getLowerName() << "_begin(), A->" << getLowerName() << "_end());\n";
+      OS << "        SmallVector<" << getType() << ", 4> InstArgs;\n";
+      OS << "        if (S.SubstExprs(ArgsToInstantiate, /*IsCall=*/false, "
+            "TemplateArgs, InstArgs))\n";
+      OS << "          return nullptr;\n";
+      OS << "        numTempInst" << getUpperName() << " = InstArgs.size();\n";
+      OS << "        tempInst" << getUpperName() << " = new (C, 16) "
+         << getType() << "[numTempInst" << getUpperName() << "];\n";
+      OS << "        std::copy(InstArgs.begin(), InstArgs.end(), tempInst"
+         << getUpperName() << ");\n";
       OS << "      }\n";
     }
 

@aaronpuchert aaronpuchert requested a review from erichkeane April 26, 2025 21:51
@aaronpuchert aaronpuchert merged commit 228b330 into llvm:main Apr 30, 2025
14 checks passed
@aaronpuchert aaronpuchert deleted the thread-safety-pack-expansion branch April 30, 2025 21:21
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
Support for attribute parameter packs was added some time ago in commit
ead1690. But template substitution
didn't expand the packs yet. For now expansion can only happen within a
`VariadicExprArgument`: i.e. in `try_acquire_capability`, which takes a
regular and a variadic argument, the template can't have a single pack
that then expands to cover both arguments.

This is a prerequisite for llvm#42000.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
Support for attribute parameter packs was added some time ago in commit
ead1690. But template substitution
didn't expand the packs yet. For now expansion can only happen within a
`VariadicExprArgument`: i.e. in `try_acquire_capability`, which takes a
regular and a variadic argument, the template can't have a single pack
that then expands to cover both arguments.

This is a prerequisite for llvm#42000.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
Support for attribute parameter packs was added some time ago in commit
ead1690. But template substitution
didn't expand the packs yet. For now expansion can only happen within a
`VariadicExprArgument`: i.e. in `try_acquire_capability`, which takes a
regular and a variadic argument, the template can't have a single pack
that then expands to cover both arguments.

This is a prerequisite for llvm#42000.
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
Support for attribute parameter packs was added some time ago in commit
ead1690. But template substitution
didn't expand the packs yet. For now expansion can only happen within a
`VariadicExprArgument`: i.e. in `try_acquire_capability`, which takes a
regular and a variadic argument, the template can't have a single pack
that then expands to cover both arguments.

This is a prerequisite for llvm#42000.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants