Dear all,

this patch seems to be a very special challenge:

The testcase has an UTF-8 byte order mark, which I do not see in the
patch unless I use "od".  If you don't see it, then please see the
attachment in the PR for reference.

We used a too short line buffer when reading the testcase, leading
to valgrind invalid read/writes.  The solution here is to extend
the line buffer, taking care of line length limits.  I do hope I
got it right.

Regtested on x86_64-pc-linux-gnu.  OK for mainline?

Thanks,
Harald

From 8f7db1002c2a98a9195c56313b4501e73c89524b Mon Sep 17 00:00:00 2001
From: Harald Anlauf <[email protected]>
Date: Tue, 3 Nov 2026 23:19:53 +0100
Subject: [PATCH] fortran: Fix treatment of UTF-8 byte order marks in source
 files [PR128343]

When a gfortran source file contains a BOM, we ignore it, but only on the
first line of a file.  In that case the temporary line buffer needs to be
extended to avoid valgrind errors.

	PR fortran/128343

gcc/fortran/ChangeLog:

	* scanner.cc (load_line): Check for optional UTF-8 BOM at beginning
	of line.  Adjust temporary line buffer length and line truncation.
	(load_file): Use it when reading the very first line of a source
	file.

gcc/testsuite/ChangeLog:

	* gfortran.dg/pr128343.f: New test.
---
 gcc/fortran/scanner.cc               | 33 ++++++++++++++++++++++++----
 gcc/testsuite/gfortran.dg/pr128343.f | 22 +++++++++++++++++++
 2 files changed, 51 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/pr128343.f

diff --git a/gcc/fortran/scanner.cc b/gcc/fortran/scanner.cc
index 9b77e559065..85b25f77da5 100644
--- a/gcc/fortran/scanner.cc
+++ b/gcc/fortran/scanner.cc
@@ -1801,6 +1801,9 @@ gfc_gobble_whitespace (void)
    the first character of the line, which has already been read by the
    caller.  This avoids the use of ungetc().
 
+   If allow_bom is true, increase length of line buffer to allow for UTF-8 BOM
+   (byte order mark) at the beginning of the line.
+
    load_line returns whether the line was truncated.
 
    NOTE: The error machinery isn't available at this point, so we can't
@@ -1808,7 +1811,8 @@ gfc_gobble_whitespace (void)
 	 parts of gfortran.  */
 
 static bool
-load_line (FILE *input, gfc_char_t **pbuf, int *pbuflen, const int *first_char)
+load_line (FILE *input, gfc_char_t **pbuf, int *pbuflen, const int *first_char,
+	   bool allow_bom = false)
 {
   int c, maxlen, i, preprocessor_flag, buflen = *pbuflen;
   int quoted = ' ', comment_ix = -1;
@@ -1819,6 +1823,7 @@ load_line (FILE *input, gfc_char_t **pbuf, int *pbuflen, const int *first_char)
   bool seen_ampersand = false;
   bool found_tab = false;
   bool warned_tabs = false;
+  bool seen_utf8_bom = false;
   gfc_char_t *buffer;
 
   /* Determine the maximum allowed line length.  */
@@ -1841,6 +1846,10 @@ load_line (FILE *input, gfc_char_t **pbuf, int *pbuflen, const int *first_char)
       else
 	buflen = 132;
 
+      /* Increase line buffer length for BOM.  */
+      if (allow_bom)
+	buflen += 3;
+
       *pbuf = gfc_get_wide_string (buflen + 1);
     }
 
@@ -1977,6 +1986,18 @@ load_line (FILE *input, gfc_char_t **pbuf, int *pbuflen, const int *first_char)
       *buffer++ = c;
       i++;
 
+      /* Check for possible UTF-8 BOM.  */
+      if (allow_bom
+	  && i == 3
+	  && buffer[-3] == (unsigned char) '\xEF'
+	  && buffer[-2] == (unsigned char) '\xBB'
+	  && buffer[-1] == (unsigned char) '\xBF')
+	seen_utf8_bom = true;
+
+      /* Check for sentinel '#' after BOM.  */
+      if (i == 4 && seen_utf8_bom)
+	preprocessor_flag = (c == '#');
+
       if (maxlen == 0 || preprocessor_flag)
 	{
 	  if (i >= buflen)
@@ -1988,7 +2009,7 @@ load_line (FILE *input, gfc_char_t **pbuf, int *pbuflen, const int *first_char)
 	      buffer = (*pbuf) + i;
 	    }
 	}
-      else if (i >= maxlen)
+      else if (i >= maxlen + (seen_utf8_bom ? 3 : 0))
 	{
 	  bool trunc_warn = true;
 
@@ -2035,7 +2056,7 @@ next_char:
       && !preprocessor_flag
       && c != EOF)
     {
-      while (i++ < maxlen)
+      while (i++ < maxlen + (seen_utf8_bom ? 3 : 0))
 	*buffer++ = ' ';
     }
 
@@ -2541,6 +2562,7 @@ load_file (const char *realfilename, const char *displayedname, bool initial)
   FILE *input;
   int len, line_len;
   bool first_line;
+  bool allow_bom;
   struct stat st;
   int stat_result;
   const char *filename;
@@ -2632,11 +2654,14 @@ load_file (const char *realfilename, const char *displayedname, bool initial)
 	}
     }
 
+  /* Allow BOM on very first line of file.  */
+  allow_bom = true;
   for (;;)
     {
-      int trunc = load_line (input, &line, &line_len, NULL);
+      int trunc = load_line (input, &line, &line_len, NULL, allow_bom);
       int inc_line;
 
+      allow_bom = false;
       len = gfc_wide_strlen (line);
       if (feof (input) && len == 0)
 	break;
diff --git a/gcc/testsuite/gfortran.dg/pr128343.f b/gcc/testsuite/gfortran.dg/pr128343.f
new file mode 100644
index 00000000000..e7767eb0897
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/pr128343.f
@@ -0,0 +1,22 @@
+#define a(i) b(i)
+!
+! PR fortran/128343 - UTF-8 byte order mark and internal line buffer
+!
+! { dg-additional-options "-w" }
+!
+! Note: this file has the following characters at the beginning:
+!
+! 0000000 357 273 277 043 144 145 146 151 156 145 040 141 050 151 051 040
+!         357 273 277   #   d   e   f   i   n   e       a   (   i   )    
+!
+! which lead to invalid write/read errors under valgrind.
+!
+! Derived from 1066_0007.f from the Fujitsu test suite on Github.
+
+#if 0
+#else ! a
+      ! a
+      write(10,*) a
+      print *,'pass'
+#endif ! a a
+      end
-- 
2.51.0

Reply via email to