[clang] [OpenMP] Fix crash with extern reference and diagnose loop variable reuse in collapsed loops. (PR #203252)
Zahira Ammarguellat via cfe-commits
cfe-commits at lists.llvm.org
Sat Jun 13 11:15:33 PDT 2026
https://github.com/zahiraam updated https://github.com/llvm/llvm-project/pull/203252
>From b925b2c3fa0f15b20898bd6456329eddfdb027ea Mon Sep 17 00:00:00 2001
From: Zahira Ammarguellat <zahira.ammarguellat at intel.com>
Date: Wed, 20 May 2026 05:48:13 -0700
Subject: [PATCH 1/7] [OpenMP] Prevent parser infinite loop on unimplemented
clauses
---
clang/lib/Basic/OpenMPKinds.cpp | 1 +
clang/lib/Parse/ParseOpenMP.cpp | 9 ++
clang/lib/Sema/SemaOpenMP.cpp | 4 +
.../OpenMP/unimplemented_clause_messages.cpp | 93 +++++++++++++++++++
4 files changed, 107 insertions(+)
create mode 100644 clang/test/OpenMP/unimplemented_clause_messages.cpp
diff --git a/clang/lib/Basic/OpenMPKinds.cpp b/clang/lib/Basic/OpenMPKinds.cpp
index 287eb217ba458..675d86349c933 100644
--- a/clang/lib/Basic/OpenMPKinds.cpp
+++ b/clang/lib/Basic/OpenMPKinds.cpp
@@ -965,6 +965,7 @@ void clang::getOpenMPCaptureRegions(
case OMPD_simd:
case OMPD_single:
case OMPD_target_data:
+ case OMPD_taskgraph:
case OMPD_taskgroup:
case OMPD_stripe:
// These directives (when standalone) use OMPD_unknown as the region,
diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp
index 45a47ec797f01..ba3d3113700ff 100644
--- a/clang/lib/Parse/ParseOpenMP.cpp
+++ b/clang/lib/Parse/ParseOpenMP.cpp
@@ -2388,6 +2388,8 @@ StmtResult Parser::ParseOpenMPExecutableDirective(
ImplicitClauseAllowed = false;
Actions.OpenMP().StartOpenMPClause(CKind);
HasImplicitClause = false;
+ SourceLocation ClauseLoc = Tok.getLocation();
+
OMPClause *Clause =
ParseOpenMPClause(DKind, CKind, !SeenClauses[unsigned(CKind)]);
SeenClauses[unsigned(CKind)] = true;
@@ -2398,6 +2400,13 @@ StmtResult Parser::ParseOpenMPExecutableDirective(
if (Tok.is(tok::comma))
ConsumeToken();
Actions.OpenMP().EndOpenMPClause();
+
+ // If ParseOpenMPClause returned without consuming any tokens, skip
+ // to end to avoid an infinite loop.
+ if (Tok.getLocation() == ClauseLoc) {
+ skipUntilPragmaOpenMPEnd(DKind);
+ break;
+ }
}
// End location of the directive.
EndLoc = Tok.getLocation();
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index d6f6bc919a31b..76b40a5039180 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -6778,6 +6778,10 @@ StmtResult SemaOpenMP::ActOnOpenMPExecutableDirective(
case OMPD_begin_declare_variant:
case OMPD_end_declare_variant:
llvm_unreachable("OpenMP Directive is not allowed");
+ case OMPD_taskgraph:
+ Diag(StartLoc, diag::err_omp_unexpected_directive)
+ << 1 << getOpenMPDirectiveName(OMPD_taskgraph);
+ return StmtError();
case OMPD_unknown:
default:
llvm_unreachable("Unknown OpenMP directive");
diff --git a/clang/test/OpenMP/unimplemented_clause_messages.cpp b/clang/test/OpenMP/unimplemented_clause_messages.cpp
new file mode 100644
index 0000000000000..172203ea5d040
--- /dev/null
+++ b/clang/test/OpenMP/unimplemented_clause_messages.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 -verify=expected,omp60 -fopenmp -fopenmp-version=60 %s
+// RUN: %clang_cc1 -verify=expected,omp51 -fopenmp -fopenmp-version=51 %s
+// RUN: %clang_cc1 -verify=expected,omp60 -fopenmp-simd -fopenmp-version=60 %s
+// RUN: %clang_cc1 -verify=expected,omp51 -fopenmp-simd -fopenmp-version=51 %s
+
+
+void test_induction_basic() {
+ int i;
+ // omp60-warning at +4{{extra tokens at the end of '#pragma omp parallel for' are ignored}}
+ // omp60-error at +3{{unexpected OpenMP clause 'induction' in directive '#pragma omp parallel for'}}
+ // omp51-warning at +2{{extra tokens at the end of '#pragma omp parallel for' are ignored}}
+ // omp51-error at +1{{unexpected OpenMP clause 'induction' in directive '#pragma omp parallel for'}}
+#pragma omp parallel for induction(i)
+ for (i = 0; i < 10; ++i)
+ ;
+}
+
+void test_apply() {
+ // omp60-warning at +4{{extra tokens at the end of '#pragma omp tile' are ignored}}
+ // omp60-error at +3{{unexpected OpenMP clause 'apply' in directive '#pragma omp tile'}}
+ // omp51-error at +2{{unexpected OpenMP clause 'apply' in directive '#pragma omp tile'}}
+ // omp51-warning at +1{{extra tokens at the end of '#pragma omp tile' are ignored}}
+#pragma omp tile sizes(10) apply(intratile: unroll)
+ for (int i = 0; i < 10; ++i)
+ ;
+}
+
+void test_empty_apply() {
+ // omp60-warning at +4{{extra tokens at the end of '#pragma omp tile' are ignored}}
+ // omp60-error at +3{{unexpected OpenMP clause 'apply' in directive '#pragma omp tile'}}
+ // omp51-error at +2{{unexpected OpenMP clause 'apply' in directive '#pragma omp tile'}}
+ // omp51-warning at +1{{extra tokens at the end of '#pragma omp tile' are ignored}}
+#pragma omp tile sizes(10) apply()
+ for (int i = 0; i < 10; ++i)
+ ;
+}
+
+void test_nested_apply()
+{
+ // omp60-error at +5{{unexpected OpenMP clause 'apply' in directive '#pragma omp tile'}}
+ // omp60-warning at +4{{extra tokens at the end of '#pragma omp tile' are ignored}}
+ //omp51-error at +3{{unexpected OpenMP clause 'apply' in directive '#pragma omp tile'}}
+ // omp51-warning at +2{{extra tokens at the end of '#pragma omp tile' are ignored}}
+#pragma omp tile sizes(10) \
+ apply(intratile: unroll partial(2) apply(reverse))
+ for (int i = 0; i < 100; ++i)
+ ;
+}
+
+void test_induction_with_following_clause() {
+ int i;
+ // omp60-warning at +4{{extra tokens at the end of '#pragma omp parallel for' are ignored}}
+ // omp60-error at +3{{unexpected OpenMP clause 'induction' in directive '#pragma omp parallel for'}}
+ // omp51-error at +2{{unexpected OpenMP clause 'induction' in directive '#pragma omp parallel for'}}
+ // omp51-warning at +1{{extra tokens at the end of '#pragma omp parallel for' are ignored}}
+#pragma omp parallel for induction(i) num_threads(4)
+ for (i = 0; i < 10; ++i)
+ ;
+}
+
+class Point {
+ float x, y, m;
+ char color;
+
+};
+
+void processPointsInLine() {
+ float separation;
+ // omp60-error at +4{{unexpected OpenMP clause 'induction' in directive '#pragma omp parallel for'}}
+ // omp60-warning at +3{{extra tokens at the end of '#pragma omp parallel for' are ignored}}
+ // omp51-error at +2{{unexpected OpenMP clause 'induction' in directive '#pragma omp parallel for'}}
+ // omp51-warning at +1{{extra tokens at the end of '#pragma omp parallel for' are ignored}}
+#pragma omp parallel for induction(step(Separation))
+ for (int i = 0; i < 10; ++i) {
+ ;
+ }
+}
+
+// Make sure test doesn't crash.
+void test_tasgraph()
+{
+ // omp60-error at +2{{unexpected OpenMP directive '#pragma omp taskgraph'}}
+ // omp51-error at +1{{unexpected OpenMP directive '#pragma omp taskgraph'}}
+#pragma omp taskgraph
+ for (int i = 0; i < 10; ++i)
+ ;
+}
+
+void test_implemented_clause() {
+#pragma omp tile sizes(10)
+ for (int i = 0; i < 10; ++i)
+ ;
+}
>From c5a58c9381dedf5ea55ecf048394bc704797c127 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Thu, 11 Jun 2026 05:16:12 -0700
Subject: [PATCH 2/7] [OpenMP] Pointer dereference collapse loop
---
clang/lib/Sema/SemaOpenMP.cpp | 2 +-
clang/test/OpenMP/collapse_extern_ref_crash.cpp | 17 +++++++++++++++++
2 files changed, 18 insertions(+), 1 deletion(-)
create mode 100644 clang/test/OpenMP/collapse_extern_ref_crash.cpp
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index 113c8f3cb3016..79a0c396f787e 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -8044,7 +8044,7 @@ class ForSubExprChecker : public DynamicRecursiveASTVisitor {
VarDecl *V = VD->getPotentiallyDecomposedVarDecl();
if (V->getType()->isReferenceType()) {
VarDecl *VD = V->getDefinition();
- if (VD->hasInit()) {
+ if (VD && VD->hasInit()) {
Expr *I = VD->getInit();
DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(I);
if (!DRE)
diff --git a/clang/test/OpenMP/collapse_extern_ref_crash.cpp b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
new file mode 100644
index 0000000000000..4cc1a56115c73
--- /dev/null
+++ b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
@@ -0,0 +1,17 @@
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 %s
+// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=51 %s
+
+// Verify no crash when collapsing a loop nest where the induction variable
+// is an extern reference type. PR/issue: null dereference in getInitLCDecl
+// when VarDecl::getDefinition() returns nullptr.
+
+extern int &dim;
+auto test() {
+ // expected-error at +1 {{expected-error for malformed collapse}}
+#pragma omp parallel for collapse(2)
+ for (int i = 0; i < dim; ++i) {
+ for (i = 0; i < 10; i++) {
+ int dummy;
+ }
+ }
+}
>From 2f48e43b7c6378862830683d1e662759e5eac1ac Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Thu, 11 Jun 2026 05:43:06 -0700
Subject: [PATCH 3/7] Fixed LIT test
---
clang/test/OpenMP/collapse_extern_ref_crash.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/test/OpenMP/collapse_extern_ref_crash.cpp b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
index 4cc1a56115c73..432fd91ef5852 100644
--- a/clang/test/OpenMP/collapse_extern_ref_crash.cpp
+++ b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
@@ -1,13 +1,14 @@
// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 %s
// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=51 %s
+// expected-no-diagnostics
+
// Verify no crash when collapsing a loop nest where the induction variable
// is an extern reference type. PR/issue: null dereference in getInitLCDecl
// when VarDecl::getDefinition() returns nullptr.
extern int &dim;
auto test() {
- // expected-error at +1 {{expected-error for malformed collapse}}
#pragma omp parallel for collapse(2)
for (int i = 0; i < dim; ++i) {
for (i = 0; i < 10; i++) {
>From c74662a207430405550212076e96b7e34c1d52b9 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Thu, 11 Jun 2026 12:02:49 -0700
Subject: [PATCH 4/7] Added diag
---
.../clang/Basic/DiagnosticSemaKinds.td | 2 +
clang/lib/Sema/SemaOpenMP.cpp | 46 +++++++++++++++----
.../test/OpenMP/collapse_extern_ref_crash.cpp | 31 ++++++++++---
3 files changed, 64 insertions(+), 15 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a3b575b7ee63a..9abbcb7fd2c3d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12255,6 +12255,8 @@ def err_omp_loop_cannot_use_stmt : Error<
"'%0' statement cannot be used in OpenMP for loop">;
def err_omp_loop_bad_collapse_var : Error<
"cannot use variable %1 in collapsed imperfectly-nested loop %select{init|condition|increment}0 statement">;
+def err_omp_loop_var_reused_in_collapsed_loop : Error<
+ "loop iteration variable %0 cannot be reused in a nested loop of a collapsed loop nest">;
def err_omp_simd_region_cannot_use_stmt : Error<
"'%0' statement cannot be used in OpenMP simd region">;
def warn_omp_loop_64_bit_var : Warning<
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index 79a0c396f787e..f94f5657a3998 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -8082,6 +8082,8 @@ class OpenMPIterationSpaceChecker {
SourceLocation ConditionLoc;
/// The set of variables declared within the (to be collapsed) loop nest.
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls;
+ /// The set of induction variables from outer collapsed loops.
+ const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars;
/// A source location for referring to loop init later.
SourceRange InitSrcRange;
/// A source location for referring to condition later.
@@ -8128,10 +8130,12 @@ class OpenMPIterationSpaceChecker {
OpenMPIterationSpaceChecker(
Sema &SemaRef, bool SupportsNonRectangular, DSAStackTy &Stack,
SourceLocation DefaultLoc,
- const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopDecls)
+ const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopDecls,
+ const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars)
: SemaRef(SemaRef), SupportsNonRectangular(SupportsNonRectangular),
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc),
- CollapsedLoopVarDecls(CollapsedLoopDecls) {}
+ CollapsedLoopVarDecls(CollapsedLoopDecls),
+ CollapsedLoopInductionVars(CollapsedLoopInductionVars) {}
/// Check init-expr for canonical loop form and save loop counter
/// variable - #Var and its initialization value - #LB.
bool checkAndSetInit(Stmt *S, bool EmitDiags = true);
@@ -8473,7 +8477,17 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
if (auto *ME = dyn_cast<MemberExpr>(getExprAsWritten(CED->getInit())))
return setLCDeclAndLB(ME->getMemberDecl(), ME, BO->getRHS(),
EmitDiags);
- return setLCDeclAndLB(DRE->getDecl(), DRE, BO->getRHS(), EmitDiags);
+ // Check if this variable is already used as an induction variable
+ // in an outer collapsed loop.
+ ValueDecl *LoopVar = DRE->getDecl();
+ if (!CollapsedLoopInductionVars.empty() &&
+ CollapsedLoopInductionVars.count(LoopVar) && EmitDiags) {
+ SemaRef.Diag(DRE->getLocation(),
+ diag::err_omp_loop_var_reused_in_collapsed_loop)
+ << LoopVar;
+ return true;
+ }
+ return setLCDeclAndLB(LoopVar, DRE, BO->getRHS(), EmitDiags);
}
if (auto *ME = dyn_cast<MemberExpr>(LHS)) {
if (ME->isArrow() &&
@@ -9435,7 +9449,8 @@ void SemaOpenMP::ActOnOpenMPLoopInitialization(SourceLocation ForLoc,
DSAStack->loopStart();
llvm::SmallPtrSet<const Decl *, 1> EmptyDeclSet;
OpenMPIterationSpaceChecker ISC(SemaRef, /*SupportsNonRectangular=*/true,
- *DSAStack, ForLoc, EmptyDeclSet);
+ *DSAStack, ForLoc, EmptyDeclSet,
+ EmptyDeclSet);
if (!ISC.checkAndSetInit(Init, /*EmitDiags=*/false)) {
if (ValueDecl *D = ISC.getLoopDecl()) {
auto *VD = dyn_cast<VarDecl>(D);
@@ -9535,7 +9550,8 @@ static bool checkOpenMPIterationSpace(
SemaOpenMP::VarsWithInheritedDSAType &VarsWithImplicitDSA,
llvm::MutableArrayRef<LoopIterationSpace> ResultIterSpaces,
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures,
- const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls) {
+ const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls,
+ const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars) {
bool SupportsNonRectangular = !isOpenMPLoopTransformationDirective(DKind);
// OpenMP [2.9.1, Canonical Loop Form]
// for (init-expr; test-expr; incr-expr) structured-block
@@ -9576,7 +9592,8 @@ static bool checkOpenMPIterationSpace(
OpenMPIterationSpaceChecker ISC(SemaRef, SupportsNonRectangular, DSA,
For ? For->getForLoc() : CXXFor->getForLoc(),
- CollapsedLoopVarDecls);
+ CollapsedLoopVarDecls,
+ CollapsedLoopInductionVars);
// Check init.
Stmt *Init = For ? For->getInit() : CXXFor->getBeginStmt();
@@ -9999,6 +10016,7 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
bool SupportsNonPerfectlyNested = (SemaRef.LangOpts.OpenMP >= 50) &&
!isOpenMPLoopTransformationDirective(DKind);
llvm::SmallPtrSet<const Decl *, 4> CollapsedLoopVarDecls;
+ llvm::SmallPtrSet<const Decl *, 4> CollapsedLoopInductionVars;
if (CollapseLoopCountExpr) {
// Found 'collapse' clause - calculate collapse number.
@@ -10047,14 +10065,24 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
SupportsNonPerfectlyNested, NumLoops,
[DKind, &SemaRef, &DSA, NumLoops, NestedLoopCount,
CollapseLoopCountExpr, OrderedLoopCountExpr, &VarsWithImplicitDSA,
- &IterSpaces, &Captures,
- &CollapsedLoopVarDecls](unsigned Cnt, Stmt *CurStmt) {
+ &IterSpaces, &Captures, &CollapsedLoopVarDecls,
+ &CollapsedLoopInductionVars](unsigned Cnt, Stmt *CurStmt) {
if (checkOpenMPIterationSpace(
DKind, CurStmt, SemaRef, DSA, Cnt, NestedLoopCount,
NumLoops, CollapseLoopCountExpr, OrderedLoopCountExpr,
VarsWithImplicitDSA, IterSpaces, Captures,
- CollapsedLoopVarDecls))
+ CollapsedLoopVarDecls, CollapsedLoopInductionVars))
return true;
+ // Add the current loop's induction variable to the set so nested
+ // loops can check against it.
+ if (Cnt < NestedLoopCount && IterSpaces[Cnt].CounterVar) {
+ if (auto *DRE =
+ dyn_cast<DeclRefExpr>(IterSpaces[Cnt].CounterVar)) {
+ if (ValueDecl *VD = DRE->getDecl()) {
+ CollapsedLoopInductionVars.insert(VD->getCanonicalDecl());
+ }
+ }
+ }
if (Cnt > 0 && Cnt >= NestedLoopCount &&
IterSpaces[Cnt].CounterVar) {
// Handle initialization of captured loop iterator variables.
diff --git a/clang/test/OpenMP/collapse_extern_ref_crash.cpp b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
index 432fd91ef5852..a8ffcae2fe262 100644
--- a/clang/test/OpenMP/collapse_extern_ref_crash.cpp
+++ b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
@@ -1,18 +1,37 @@
// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 %s
// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=51 %s
-// expected-no-diagnostics
+// This test verify two behaviors:
+// 1. No crash when the loop bound is an extern reference (fixed null pointer
+// dereference).
+// 2. Proper diagnostic when a nested loop reuses the outer loop's induction
+// variable
-// Verify no crash when collapsing a loop nest where the induction variable
-// is an extern reference type. PR/issue: null dereference in getInitLCDecl
-// when VarDecl::getDefinition() returns nullptr.
extern int &dim;
-auto test() {
+auto test1() {
#pragma omp parallel for collapse(2)
for (int i = 0; i < dim; ++i) {
+ // expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
for (i = 0; i < 10; i++) {
- int dummy;
+ }
+ }
+}
+
+auto test2() {
+#pragma omp parallel for collapse(2)
+ for (int i = 0; i < dim; ++i) {
+ for (int i = 0; i < 10; i++) {
+ }
+ }
+}
+
+int dim_storage = 10;
+int &dim = dim_storage;
+auto test3() {
+#pragma omp parallel for collapse(2)
+ for (int i = 0; i < dim; ++i) {
+ for (int j = 0; j < 10; j++) {
}
}
}
>From facedb15772056a092b487da0ed5b53bcaf6aa54 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Thu, 11 Jun 2026 14:03:33 -0700
Subject: [PATCH 5/7] Addressed review comments
---
clang/lib/Sema/SemaOpenMP.cpp | 3 ++-
clang/test/OpenMP/collapse_extern_ref_crash.cpp | 3 +++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index f94f5657a3998..918717b45b0d3 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -8481,7 +8481,8 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
// in an outer collapsed loop.
ValueDecl *LoopVar = DRE->getDecl();
if (!CollapsedLoopInductionVars.empty() &&
- CollapsedLoopInductionVars.count(LoopVar) && EmitDiags) {
+ CollapsedLoopInductionVars.count(LoopVar->getCanonicalDecl()) &&
+ EmitDiags) {
SemaRef.Diag(DRE->getLocation(),
diag::err_omp_loop_var_reused_in_collapsed_loop)
<< LoopVar;
diff --git a/clang/test/OpenMP/collapse_extern_ref_crash.cpp b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
index a8ffcae2fe262..52192e648044d 100644
--- a/clang/test/OpenMP/collapse_extern_ref_crash.cpp
+++ b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
@@ -14,6 +14,7 @@ auto test1() {
for (int i = 0; i < dim; ++i) {
// expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
for (i = 0; i < 10; i++) {
+ int dummy;
}
}
}
@@ -22,6 +23,7 @@ auto test2() {
#pragma omp parallel for collapse(2)
for (int i = 0; i < dim; ++i) {
for (int i = 0; i < 10; i++) {
+ int dummy;
}
}
}
@@ -32,6 +34,7 @@ auto test3() {
#pragma omp parallel for collapse(2)
for (int i = 0; i < dim; ++i) {
for (int j = 0; j < 10; j++) {
+ int dummy;
}
}
}
>From e673f026fcba9afeadab5b7dacf65731c1ab3700 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Fri, 12 Jun 2026 08:22:11 -0700
Subject: [PATCH 6/7] Added CXXOperatorCallExpr/OO_Equal test
---
clang/lib/Sema/SemaOpenMP.cpp | 13 +++++++-
.../test/OpenMP/collapse_extern_ref_crash.cpp | 28 ++++++++++++++++
clang/test/OpenMP/t1.cpp | 32 +++++++++++++++++++
3 files changed, 72 insertions(+), 1 deletion(-)
create mode 100644 clang/test/OpenMP/t1.cpp
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index 918717b45b0d3..d3c66072239fa 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -8523,7 +8523,18 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
if (auto *ME = dyn_cast<MemberExpr>(getExprAsWritten(CED->getInit())))
return setLCDeclAndLB(ME->getMemberDecl(), ME, BO->getRHS(),
EmitDiags);
- return setLCDeclAndLB(DRE->getDecl(), DRE, CE->getArg(1), EmitDiags);
+ // Check if this variable is already used as an induction variable
+ // in an outer collapsed loop.
+ ValueDecl *LoopVar = DRE->getDecl();
+ if (!CollapsedLoopInductionVars.empty() &&
+ CollapsedLoopInductionVars.count(LoopVar->getCanonicalDecl()) &&
+ EmitDiags) {
+ SemaRef.Diag(DRE->getLocation(),
+ diag::err_omp_loop_var_reused_in_collapsed_loop)
+ << LoopVar;
+ return true;
+ }
+ return setLCDeclAndLB(LoopVar, DRE, CE->getArg(1), EmitDiags);
}
if (auto *ME = dyn_cast<MemberExpr>(LHS)) {
if (ME->isArrow() &&
diff --git a/clang/test/OpenMP/collapse_extern_ref_crash.cpp b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
index 52192e648044d..8995efc43e59f 100644
--- a/clang/test/OpenMP/collapse_extern_ref_crash.cpp
+++ b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
@@ -38,3 +38,31 @@ auto test3() {
}
}
}
+
+struct Iterator {
+ int value;
+ Iterator& operator=(int v) { value = v; return *this; }
+ bool operator<(int n) const { return value < n; }
+ Iterator& operator++() { ++value; return *this; }
+};
+
+Iterator i;
+auto test4() {
+#pragma omp parallel for collapse(2)
+ for (i = 0; i < dim; ++i) {
+ // expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
+ for (i = 0; i < 10; ++i) {
+ int dummy;
+ }
+ }
+}
+
+auto test5() {
+#pragma omp parallel for collapse(2)
+ for (i = 0; i < dim; ++i) {
+ // expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
+ for (i = 0; i < 10; ++i) {
+ int dummy;
+ }
+ }
+}
diff --git a/clang/test/OpenMP/t1.cpp b/clang/test/OpenMP/t1.cpp
new file mode 100644
index 0000000000000..c50f878d52e7a
--- /dev/null
+++ b/clang/test/OpenMP/t1.cpp
@@ -0,0 +1,32 @@
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 %s
+// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=51 %s
+
+// Test 4: Reuse with overloaded operator= (should error)
+struct Iterator {
+ int value;
+ Iterator& operator=(int v) { value = v; return *this; }
+ bool operator<(int n) const { return value < n; }
+ Iterator& operator++() { ++value; return *this; }
+};
+
+Iterator i;
+extern int &dim;
+auto test4() {
+ #pragma omp parallel for collapse(2)
+ for (i = 0; i < dim; ++i) {
+ // expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
+ for (i = 0; i < 10; ++i) {
+ int dummy;
+ }
+ }
+ }
+
+auto test5() {
+#pragma omp parallel for collapse(2)
+ for (i = 0; i < dim; ++i) {
+ // expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
+ for (i = 0; i < 10; ++i) {
+ int dummy;
+ }
+ }
+}
>From 64c837becfe5f716f09c76a5a0807bff6a0bd859 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Sat, 13 Jun 2026 11:15:16 -0700
Subject: [PATCH 7/7] Addressed review comments
---
clang/lib/Sema/SemaOpenMP.cpp | 86 +++++++++++--------
.../test/OpenMP/collapse_extern_ref_crash.cpp | 39 +++++++++
clang/test/OpenMP/t1.cpp | 32 -------
3 files changed, 91 insertions(+), 66 deletions(-)
delete mode 100644 clang/test/OpenMP/t1.cpp
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index d3c66072239fa..82f74d455a6de 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -8083,7 +8083,7 @@ class OpenMPIterationSpaceChecker {
/// The set of variables declared within the (to be collapsed) loop nest.
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls;
/// The set of induction variables from outer collapsed loops.
- const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars;
+ llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars;
/// A source location for referring to loop init later.
SourceRange InitSrcRange;
/// A source location for referring to condition later.
@@ -8131,7 +8131,7 @@ class OpenMPIterationSpaceChecker {
Sema &SemaRef, bool SupportsNonRectangular, DSAStackTy &Stack,
SourceLocation DefaultLoc,
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopDecls,
- const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars)
+ llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars)
: SemaRef(SemaRef), SupportsNonRectangular(SupportsNonRectangular),
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc),
CollapsedLoopVarDecls(CollapsedLoopDecls),
@@ -8466,6 +8466,19 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
}
}
+ // Helper lambda to check if a loop variable is already used in an outer
+ // loop.
+ auto CheckLoopVarReuse = [&](ValueDecl *LoopVar, SourceLocation Loc) -> bool {
+ if (!CollapsedLoopInductionVars.empty() &&
+ CollapsedLoopInductionVars.count(LoopVar->getCanonicalDecl()) &&
+ EmitDiags) {
+ SemaRef.Diag(Loc, diag::err_omp_loop_var_reused_in_collapsed_loop)
+ << LoopVar;
+ return true;
+ }
+ return false;
+ };
+
InitSrcRange = S->getSourceRange();
if (Expr *E = dyn_cast<Expr>(S))
S = E->IgnoreParens();
@@ -8474,27 +8487,26 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
Expr *LHS = BO->getLHS()->IgnoreParens();
if (auto *DRE = dyn_cast<DeclRefExpr>(LHS)) {
if (auto *CED = dyn_cast<OMPCapturedExprDecl>(DRE->getDecl()))
- if (auto *ME = dyn_cast<MemberExpr>(getExprAsWritten(CED->getInit())))
- return setLCDeclAndLB(ME->getMemberDecl(), ME, BO->getRHS(),
- EmitDiags);
- // Check if this variable is already used as an induction variable
- // in an outer collapsed loop.
+ if (auto *ME =
+ dyn_cast<MemberExpr>(getExprAsWritten(CED->getInit()))) {
+ ValueDecl *LoopVar = ME->getMemberDecl();
+ if (CheckLoopVarReuse(LoopVar, DRE->getLocation()))
+ return true;
+ return setLCDeclAndLB(LoopVar, ME, BO->getRHS(), EmitDiags);
+ }
ValueDecl *LoopVar = DRE->getDecl();
- if (!CollapsedLoopInductionVars.empty() &&
- CollapsedLoopInductionVars.count(LoopVar->getCanonicalDecl()) &&
- EmitDiags) {
- SemaRef.Diag(DRE->getLocation(),
- diag::err_omp_loop_var_reused_in_collapsed_loop)
- << LoopVar;
+ if (CheckLoopVarReuse(LoopVar, DRE->getLocation()))
return true;
- }
return setLCDeclAndLB(LoopVar, DRE, BO->getRHS(), EmitDiags);
}
if (auto *ME = dyn_cast<MemberExpr>(LHS)) {
if (ME->isArrow() &&
- isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()))
- return setLCDeclAndLB(ME->getMemberDecl(), ME, BO->getRHS(),
- EmitDiags);
+ isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts())) {
+ ValueDecl *LoopVar = ME->getMemberDecl();
+ if (CheckLoopVarReuse(LoopVar, LHS->getBeginLoc()))
+ return true;
+ return setLCDeclAndLB(LoopVar, ME, BO->getRHS(), EmitDiags);
+ }
}
}
} else if (auto *DS = dyn_cast<DeclStmt>(S)) {
@@ -8506,6 +8518,8 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
SemaRef.Diag(S->getBeginLoc(),
diag::ext_omp_loop_not_canonical_init)
<< S->getSourceRange();
+ if (CheckLoopVarReuse(Var, Var->getLocation()))
+ return true;
return setLCDeclAndLB(
Var,
buildDeclRefExpr(SemaRef, Var,
@@ -8520,27 +8534,26 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
Expr *LHS = CE->getArg(0);
if (auto *DRE = dyn_cast<DeclRefExpr>(LHS)) {
if (auto *CED = dyn_cast<OMPCapturedExprDecl>(DRE->getDecl()))
- if (auto *ME = dyn_cast<MemberExpr>(getExprAsWritten(CED->getInit())))
- return setLCDeclAndLB(ME->getMemberDecl(), ME, BO->getRHS(),
- EmitDiags);
- // Check if this variable is already used as an induction variable
- // in an outer collapsed loop.
+ if (auto *ME =
+ dyn_cast<MemberExpr>(getExprAsWritten(CED->getInit()))) {
+ ValueDecl *LoopVar = ME->getMemberDecl();
+ if (CheckLoopVarReuse(LoopVar, DRE->getLocation()))
+ return true;
+ return setLCDeclAndLB(LoopVar, ME, CE->getArg(1), EmitDiags);
+ }
ValueDecl *LoopVar = DRE->getDecl();
- if (!CollapsedLoopInductionVars.empty() &&
- CollapsedLoopInductionVars.count(LoopVar->getCanonicalDecl()) &&
- EmitDiags) {
- SemaRef.Diag(DRE->getLocation(),
- diag::err_omp_loop_var_reused_in_collapsed_loop)
- << LoopVar;
+ if (CheckLoopVarReuse(LoopVar, DRE->getLocation()))
return true;
- }
return setLCDeclAndLB(LoopVar, DRE, CE->getArg(1), EmitDiags);
}
if (auto *ME = dyn_cast<MemberExpr>(LHS)) {
if (ME->isArrow() &&
- isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()))
- return setLCDeclAndLB(ME->getMemberDecl(), ME, BO->getRHS(),
- EmitDiags);
+ isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts())) {
+ ValueDecl *LoopVar = ME->getMemberDecl();
+ if (CheckLoopVarReuse(LoopVar, LHS->getBeginLoc()))
+ return true;
+ return setLCDeclAndLB(LoopVar, ME, CE->getArg(1), EmitDiags);
+ }
}
}
}
@@ -9563,7 +9576,7 @@ static bool checkOpenMPIterationSpace(
llvm::MutableArrayRef<LoopIterationSpace> ResultIterSpaces,
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures,
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls,
- const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars) {
+ llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopInductionVars) {
bool SupportsNonRectangular = !isOpenMPLoopTransformationDirective(DKind);
// OpenMP [2.9.1, Canonical Loop Form]
// for (init-expr; test-expr; incr-expr) structured-block
@@ -9769,7 +9782,12 @@ static bool checkOpenMPIterationSpace(
DoacrossC->setLoopData(CurrentNestedLoopCount, CntValue);
}
}
-
+ // Record the loop induction variable for nested loop reuse checking.
+ if (CurrentNestedLoopCount < NestedLoopCount && !HasErrors) {
+ if (const ValueDecl *LCDecl = ISC.getLoopDecl()) {
+ CollapsedLoopInductionVars.insert(LCDecl->getCanonicalDecl());
+ }
+ }
return HasErrors;
}
diff --git a/clang/test/OpenMP/collapse_extern_ref_crash.cpp b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
index 8995efc43e59f..bafc1e124042e 100644
--- a/clang/test/OpenMP/collapse_extern_ref_crash.cpp
+++ b/clang/test/OpenMP/collapse_extern_ref_crash.cpp
@@ -66,3 +66,42 @@ auto test5() {
}
}
}
+
+struct S {
+ int x;
+ void test6() {
+#pragma omp parallel for collapse(2)
+ for (this->x = 0; this->x < 10; ++this->x) {
+ // expected-error at +1{{loop iteration variable 'x' cannot be reused in a nested loop of a collapsed loop nest}}
+ for (this->x = 0; this->x < 10; ++this->x) {
+ int dummy;
+ }
+ }
+ }
+};
+
+struct S2 {
+ int x;
+ int y;
+ void test7() {
+#pragma omp parallel for collapse(2)
+ for (this->x = 0; this->x < 10; ++this->x) {
+ for (this->y = 0; this->y < 10; ++this->y) {
+ int dummy;
+ }
+ }
+ }
+};
+
+struct S3 {
+ Iterator x;
+ void test8() {
+#pragma omp parallel for collapse(2)
+ for (this->x = 0; this->x < 10; ++this->x) {
+ // expected-error at +1{{loop iteration variable 'x' cannot be reused in a nested loop of a collapsed loop nest}}
+ for (this->x = 0; this->x < 10; ++this->x) {
+ int dummy;
+ }
+ }
+ }
+};
diff --git a/clang/test/OpenMP/t1.cpp b/clang/test/OpenMP/t1.cpp
deleted file mode 100644
index c50f878d52e7a..0000000000000
--- a/clang/test/OpenMP/t1.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 %s
-// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=51 %s
-
-// Test 4: Reuse with overloaded operator= (should error)
-struct Iterator {
- int value;
- Iterator& operator=(int v) { value = v; return *this; }
- bool operator<(int n) const { return value < n; }
- Iterator& operator++() { ++value; return *this; }
-};
-
-Iterator i;
-extern int &dim;
-auto test4() {
- #pragma omp parallel for collapse(2)
- for (i = 0; i < dim; ++i) {
- // expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
- for (i = 0; i < 10; ++i) {
- int dummy;
- }
- }
- }
-
-auto test5() {
-#pragma omp parallel for collapse(2)
- for (i = 0; i < dim; ++i) {
- // expected-error at +1{{loop iteration variable 'i' cannot be reused in a nested loop of a collapsed loop nest}}
- for (i = 0; i < 10; ++i) {
- int dummy;
- }
- }
-}
More information about the cfe-commits
mailing list