See attached patch. This one was a surprise and a subtle problem.

See the explanation in the commit message. Since this touches on libcpp I am not sure who else should review this.

Regression tested on x86_64.

OK for mainline and backport to 16 later.

Regards,

Jerry

---

The traditional-mode CPP stringification operator escaped double-quote
characters in macro argument tokens with a backslash (e.g.
CPP_STRINGIFY(key .eq. "x") expanded to "key .eq. \"x\"").  This is
correct for C but not valid Fortran: Fortran represents an embedded
double-quote with a doubled quote (""), not a backslash-quote (\").
The backslash-escaped output was rejected by the Fortran scanner with a
spurious syntax error.

Fix: add a new flag fortran_string_escaping to struct cpp_options.  When
set, traditional.cc emits a doubled quote instead of a backslash-quote when
escaping a double-quote character inside a stringified argument, and
suppresses the extra backslash before a literal backslash in that context.
Set the flag in gfc_cpp_post_options, which configures the CPP reader for
Fortran.

PR fortran/125533

Assisted by: Claude Sonnet 4.6

gcc/fortran/ChangeLog:

        * cpp.cc (gfc_cpp_post_options): Set fortran_string_escaping in
        cpp_options so traditional-mode stringification uses doubled-quote
        escaping instead of backslash-quote.

gcc/testsuite/ChangeLog:

        * gfortran.dg/cpp_stringify_quote_1.F90: New test.

libcpp/ChangeLog:

        * include/cpplib.h (cpp_options): Add fortran_string_escaping flag.
        * traditional.cc (replace_args_and_push): When
        fortran_string_escaping is set, escape double-quote with a doubled
        quote rather than a backslash; suppress the extra backslash before a
        backslash inside a quoted argument.
---
From 2b355d4f4239bfd9a2c184f5270e0d852e90f7fd Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Fri, 5 Jun 2026 12:20:18 -0700
Subject: [PATCH] fortran/libcpp: CPP stringify operator emits \" for
 double-quotes in Fortran mode

The traditional-mode CPP stringification operator escaped double-quote
characters in macro argument tokens with a backslash (e.g.
CPP_STRINGIFY(key .eq. "x") expanded to "key .eq. \"x\"").  This is
correct for C but not valid Fortran: Fortran represents an embedded
double-quote with a doubled quote (""), not a backslash-quote (\").
The backslash-escaped output was rejected by the Fortran scanner with a
spurious syntax error.

Fix: add a new flag fortran_string_escaping to struct cpp_options.  When
set, traditional.cc emits a doubled quote instead of a backslash-quote when
escaping a double-quote character inside a stringified argument, and
suppresses the extra backslash before a literal backslash in that context.
Set the flag in gfc_cpp_post_options, which configures the CPP reader for
Fortran.

PR fortran/125533

Assisted by: Claude Sonnet 4.6

gcc/fortran/ChangeLog:

	* cpp.cc (gfc_cpp_post_options): Set fortran_string_escaping in
	cpp_options so traditional-mode stringification uses doubled-quote
	escaping instead of backslash-quote.

gcc/testsuite/ChangeLog:

	* gfortran.dg/cpp_stringify_quote_1.F90: New test.

libcpp/ChangeLog:

	* include/cpplib.h (cpp_options): Add fortran_string_escaping flag.
	* traditional.cc (replace_args_and_push): When
	fortran_string_escaping is set, escape double-quote with a doubled
	quote rather than a backslash; suppress the extra backslash before a
	backslash inside a quoted argument.
---
 gcc/fortran/cpp.cc                            |  1 +
 .../gfortran.dg/cpp_stringify_quote_1.F90     | 29 +++++++++++++++++++
 libcpp/include/cpplib.h                       |  6 ++++
 libcpp/traditional.cc                         | 20 +++++++++----
 4 files changed, 50 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/cpp_stringify_quote_1.F90

diff --git a/gcc/fortran/cpp.cc b/gcc/fortran/cpp.cc
index 6b5f136e4f3..bac34928ce4 100644
--- a/gcc/fortran/cpp.cc
+++ b/gcc/fortran/cpp.cc
@@ -517,6 +517,7 @@ gfc_cpp_post_options (bool verbose_missing_dir_warn)
 
   /* TODO: allow non-traditional modes, e.g. by -cpp-std=...?  */
   cpp_option->traditional = 1;
+  cpp_option->fortran_string_escaping = 1;
   cpp_option->cplusplus_comments = 0;
 
   cpp_option->cpp_pedantic = pedantic;
diff --git a/gcc/testsuite/gfortran.dg/cpp_stringify_quote_1.F90 b/gcc/testsuite/gfortran.dg/cpp_stringify_quote_1.F90
new file mode 100644
index 00000000000..16b082ed9c0
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/cpp_stringify_quote_1.F90
@@ -0,0 +1,29 @@
+! { dg-do compile }
+! { dg-options "-cpp -DASSERTIONS" }
+!
+! Test that \" inside a CPP-macro-expanded double-quoted character literal
+! is treated as an embedded double-quote rather than a string terminator.
+! The gfortran-compatible CPP_STRINGIFY_SOURCE workaround ("x" form)
+! embeds the macro argument directly inside a string literal; when the
+! argument contains " the CPP emits \" which must be accepted.
+
+! This matches the pattern in assert_macros.h (assert 3.0.2 / julienne 3.6.1)
+#define CPP_STRINGIFY_SOURCE(x) "x"
+
+#if ASSERTIONS
+# define SHOW(a) print *, "assert(" // CPP_STRINGIFY_SOURCE(a) // ")"
+#else
+# define SHOW(a)
+#endif
+
+program cpp_stringify_quote
+  implicit none
+  character(len=20) :: key
+
+  ! No double quotes in argument -- baseline
+  SHOW(1 + 2 .eq. 3)
+
+  ! Double-quoted string literal in argument -- was failing before fix
+  SHOW(key .eq. "metadata")
+  SHOW(key .eq. "a" // "b")
+end program
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index e8c35cd68d7..aae71eaa167 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -511,6 +511,12 @@ struct cpp_options
   /* True for traditional preprocessing.  */
   unsigned char traditional;
 
+  /* True when preprocessing Fortran source.  In traditional mode,
+     double-quote characters in macro arguments expanded inside a
+     quoted context are escaped using the Fortran doubled-quote
+     convention ("") rather than the C backslash convention (\").  */
+  unsigned char fortran_string_escaping;
+
   /* Nonzero for C++ 2011 Standard user-defined literals.  */
   unsigned char user_literals;
 
diff --git a/libcpp/traditional.cc b/libcpp/traditional.cc
index 2ed07bc7346..20e67d7114e 100644
--- a/libcpp/traditional.cc
+++ b/libcpp/traditional.cc
@@ -1041,18 +1041,26 @@ replace_args_and_push (cpp_reader *pfile, struct fun_macro *fmacro)
 		{
 		  if (in > base && *(in-1) != '\\')
 		    argquote = ! argquote;
-		  /* Always add backslash before double quote if argument
-		     is expanded in a quoted context */
-		  *p++ = '\\';
+		  /* Escape the double-quote character inside the string.
+		     For Fortran, use the doubled-quote convention ("") so
+		     that the result is valid Fortran; for C, use the
+		     backslash convention (\").  */
+		  if (CPP_OPTION (pfile, fortran_string_escaping))
+		    *p++ = '"';
+		  else
+		    *p++ = '\\';
 		  len++;
 		}
 	      else if (cxtquote && argquote && *in == '\\')
 		{
 		  /* Always add backslash before a backslash in an argument
 		     that is expanded in a quoted context and also in the
-		     range of a quoted context in the argument itself. */
-		  *p++ = '\\';
-		  len++;
+		     range of a quoted context in the argument itself.  */
+		  if (!CPP_OPTION (pfile, fortran_string_escaping))
+		    {
+		      *p++ = '\\';
+		      len++;
+		    }
 		}
 	      *p++ = *in++;
 	      len++;
-- 
2.54.0

Reply via email to