[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