[clang] [Clang][OpenMP] Fix crash on `copyprivate` of a data member or structured binding (PR #227699)

Akash Manna via cfe-commits cfe-commits at lists.llvm.org
Sat Oct 3 11:09:10 PDT 2026


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

>From be5882a227116e5ea3014aeeb47364bf89b251cf 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 1/2] [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 6afaa6675be7cd..b55502e2003455 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -572,6 +572,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 6ef86b8391f40c..2fb49c14b3aa51 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -23303,6 +23303,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;
+        }
       }
     }
 
@@ -23347,10 +23355,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 1dc508b01bd74b..734c48f863b6dd 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}}
+}
+}

>From bbe0178523c39dd86215613cf0cee4c674f01449 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Sat, 3 Oct 2026 09:45:21 +0530
Subject: [PATCH 2/2] [Clang][OpenMP] Keep structured bindings as written in
 copyprivate

Review follow-up for #227699. A structured binding in `copyprivate` hit
the same assertion; it now stays as written in the clause, like the
other data-sharing clauses treat it. The data-member check is folded
into the `shared` branch and the DSA note is only reported for the
`shared` case.
---
 clang/docs/ReleaseNotes.md                    |  3 +-
 clang/lib/Sema/SemaOpenMP.cpp                 | 20 ++++-----
 .../OpenMP/structured-bindings-messages.cpp   | 42 +++++++++++++++++++
 3 files changed, 53 insertions(+), 12 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index b55502e2003455..4a00a5b7b76669 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -573,7 +573,8 @@ features cannot lower the translation-unit ABI level;
 - 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)
+  enclosing context, which is now diagnosed, a data member inside a member function template, or a structured
+  binding. (#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 2fb49c14b3aa51..aa8f13ebb3b789 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -23296,19 +23296,16 @@ OMPClause *SemaOpenMP::ActOnOpenMPCopyprivateClause(ArrayRef<Expr *> VarList,
       //  threadprivate or private in the enclosing context.
       if (DVar.CKind == OMPC_unknown) {
         DVar = DSAStack->getImplicitDSA(D, false);
-        if (DVar.CKind == OMPC_shared) {
-          Diag(ELoc, diag::err_omp_required_access)
-              << getOpenMPClauseNameForDiag(OMPC_copyprivate)
-              << "threadprivate or private in the enclosing context";
-          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)) {
+        const bool IsShared = DVar.CKind == OMPC_shared;
+        if (IsShared ||
+            (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";
+          if (IsShared)
+            reportOriginalDsa(SemaRef, DSAStack, D, DVar);
           continue;
         }
       }
@@ -23355,10 +23352,11 @@ OMPClause *SemaOpenMP::ActOnOpenMPCopyprivateClause(ArrayRef<Expr *> VarList,
 
     // No need to mark vars as copyprivate, they are already threadprivate or
     // implicitly private.
-    assert(VD || SemaRef.CurContext->isDependentContext() ||
+    const bool IsBindingDecl = isa<BindingDecl>(D);
+    assert(VD || IsBindingDecl || SemaRef.CurContext->isDependentContext() ||
            isOpenMPCapturedDecl(D));
     Vars.push_back(
-        (VD || SemaRef.CurContext->isDependentContext())
+        (VD || IsBindingDecl || SemaRef.CurContext->isDependentContext())
             ? RefExpr->IgnoreParens()
             : buildCapture(SemaRef, D, SimpleRefExpr, /*WithInit=*/false));
     SrcExprs.push_back(PseudoSrcExpr);
diff --git a/clang/test/OpenMP/structured-bindings-messages.cpp b/clang/test/OpenMP/structured-bindings-messages.cpp
index 647de0055068cd..88316d317ef981 100644
--- a/clang/test/OpenMP/structured-bindings-messages.cpp
+++ b/clang/test/OpenMP/structured-bindings-messages.cpp
@@ -563,3 +563,45 @@ void test_bindings_only_orig_not_dereferenced() {
     c = c + 30;
   }
 }
+
+void test_copyprivate_orphaned() {
+  Point p{1, 2};
+  auto [a, b] = p;
+#pragma omp single copyprivate(a, b)
+  ;
+}
+
+void test_copyprivate_private() {
+  Point p{1, 2};
+  auto [a, b] = p;
+#pragma omp parallel private(a, b)
+#pragma omp single copyprivate(a, b)
+  ;
+}
+
+void test_copyprivate_firstprivate() {
+  Point p{1, 2};
+  auto [a, b] = p;
+#pragma omp parallel firstprivate(a)
+#pragma omp single copyprivate(a)
+  ;
+}
+
+void test_copyprivate_shared() {
+  Point p{1, 2};
+  auto [a, b] = p;
+#pragma omp parallel // expected-note {{implicitly determined as shared}}
+#pragma omp single copyprivate(a) // expected-error {{copyprivate variable must be threadprivate or private in the enclosing context}}
+  ;
+}
+
+template <typename T> void test_copyprivate_template(T p) {
+  auto [a, b] = p;
+#pragma omp parallel private(a)
+#pragma omp single copyprivate(a)
+  ;
+}
+
+void instantiate_copyprivate_template() {
+  test_copyprivate_template(Point{1, 2});
+}



More information about the cfe-commits mailing list