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