See attached patch which cleans up I hope the last of the tabbing issues in formatted output. I renamed the existing test case and created a new one to test several variations.

Comments appreciated.

Regression tested on X86_64.

Ok for mainline? No plans to backport unless someone thinks its needed.

Best regards,

Jerry

---

When an X or TR edit descriptor advances the file position into
content that has already been written (e.g. after a backward T or
TL tab), formatted_transfer_scalar_write either corrupted the
pending_spaces accounting or overwrote the existing characters with
blanks.

Fix this by detecting the case where pending_spaces is negative and
skips is positive (the advance falls within already-written content)
and using write_x (skips, 0) to move the file position forward
without overwriting.  Also, for stream I/O, compute bytes_used from
fbuf->pos (the current write position) rather than fbuf->act (the
high-water mark), since the two diverge after a backward tab followed
by a partial overwrite, which previously prevented this case from
being detected for stream units.

Assisted by: Claude Sonnet 4.6

        PR libfortran/114618

libgfortran/ChangeLog:

        * io/transfer.c (formatted_transfer_scalar_write): Use
        fbuf->pos rather than fbuf->act for bytes_used with stream
        I/O.  When pending_spaces is negative and skips is positive,
        advance the position with write_x (skips, 0) instead of
        overwriting existing content.

gcc/testsuite/ChangeLog:

        * gfortran.dg/pr114618.f90: Move to...
        * gfortran.dg/fmt_t_10.f90: ...here.
        * gfortran.dg/fmt_t_11.f90: New test.
---
From c01c6cc8ae57f13bbd8185144760b24d73db7e84 Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Sun, 14 Jun 2026 21:28:32 -0700
Subject: [PATCH] Fortran: Fix FMT_X/TR output into already-written content
 [PR114618]

When an X or TR edit descriptor advances the file position into
content that has already been written (e.g. after a backward T or
TL tab), formatted_transfer_scalar_write either corrupted the
pending_spaces accounting or overwrote the existing characters with
blanks.

Fix this by detecting the case where pending_spaces is negative and
skips is positive (the advance falls within already-written content)
and using write_x (skips, 0) to move the file position forward
without overwriting.  Also, for stream I/O, compute bytes_used from
fbuf->pos (the current write position) rather than fbuf->act (the
high-water mark), since the two diverge after a backward tab followed
by a partial overwrite, which previously prevented this case from
being detected for stream units.

Assisted by: Claude Sonnet 4.6

	PR libfortran/114618

libgfortran/ChangeLog:

	* io/transfer.c (formatted_transfer_scalar_write): Use
	fbuf->pos rather than fbuf->act for bytes_used with stream
	I/O.  When pending_spaces is negative and skips is positive,
	advance the position with write_x (skips, 0) instead of
	overwriting existing content.

gcc/testsuite/ChangeLog:

	* gfortran.dg/pr114618.f90: Move to...
	* gfortran.dg/fmt_t_10.f90: ...here.
	* gfortran.dg/fmt_t_11.f90: New test.
---
 .../{pr114618.f90 => fmt_t_10.f90}            |  4 +-
 gcc/testsuite/gfortran.dg/fmt_t_11.f90        | 61 +++++++++++++++++++
 libgfortran/io/transfer.c                     | 19 +++++-
 3 files changed, 81 insertions(+), 3 deletions(-)
 rename gcc/testsuite/gfortran.dg/{pr114618.f90 => fmt_t_10.f90} (94%)
 create mode 100644 gcc/testsuite/gfortran.dg/fmt_t_11.f90

diff --git a/gcc/testsuite/gfortran.dg/pr114618.f90 b/gcc/testsuite/gfortran.dg/fmt_t_10.f90
similarity index 94%
rename from gcc/testsuite/gfortran.dg/pr114618.f90
rename to gcc/testsuite/gfortran.dg/fmt_t_10.f90
index 835597b8513..1f714d24d4e 100644
--- a/gcc/testsuite/gfortran.dg/pr114618.f90
+++ b/gcc/testsuite/gfortran.dg/fmt_t_10.f90
@@ -1,7 +1,7 @@
 ! { dg-do run }
 ! PR114618 Format produces incorrect output when contains 1x, ok when uses " " 
 ! aside: Before patch output1 is garbage.
-program pr114618
+program fmt_t_10
    implicit none
    integer, parameter :: wp = kind(0d0)
    real(kind=wp) :: pi  = 3.14159265358979323846264338_wp
@@ -12,4 +12,4 @@ program pr114618
    write (output2, fmt2) 'RADIX', radix(pi)
    if (output1 /= 'RADIX.............. 2') stop 1
    if (output2 /= 'RADIX ............. 2') stop 2
