[clang] [Clang][OpenMP] Fix crash on `copyprivate` of a non-privatized data member (PR #227699)

Akash Manna via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 30 06:10:31 PDT 2026


https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/227699

Fixes #217893

`copyprivate` needs its list items to be threadprivate or private in the enclosing context. For a data member we only checked that it wasn't `shared`, and then assumed an enclosing construct had already made a private copy of it. That doesn't hold in a `task` or `taskloop` region, where a member that is private outside is implicitly firstprivate but gets no copy, or under `default(none)`, where it has no attribute at all. With no copy to refer to we hit `assert(VD || isOpenMPCapturedDecl(D))`, and without assertions a null address could end up being passed to `__kmpc_copyprivate`.

A data member with no private copy is now diagnosed with the existing "copyprivate variable must be threadprivate or private in the enclosing context" error. In a dependent context the copies aren't built until instantiation, so the clause keeps the list item as written there and is checked when instantiated, like the other data-sharing clauses. That also fixes the crash for a data member in `copyprivate` inside a member function template.


>From cd34c759b7b7d527313f4759a63e57bc37f37971 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Wed, 30 Sep 2026 18:38:26 +0530
Subject: [PATCH] [Clang][OpenMP] Fix crash on `copyprivate` of a
 non-privatized data member

`copyprivate` needs its list items to be threadprivate or private in the
enclosing context. For a data member we only checked that it was not
`shared` and then assumed an enclosing construct had already made a
private copy of it. That does not hold in a `task` or `taskloop` region,
where a member that is private outside is implicitly firstprivate but
gets no copy, or under `default(none)`. With no copy to refer to we hit
`assert(VD || isOpenMPCapturedDecl(D))`.

Diagnose such a member with the existing "copyprivate variable must be
threadprivate or private in the enclosing context" error. In a dependent
context the copies are not built until instantiation, so keep the list
item as written there and check it on instantiation, like the other
data-sharing clauses do.

Fixes #217893
---
 clang/docs/ReleaseNotes.md                    |  2 +
 clang/lib/Sema/SemaOpenMP.cpp                 | 16 ++++++--
 .../OpenMP/single_copyprivate_messages.cpp    | 41 +++++++++++++++++++
 3 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3c6acf353f93f..01d9cd14cf4f4 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -550,6 +550,8 @@ features cannot lower the translation-unit ABI level;
 - Fixed a bug where a stray closing curley brace in an OpenMP/OpenACC pragma could cause pragma parsing issues when inside of a member function. (#GH214195)
 - Fixed a bug where preprocessor directives following comments were not correctly recognized when using -C. (#GH48361)
 - Fixed a crash when declaring a member template within a local class inside an OpenMP region. (#GH216052)
+- Fixed a crash when an OpenMP `copyprivate` clause names a non-static data member that is not private in the
+  enclosing context, which is now diagnosed, or names a data member inside a member function template. (#GH217893)
 - Fixed an assertion failure when a variable implicitly mapped by an OpenMP `target` directive has a class type
   (such as `std::map`) whose mapper lookup instantiates a class template specialization. (#GH154704)
 - Fixed a bug where repeated #imports of modular headers in non-modular compilation were translated to #pragma clang module import. (#GH216924)
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index 41cfc1dcd3649..f1b440a625d7c 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -23289,6 +23289,14 @@ OMPClause *SemaOpenMP::ActOnOpenMPCopyprivateClause(ArrayRef<Expr *> VarList,
           reportOriginalDsa(SemaRef, DSAStack, D, DVar);
           continue;
         }
+        // A data member is private only if an enclosing construct captured it.
+        if (isa<FieldDecl>(D) && !SemaRef.CurContext->isDependentContext() &&
+            !isOpenMPCapturedDecl(D)) {
+          Diag(ELoc, diag::err_omp_required_access)
+              << getOpenMPClauseNameForDiag(OMPC_copyprivate)
+              << "threadprivate or private in the enclosing context";
+          continue;
+        }
       }
     }
 
@@ -23333,10 +23341,12 @@ OMPClause *SemaOpenMP::ActOnOpenMPCopyprivateClause(ArrayRef<Expr *> VarList,
 
     // No need to mark vars as copyprivate, they are already threadprivate or
     // implicitly private.
-    assert(VD || isOpenMPCapturedDecl(D));
+    assert(VD || SemaRef.CurContext->isDependentContext() ||
+           isOpenMPCapturedDecl(D));
     Vars.push_back(
-        VD ? RefExpr->IgnoreParens()
-           : buildCapture(SemaRef, D, SimpleRefExpr, /*WithInit=*/false));
+        (VD || SemaRef.CurContext->isDependentContext())
+            ? RefExpr->IgnoreParens()
+            : buildCapture(SemaRef, D, SimpleRefExpr, /*WithInit=*/false));
     SrcExprs.push_back(PseudoSrcExpr);
     DstExprs.push_back(PseudoDstExpr);
     AssignmentOps.push_back(AssignmentOp.get());
diff --git a/clang/test/OpenMP/single_copyprivate_messages.cpp b/clang/test/OpenMP/single_copyprivate_messages.cpp
index 1dc508b01bd74..734c48f863b6d 100644
--- a/clang/test/OpenMP/single_copyprivate_messages.cpp
+++ b/clang/test/OpenMP/single_copyprivate_messages.cpp
@@ -206,3 +206,44 @@ int parallel() {
 
   return 0;
 }
+
+namespace GH217893 {
+struct S {
+  int a;
+  S() {
+#pragma omp parallel firstprivate(a)
+#pragma omp taskloop
+#pragma omp single copyprivate(a) // expected-error {{copyprivate variable must be threadprivate or private in the enclosing context}} expected-error {{region cannot be closely nested inside 'taskloop' region; perhaps you forget to enclose 'omp single' directive into a parallel region?}}
+  }; // expected-error {{expected statement}}
+
+  void nested() {
+#pragma omp parallel firstprivate(a)
+#pragma omp taskloop
+    for (int i = 0; i < 2; ++i) {
+#pragma omp single copyprivate(a) // expected-error {{copyprivate variable must be threadprivate or private in the enclosing context}} expected-error {{region cannot be closely nested inside 'taskloop' region; perhaps you forget to enclose 'omp single' directive into a parallel region?}}
+      foo();
+    }
+#pragma omp parallel default(none)
+#pragma omp single copyprivate(a) // expected-error {{copyprivate variable must be threadprivate or private in the enclosing context}}
+    foo();
+#pragma omp parallel private(a)
+#pragma omp single copyprivate(a)
+    foo();
+  }
+
+  template <class T> void dependent() {
+#pragma omp parallel private(a)
+#pragma omp single copyprivate(a)
+    foo();
+#pragma omp parallel private(a)
+#pragma omp task
+#pragma omp taskgroup
+#pragma omp single copyprivate(a) // expected-error {{copyprivate variable must be threadprivate or private in the enclosing context}}
+    foo();
+  }
+};
+
+void instantiate(S &s) {
+  s.dependent<int>(); // expected-note {{in instantiation of function template specialization 'GH217893::S::dependent<int>' requested here}}
+}
+}



More information about the cfe-commits mailing list