https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125533
Jerry DeLisle <jvdelisle at gcc dot gnu.org> changed:
What |Removed |Added
----------------------------------------------------------------------------
Component|fortran |c
Assignee|jvdelisle at gcc dot gnu.org |unassigned at gcc dot
gnu.org
--- Comment #1 from Jerry DeLisle <jvdelisle at gcc dot gnu.org> ---
This analysis by Claude Opus 5.5 . Several of the issues identfied are in the
cpp code and not fortran. I dont plan to go further on this unless asked to by
someone who can commit to the cpp code.
Subject: gfortran preprocessing: C string rules break Fortran literals
in traditional cpp (GCC 16.2)
Summary
-------
gfortran runs libcpp in traditional mode (gcc/fortran/cpp.cc,
gfc_cpp_post_options, sets cpp_option->traditional = 1). In two places
libcpp/traditional.cc applies C string-literal rules to the text it
processes. Fortran literals follow different rules:
- both "..." and '...' are character literals;
- an embedded delimiter is written doubled ("" or ''), never as \"
or \' -- not even with -fbackslash;
- a backslash is an ordinary character (without -fbackslash), so a
literal may end in one, e.g. "C:\dir\".
This causes three groups of failures, one of them silent wrong code.
All examples below were reproduced with
GNU Fortran (GCC) 16.2.1 20260819 (Red Hat 16.2.1-2)
and preprocessed output was taken with "gfortran -E -P". The relevant
code in libcpp/traditional.cc is the same on releases/gcc-16 and on
trunk (trunk differs only by a comment typo fix).
Background: traditional mode has no # operator ("#define S(x) #x"
expands to "#key ..."), so Fortran code that needs the text of a macro
argument uses the idiom
#define CPP_STRINGIFY_SOURCE(x) "x"
as found, for example, in assertion macros of several Fortran
libraries. Issues 1 and 2 hit this idiom directly.
Issue 1: macro arguments substituted inside "..." get C escaping
-----------------------------------------------------------------
When a parameter is replaced inside a double-quoted part of the macro
body, replace_args_and_push() puts a backslash before every " in the
argument, and doubles every backslash that lies inside a quoted part
of the argument.
#define STR(x) "x"
program p
character(len=20) :: key = "metadata"
print '(a)', STR(key .eq. "metadata")
end program p
Expands to: print '(a)', "key .eq. \"metadata\""
Result: Error: Syntax error in PRINT statement at (1)
Expected: print '(a)', "key .eq. ""metadata"""
#define STR(x) "x"
print '(a)', STR(path .eq. "a\b")
Expands to: print '(a)', "path .eq. \"a\\b\""
Result: Error: Syntax error in PRINT statement at (1)
Expected: print '(a)', "path .eq. ""a\b"""
-fbackslash does not help: gfortran never accepts \" as an embedded
delimiter, so both examples fail the same way with it.
Issue 2: '...' literals are ignored when tracking quoted context
-----------------------------------------------------------------
replace_args_and_push() decides whether a parameter sits inside a
literal (cxtquote) by toggling on '"' only. As a result:
(a) A parameter inside '...' is not escaped at all.
#define STRA(x) 'x'
print '(a)', STRA(key .eq. 'metadata')
Expands to: print '(a)', 'key .eq. 'metadata''
Result: Error: Syntax error in PRINT statement at (1)
Expected: print '(a)', 'key .eq. ''metadata'''
(b) A " inside a '...' literal in the macro body flips the state, so
a later "x" is treated as unquoted and its argument is not
escaped.
#define SHOW(x) print *, 'it"s', "x"
program p
integer :: k = 1
SHOW(k .eq. "z")
end program p
Expands to: print *, 'it"s', "k .eq. "z""
Result: Error: Empty set of digits in BOZ constant at (1)
Expected: print *, 'it"s', "k .eq. ""z"""
Issue 3: backslash treated as an escape when scanning lines
-----------------------------------------------------------
_cpp_scan_out_logical_line() tracks quotes so that it does not expand
macros inside literals. Its case '\\' skips the following character
when that is a quote or a backslash. For a Fortran literal ending in
a backslash, the closing delimiter is therefore skipped, and the
scanner treats the rest of the line as still inside the literal.
(a) Silent wrong code: macros later on the same line are not expanded.
#define N 42
program p
print *, "C:\dir\", N
print *, N
end program p
Expands to: print *, "C:\dir\", N
print *, 42
Result: compiles without any diagnostic (even with -Wall)
under implicit typing, and prints an uninitialized
variable N instead of 42:
C:\dir\ 1908119193
42
The same happens with apostrophes ('C:\dir\', N). With IMPLICIT
NONE it shows up as "Symbol 'n' at (1) has no IMPLICIT type".
(b) A macro argument containing such a literal is never closed.
#define STR(x) "x"
print '(a)', STR(path .eq. "C:\dir\")
Result: Error: unterminated argument list invoking macro 'STR'
followed by "Unexpected end of file".
Without the preprocessor, gfortran accepts a literal ending in a
backslash, with or without -fbackslash ("a\" has length 2).
Notes for a fix
---------------
- All three issues come from C string rules in libcpp/traditional.cc,
which is shared with C traditional preprocessing. C behaviour must
not change, so the Fortran rules need a cpp_options flag set by
gfc_cpp_post_options().
- In Fortran mode the quoted-context state needs to remember which
delimiter is open (" or '), and only an argument character equal to
that delimiter should be doubled. Backslashes should be copied
unchanged.
- In Fortran mode, _cpp_scan_out_logical_line() should not treat a
backslash as escaping a quote. Doubled delimiters inside a literal
already work with the existing toggle logic.
Workarounds for users today: use apostrophes inside arguments passed
to a "x" macro (STR(key .eq. 'metadata') expands correctly), and avoid
literals ending in a backslash on lines that use macros.