See attached patch.

Regression tested on x86_64.

OK for mainline.

Regards,

Jerry
---
fortran: [PR126116] Internal file I/O corrupts NEWUNIT state

    Before this fix, Performing an internal file I/O
    (such as WRITE to a character variable) incorrectly corrupts
    the internal unit management table in libgfortran.

    This patch adds a bool to the gfc_unit structure to allow
    tracking whether a gfc_unit was created for purposes of
    internal I/O to avoid the breakage.

            PR libfortran/126116

    libgfortran/ChangeLog:

            * io/io.h (struct gfc_unit): Add bool for tracking.
            Fix some white space problems.
            * io/unit.c (close_unit_1): Add a declaration for this
            function so it can be called before it has been defined.
            Use the new bool.
            (is_internal_reserved): New helper function. Returns true
            only if the unit is not null and the internal unit kind is zero
            meaning its not a character string.
            (find_unit): Use the new helper function
            (find_or_create_unit): Use the new helper.
            (get_unit): Set the new bool.
            (close_unit): Update the close_unit_1 argument list.
            (close_units): Likewise.

    gcc/testsuite/ChangeLog:

            * gfortran.dg/pr126116.f90: New test.
commit e9078a638c1bb7f54f702db22a231e3adc71efc6
Author: Jerry DeLisle <[email protected]>
Date:   Wed Jul 8 10:22:04 2026 -0700

    fortran: [PR126116] Internal file I/O corrupts NEWUNIT state
    
    Before this fix, Performing an internal file I/O
    (such as WRITE to a character variable) incorrectly corrupts
    the internal unit management table in libgfortran.
    
    This patch adds a bool to the gfc_unit structure to allow
    tracking whether a gfc_unit was created for purposes of
    internal I/O to avoid the breakage.
    
            PR libfortran/126116
    
    libgfortran/ChangeLog:
    
            * io/io.h (struct gfc_unit): Add bool for tracking.
            Fix some white space problems.
            * io/unit.c (close_unit_1): Add a declaration for this
            function so it can be called before it has been defined.
            Use the new bool.
            (is_internal_reserved): New helper function. Returns true
            only if the unit is not null and the internal unit kind is zero
            meaning its not a character string.
            (find_unit): Use the new helper function
            (find_or_create_unit): Use the new helper.
            (get_unit): Set the new bool.
            (close_unit): Update the close_unit_1 argument list.
            (close_units): Likewise.
    
    gcc/testsuite/ChangeLog:
    
            * gfortran.dg/pr126116.f90: New test.

diff --git a/gcc/testsuite/gfortran.dg/pr126116.f90 b/gcc/testsuite/gfortran.dg/pr126116.f90
new file mode 100644
index 00000000000..420584e07df
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/pr126116.f90
@@ -0,0 +1,19 @@
+! { dg-do run }
+
+program check
+    implicit none
+    integer :: u
+    character(50) :: chr
+
+    ! 1. Correctly generate a negative unit via NEWUNIT
+    open(file="test.txt", newunit=u) ! u gets -10
+    close(u) ! u (-10) is now closed and free
+
+    ! 2. Internal I/O corrupts the state of unit -10
+    write(chr, *) "test"
+
+    ! 3. Redundant CLOSE on the valid unit 'u'.
+    ! This should be a safe no-op according to the standard,
+    ! but it triggers the runtime error/segfault!
+    close(u)
+end program
diff --git a/libgfortran/io/io.h b/libgfortran/io/io.h
index ade0bef32d5..ecf1b779a48 100644
--- a/libgfortran/io/io.h
+++ b/libgfortran/io/io.h
@@ -535,7 +535,7 @@ typedef struct st_parameter_dt
 	     character string is being read so don't use commas to shorten a
 	     formatted field width.  */
 	  unsigned sf_read_comma : 1;
-	  /* A namelist specific flag used to enable reading input from 
+	  /* A namelist specific flag used to enable reading input from
 	     line_buffer for logical reads.  */
 	  unsigned line_buffer_enabled : 1;
 	  /* An internal unit specific flag used to identify that the associated
@@ -649,7 +649,7 @@ typedef struct gfc_unit
 {
   int unit_number;
   stream *s;
-  
+
   /* Treap links.  */
   struct gfc_unit *left, *right;
   int priority;
