[flang-commits] [flang] [flang][Semantics] Break recursion on illegal recursive type in I/O check (PR #194284)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Sun Apr 26 20:46:21 PDT 2026


https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/194284

>From 48eb2606a942dec00e1f88cac9964ea0418d342b Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:15:06 -0400
Subject: [PATCH 1/6] [flang][Semantics] Break recursion on illegal recursive
 type in I/O check

When an illegal recursive derived type (a non-POINTER/non-ALLOCATABLE
component whose type is the enclosing type itself, prohibited by F2023
C749) is used in an I/O list, the offending component is left in the
symbol table after the error is diagnosed. The component-walking helpers
FindUnsafeIoDirectComponent and FindInaccessibleComponent then recurse
through it forever and the compiler loops.

Track the derived types currently on the recursion path in an
inProgress set and bail out when a type is reached again. The set is
threaded through the recursive call and seeded by a thin wrapper at
each entry point so call sites are unchanged.

Fixes #192387

Co-authored-by: AI
---
 flang/lib/Semantics/check-io.cpp | 37 ++++++++++++++++++++++++++------
 flang/test/Semantics/io12.f90    | 12 +++++++++++
 2 files changed, 43 insertions(+), 6 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 46abd3d298d02..0073f1fc6c505 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -15,6 +15,7 @@
 #include "flang/Parser/tools.h"
 #include "flang/Semantics/expression.h"
 #include "flang/Semantics/tools.h"
+#include <set>
 #include <unordered_map>
 
 namespace Fortran::semantics {
@@ -1118,12 +1119,19 @@ void IoChecker::CheckForUselessIomsg() const {
 
 // Seeks out an allocatable or pointer ultimate component that is not
 // nested in a nonallocatable/nonpointer component with a specific
-// defined I/O procedure.
+// defined I/O procedure.  The inProgress set tracks derived types
+// currently on the recursion path to break cycles caused by an illegal
+// recursive type definition (F2023 C749) that has already been
+// diagnosed but whose offending component is still in the symbol table.
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
-    const DerivedTypeSpec &derived, const Scope &scope) {
+    const DerivedTypeSpec &derived, const Scope &scope,
+    std::set<const Symbol *> &inProgress) {
   if (HasDefinedIo(which, derived, &scope)) {
     return nullptr;
   }
+  if (!inProgress.insert(&derived.typeSymbol()).second) {
+    return nullptr;
+  }
   if (const Scope * dtScope{derived.scope()}) {
     for (const auto &pair : *dtScope) {
       const Symbol &symbol{*pair.second};
@@ -1136,7 +1144,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
             const DerivedTypeSpec &componentDerived{type->derivedTypeSpec()};
             if (const Symbol *
                 bad{FindUnsafeIoDirectComponent(
-                    which, componentDerived, scope)}) {
+                    which, componentDerived, scope, inProgress)}) {
               return bad;
             }
           }
@@ -1147,11 +1155,22 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
   return nullptr;
 }
 
+static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
+    const DerivedTypeSpec &derived, const Scope &scope) {
+  std::set<const Symbol *> inProgress;
+  return FindUnsafeIoDirectComponent(which, derived, scope, inProgress);
+}
+
 // For a type that does not have a defined I/O subroutine, finds a direct
 // component that is a witness to an accessibility violation outside the module