-end program pr114618
+end program fmt_t_10
diff --git a/gcc/testsuite/gfortran.dg/fmt_t_11.f90 b/gcc/testsuite/gfortran.dg/fmt_t_11.f90
new file mode 100644
index 00000000000..7c4300e616b
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/fmt_t_11.f90
@@ -0,0 +1,61 @@
+! { dg-do run }
+! PR libfortran/114618 - Test TL, TR, and T specifiers in combination for
+! sequential and stream formatted file I/O.
+program fmt_t_11
+  implicit none
+  integer :: u
+  character(len=20) :: line
+
+  ! --- Sequential formatted file tests ---
+
+  ! T backward then TR forward into already-written content
+  open (newunit=u, status='scratch', form='formatted')
+  write (u, '(10("X"),t3,"AB",tr3,"C")')
+  rewind (u)
+  read (u, '(a)') line
+  close (u)
+  if (line(1:10) /= 'XXABXXXCXX') stop 1
+
+  ! TL backward then TR forward into already-written content
+  open (newunit=u, status='scratch', form='formatted')
+  write (u, '("HELLO123",tl5,"AB",tr3,"Z")')
+  rewind (u)
+  read (u, '(a)') line
+  close (u)
+  if (line(1:9) /= 'HELAB123Z') stop 2
+
+  ! T, TL, and TR all combined in one format
+  open (newunit=u, status='scratch', form='formatted')
+  write (u, '(t7,"*",tl5,"AB",tr3,"Z")')
+  rewind (u)
+  read (u, '(a)') line
+  close (u)
+  if (line(1:8) /= '  AB  *Z') stop 3
+
+  ! --- Stream formatted file tests ---
+
+  ! T backward then TR forward into already-written content
+  open (newunit=u, status='scratch', form='formatted', access='stream')
+  write (u, '(10("X"),t3,"AB",tr3,"C")')
+  rewind (u)
+  read (u, '(a)') line
+  close (u)
+  if (line(1:10) /= 'XXABXXXCXX') stop 4
+
+  ! TL backward then TR forward into already-written content
+  open (newunit=u, status='scratch', form='formatted', access='stream')
+  write (u, '("HELLO123",tl5,"AB",tr3,"Z")')
+  rewind (u)
+  read (u, '(a)') line
+  close (u)
+  if (line(1:9) /= 'HELAB123Z') stop 5
+
+  ! T, TL, and TR all combined in one format
+  open (newunit=u, status='scratch', form='formatted', access='stream')
+  write (u, '(t7,"*",tl5,"AB",tr3,"Z")')
+  rewind (u)
+  read (u, '(a)') line
+  close (u)
+  if (line(1:8) /= '  AB  *Z') stop 6
+
+end program fmt_t_11
diff --git a/libgfortran/io/transfer.c b/libgfortran/io/transfer.c
index d88cd810529..f15ca04ed30 100644
--- a/libgfortran/io/transfer.c
+++ b/libgfortran/io/transfer.c
@@ -2162,7 +2162,7 @@ formatted_transfer_scalar_write (st_parameter_dt *dtp, bt type, void *p, int kin
 	}
 
       if (is_stream_io(dtp))
-	bytes_used = dtp->u.p.current_unit->fbuf->act;
+	bytes_used = dtp->u.p.current_unit->fbuf->pos;
       else
 	bytes_used = dtp->u.p.current_unit->recl
 		    - dtp->u.p.current_unit->bytes_left;
@@ -2420,6 +2420,23 @@ formatted_transfer_scalar_write (st_parameter_dt *dtp, bt type, void *p, int kin
 	  dtp->u.p.skips += f->u.n;
 	  tab_pos = bytes_used + dtp->u.p.skips - 1;
 	  dtp->u.p.pending_spaces = tab_pos - dtp->u.p.max_pos + 1;
+	  if (dtp->u.p.pending_spaces < 0 && dtp->u.p.skips > 0)
+	    {
+	      /* The advance falls within already-written content (e.g. after
+		 a backward tab).  Advance the position without overwriting
+		 the existing characters.  */
+	      gfc_offset tmp;
+	      write_x (dtp, dtp->u.p.skips, 0);
+	      if (is_stream_io (dtp))
+		tmp = dtp->u.p.current_unit->fbuf->act;
+	      else
+		tmp = dtp->u.p.current_unit->recl
+		      - dtp->u.p.current_unit->bytes_left;
+	      dtp->u.p.max_pos = dtp->u.p.max_pos > tmp
+				  ? dtp->u.p.max_pos : tmp;
+	      dtp->u.p.skips = dtp->u.p.pending_spaces = 0;
+	      break;
+	    }
 	  dtp->u.p.pending_spaces = dtp->u.p.pending_spaces < 0
 				    ? f->u.n : dtp->u.p.pending_spaces;
 
-- 
2.54.0

Reply via email to