https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121759

--- Comment #8 from Yohann Vautrin <[email protected]> ---
I believe we were experiencing the same bug with gfortran 16.1 and it seems to
be a locale-handling bug in libgfortran. It also appears to be related to this
MSYS2 issue: https://github.com/msys2/MINGW-packages/issues/29825.

The important thing missing to reproduce the bug reported here is that the
locale, or at least LC_NUMERIC, should be set to a locale that does not use
periods as decimal separators (French for example uses commas). The bug causes
values written with a decimal point to be parsed only up to the decimal point.

Linux is unaffected because libgfortran uses the thread-safe uselocale() path.

Bug report
----------

There appear to be two separate bugs in the locale-handling code in
libgfortran/io/transfer.c:

1. Reuse of an invalidated locale pointer
For formatted IO, data_transfer_init saves the pointer returned by
"setlocale(LC_NUMERIC, NULL)" but does not copy the string referenced by that
pointer. It then calls setlocale(LC_NUMERIC, "C"). A subsequent call to
setlocale may invalidate the pointer returned by the previous call, which is
what happens with the Windows UCRT. The older MSVCRT behavior masked the bug
because the "C" locale used at program start was stored in static storage, but
there are ways to reproduce the issue with it as well. When libgfortran later
tries to restore the original locale in finalize_transfer, it may therefore
pass an invalid, corrupted, or empty string to setlocale. This changes
LC_NUMERIC for the program from its "C" default value to the locale set in
Windows regional settings

2. Locale guard value corruption 
data_transfer_init increments old_locale_ctr only for formatted IO, but the
corresponding code in finalize_transfer is not guarded by the same
FORM_FORMATTED condition. The counter is therefore also decremented after
unformatted IO. An unformatted operation will therefore change old_locale_ctr
from 0 to -1. Subsequent formatted operations no longer enter the locale guard
correctly, and the counter remains in an invalid state. This prevents formatted
operations from restoring the expected "C" numeric locale, causing the
incorrect locale to persist.

How to reproduce
----------------

Environment: Windows 11 23H2, amd64

- Install MSYS2
- Open Settings / Time & language / Language & region and set Regional format
to, e.g., "French (Canada)"
- Start a MSYS2 UCRT64 environment with gfortran installed with pacman (package
name is mingw-w64-ucrt-x86_64-gcc-libgfortran, version is 16.1.0-5 currently)
- Compile the attached reproducer with gfortran -Wall -Wextra -pedantic
reproducer.f90 and run it

If the bug is reproduced, the program will output this, followed by a
backtrace:
Before unformatted I/O:
  - Expected:  0.10000000000000001
  - Parsed  :  0.10000000000000001
 After unformatted I/O:
  - Expected:  0.10000000000000001
  - Parsed  :   0.0000000000000000
ERROR STOP 2

Suggested fix
----------------

I've attached a patch that should fix both issues. It adds the missing guard in
finalize_transfer and copies the current locale. I've not been able to test it
yet.
The fix should probably be backported. I believe it was added in GCC 5 by PR
47007 and 61847.

Reply via email to