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.

Reply via email to