-// in which the type was defined.
+// in which the type was defined.  See FindUnsafeIoDirectComponent for the
+// purpose of inProgress.
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
-    const DerivedTypeSpec &derived, const Scope &scope) {
+    const DerivedTypeSpec &derived, const Scope &scope,
+    std::set<const Symbol *> &inProgress) {
+  if (!inProgress.insert(&derived.typeSymbol()).second) {
+    return nullptr;
+  }
   if (const Scope * dtScope{derived.scope()}) {
     if (const Scope * module{FindModuleContaining(*dtScope)}) {
       for (const auto &pair : *dtScope) {
@@ -1179,7 +1198,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
           if (componentDerived) {
             if (const Symbol *
                 bad{FindInaccessibleComponent(
-                    which, *componentDerived, scope)}) {
+                    which, *componentDerived, scope, inProgress)}) {
               return bad;
             }
           }
@@ -1190,6 +1209,12 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
   return nullptr;
 }
 
+static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
+    const DerivedTypeSpec &derived, const Scope &scope) {
+  std::set<const Symbol *> inProgress;
+  return FindInaccessibleComponent(which, derived, scope, inProgress);
+}
+
 // Fortran 2018, 12.6.3 paragraphs 5 & 7
 parser::Message *IoChecker::CheckForBadIoType(const evaluate::DynamicType &type,
     common::DefinedIo which, parser::CharBlock where) const {
diff --git a/flang/test/Semantics/io12.f90 b/flang/test/Semantics/io12.f90
index 474b07c044512..92d0f7f2741be 100644
--- a/flang/test/Semantics/io12.f90
+++ b/flang/test/Semantics/io12.f90
@@ -74,3 +74,15 @@ subroutine test4(u)
   end subroutine
 end module
 
+! Regression test: an illegal recursive derived-type component used to cause
+! infinite recursion in FindUnsafeIoDirectComponent when the object appeared
+! in an I/O list (https://github.com/llvm/llvm-project/issues/192387).
+subroutine test_recursive_io
+  type t1
+    !ERROR: Recursive use of the derived type requires POINTER or ALLOCATABLE
+    type(t1) :: b
+  end type t1
+  type(t1) :: obj
+  print *, obj
+end subroutine
+

>From 4ae6f936d47afccfd7db89c59395798ddc058d93 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:17:51 -0400
Subject: [PATCH 2/6] clang-format

---
 flang/lib/Semantics/check-io.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 0073f1fc6c505..27851d1798ead 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -1142,8 +1142,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
         if (const DeclTypeSpec * type{details->type()}) {
           if (type->category() == DeclTypeSpec::Category::TypeDerived) {
             const DerivedTypeSpec &componentDerived{type->derivedTypeSpec()};
-            if (const Symbol *
-                bad{FindUnsafeIoDirectComponent(
+            if (const Symbol *bad{FindUnsafeIoDirectComponent(
                     which, componentDerived, scope, inProgress)}) {
               return bad;
             }
@@ -1196,8 +1195,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
             }
           }
           if (componentDerived) {
-            if (const Symbol *
-                bad{FindInaccessibleComponent(
+            if (const Symbol *bad{FindInaccessibleComponent(
                     which, *componentDerived, scope, inProgress)}) {
               return bad;
             }

>From 3e4bf01c8d1a73aa0f807def9d2f7dc5e0482cd2 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:22:22 -0400
Subject: [PATCH 3/6] [flang][Semantics] Use std::unordered_set for inProgress

Replace std::set<const Symbol *> with std::unordered_set<const Symbol *>
for the recursion-tracking set. Order is irrelevant for membership
checking, and unordered_set gives O(1) lookup/insert vs O(log n) for
std::set.

Co-authored-by: AI
---
 flang/lib/Semantics/check-io.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 27851d1798ead..e7bb41e5b1631 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -15,8 +15,8 @@
 #include "flang/Parser/tools.h"
 #include "flang/Semantics/expression.h"
 #include "flang/Semantics/tools.h"
-#include <set>
 #include <unordered_map>
+#include <unordered_set>
 
 namespace Fortran::semantics {
 
@@ -1125,7 +1125,7 @@ void IoChecker::CheckForUselessIomsg() const {
 // diagnosed but whose offending component is still in the symbol table.
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::set<const Symbol *> &inProgress) {
+    std::unordered_set<const Symbol *> &inProgress) {
   if (HasDefinedIo(which, derived, &scope)) {
     return nullptr;
   }
@@ -1156,7 +1156,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::set<const Symbol *> inProgress;
+  std::unordered_set<const Symbol *> inProgress;
   return FindUnsafeIoDirectComponent(which, derived, scope, inProgress);
 }
 
@@ -1166,7 +1166,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 // purpose of inProgress.
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::set<const Symbol *> &inProgress) {
+    std::unordered_set<const Symbol *> &inProgress) {
   if (!inProgress.insert(&derived.typeSymbol()).second) {
     return nullptr;
   }
@@ -1209,7 +1209,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
 
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::set<const Symbol *> inProgress;
+  std::unordered_set<const Symbol *> inProgress;
   return FindInaccessibleComponent(which, derived, scope, inProgress);
 }
 

>From f8edf8b37bba614bea0216d9c809db1d67375757 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:27:49 -0400
Subject: [PATCH 4/6] [flang][Semantics] Introduce VisitedSymbolSet alias

Localize the container type used by FindUnsafeIoDirectComponent and
FindInaccessibleComponent for tracking derived types on the recursion
path. With the alias in place the container can be swapped without
touching the recursive functions or their wrappers.

Co-authored-by: AI
---
 flang/lib/Semantics/check-io.cpp | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index e7bb41e5b1631..8ba0defa9ba6f 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -1117,6 +1117,11 @@ void IoChecker::CheckForUselessIomsg() const {
   }
 }
 
+// Set of derived-type symbols already visited on the current recursion
+// path of the component walks below.  Localized here so the underlying
+// container can be swapped without touching call sites.
+using VisitedSymbolSet = std::unordered_set<const Symbol *>;
+
 // Seeks out an allocatable or pointer ultimate component that is not
 // nested in a nonallocatable/nonpointer component with a specific
 // defined I/O procedure.  The inProgress set tracks derived types
@@ -1125,7 +1130,7 @@ void IoChecker::CheckForUselessIomsg() const {
 // diagnosed but whose offending component is still in the symbol table.
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::unordered_set<const Symbol *> &inProgress) {
+    VisitedSymbolSet &inProgress) {
   if (HasDefinedIo(which, derived, &scope)) {
     return nullptr;
   }
@@ -1156,7 +1161,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::unordered_set<const Symbol *> inProgress;
+  VisitedSymbolSet inProgress;
   return FindUnsafeIoDirectComponent(which, derived, scope, inProgress);
 }
 
@@ -1166,7 +1171,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 // purpose of inProgress.
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::unordered_set<const Symbol *> &inProgress) {
+    VisitedSymbolSet &inProgress) {
   if (!inProgress.insert(&derived.typeSymbol()).second) {
     return nullptr;
   }
@@ -1209,7 +1214,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
 
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::unordered_set<const Symbol *> inProgress;
+  VisitedSymbolSet inProgress;
   return FindInaccessibleComponent(which, derived, scope, inProgress);
 }
 

>From b171ff6e659e4f089bc18777ca3c046b724b8ae5 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:29:39 -0400
Subject: [PATCH 5/6] Updated comment

---
 flang/lib/Semantics/check-io.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 8ba0defa9ba6f..3c66380548d0d 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -1118,8 +1118,7 @@ void IoChecker::CheckForUselessIomsg() const {
 }
 
 // Set of derived-type symbols already visited on the current recursion
-// path of the component walks below.  Localized here so the underlying
-// container can be swapped without touching call sites.
+// path of the component walks below.
 using VisitedSymbolSet = std::unordered_set<const Symbol *>;
 
 // Seeks out an allocatable or pointer ultimate component that is not

>From b21598773bf15d5a032a565f85a752b8844c884e Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:46:09 -0400
Subject: [PATCH 6/6] Updated test comment

---
 flang/test/Semantics/io12.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/test/Semantics/io12.f90 b/flang/test/Semantics/io12.f90
index 92d0f7f2741be..be272d6b47a82 100644
--- a/flang/test/Semantics/io12.f90
+++ b/flang/test/Semantics/io12.f90
@@ -76,7 +76,7 @@ subroutine test4(u)
 
 ! Regression test: an illegal recursive derived-type component used to cause
 ! infinite recursion in FindUnsafeIoDirectComponent when the object appeared
-! in an I/O list (https://github.com/llvm/llvm-project/issues/192387).
+! in an I/O list (issue #192387).
 subroutine test_recursive_io
   type t1
     !ERROR: Recursive use of the derived type requires POINTER or ALLOCATABLE



More information about the flang-commits mailing list