@@ -709,10 +709,10 @@ typedef struct gfc_unit
 
   /* The format hash table.  */
   struct format_hash_entry format_hash_table[FORMAT_HASH_SIZE];
-  
+
   /* Formatting buffer.  */
   struct fbuf *fbuf;
-  
+
   /* Function pointer, points to list_read worker functions.  */
   int (*next_char_fn_ptr) (st_parameter_dt *);
   void (*push_char_fn_ptr) (st_parameter_dt *, int);
@@ -723,6 +723,9 @@ typedef struct gfc_unit
   gfc_array_char *string_unit_desc;
   int internal_unit_kind;
 
+  /* Track when a gfc_unit is reserved for an internal unit.  */
+  bool internal_reserved;
+
   /* DTIO Parent/Child procedure, 0 = parent, >0 = child level.  */
   int child_dtio;
 
@@ -1023,7 +1026,7 @@ predec_waiting_locked (gfc_unit *u)
 
      if (predec_waiting_locked (u) == 0)
          // destroy u
-	 
+
      could be further optimized by making this be an __ATOMIC_RELEASE,
      and then inserting a
 
diff --git a/libgfortran/io/unit.c b/libgfortran/io/unit.c
index 84acfbdff98..ed40fd58b80 100644
--- a/libgfortran/io/unit.c
+++ b/libgfortran/io/unit.c
@@ -479,17 +479,45 @@ found:
 }
 
 
+static int close_unit_1 (gfc_unit *, int, bool);
+
+
+/*  Helper to check if an internal unit is reserved.  */
+
+static inline bool
+is_internal_reserved (gfc_unit *u)
+{
+  return u->internal_reserved && u->internal_unit_kind == 0;
+}
+
+
 gfc_unit *
 find_unit (int n)
 {
-  return get_gfc_unit (n, 0);
+  gfc_unit *u = get_gfc_unit (n, 0);
+
+  if (u != NULL && is_internal_reserved (u))
+    {
+      unlock_unit (u);
+      return NULL;
+    }
+
+  return u;
 }
 
 
 gfc_unit *
 find_or_create_unit (int n)
 {
-  return get_gfc_unit (n, 1);
+  gfc_unit *u = get_gfc_unit (n, 1);
+
+  if (u != NULL && is_internal_reserved (u))
+    {
+      close_unit_1 (u, 0, false);
+      u = get_gfc_unit (n, 1);
+    }
+
+  return u;
 }
 
 
@@ -625,6 +653,7 @@ get_unit (st_parameter_dt *dtp, int do_create)
       dtp->u.p.unit_is_internal = 1;
       dtp->common.unit = newunit_alloc ();
       unit = get_gfc_unit (dtp->common.unit, do_create);
+      unit->internal_reserved = true;
       set_internal_unit (dtp, unit, kind);
       fbuf_init (unit, 128);
       return unit;
@@ -787,7 +816,7 @@ init_units (void)
 
 
 static int
-close_unit_1 (gfc_unit *u, int locked)
+close_unit_1 (gfc_unit *u, int locked, bool free_newunit)
 {
   int i, rc;
 
@@ -817,7 +846,7 @@ close_unit_1 (gfc_unit *u, int locked)
   free_format_hash_table (u);
   fbuf_destroy (u);
 
-  if (u->unit_number <= NEWUNIT_START)
+  if (free_newunit && u->unit_number <= NEWUNIT_START)
     newunit_free (u->unit_number);
 
   if (!locked)
@@ -853,7 +882,7 @@ unlock_unit (gfc_unit *u)
 int
 close_unit (gfc_unit *u)
 {
-  return close_unit_1 (u, 0);
+  return close_unit_1 (u, 0, true);
 }
 
 
@@ -869,7 +898,7 @@ close_units (void)
 {
   WRLOCK (&unit_rwlock);
   while (unit_root != NULL)
-    close_unit_1 (unit_root, 1);
+    close_unit_1 (unit_root, 1, true);
   RWUNLOCK (&unit_rwlock);
 
   free (newunits);

Reply via email to