Bug 93762 - Truncation of deferred-length string when passing as optional
Summary: Truncation of deferred-length string when passing as optional
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: fortran (show other bugs)
Version: 8.2.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-02-15 20:38 UTC by Ross Chaudhry
Modified: 2021-03-12 22:57 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2020-04-10 00:00:00


Attachments
Minimal reproducing code (274 bytes, text/plain)
2020-02-15 20:38 UTC, Ross Chaudhry
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Ross Chaudhry 2020-02-15 20:38:51 UTC
Created attachment 47848 [details]
Minimal reproducing code

(This issue was first reported on SO here: https://stackoverflow.com/questions/60229805/truncation-of-deferred-length-string-when-passing-as-optional)

When passing strings declared as deferred-length and optional through several subroutines, they are truncated. A minimal example is attached, which yields the following (expected) result on Intel 16.0, Intel 2019_U4, or PGI 15.10:

$ ifort main.f90 && ./a.out 
 at bot of deepest_call, str is "12345"
 at bot of interface_call, str is "12345"
 at bot of main, str is "12345"

However, with gfortran 4.8.5:

$ gfortran --version
GNU Fortran (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36)
Copyright (C) 2015 Free Software Foundation, Inc.

GNU Fortran comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of GNU Fortran
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING

$ gfortran main.f90 && ./a.out 
 at bot of deepest_call, str is "12345"
 at bot of interface_call, str is ""
 at bot of main, str is ""

On the newer version of gfortran I have available (7.2 and 8.2.0), this simple example segfaults when compiled with no options, but truncates the output when compiled using checks:

$ gfortran --version
GNU Fortran (GCC) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gfortran -g -fbacktrace -Wall -Wextra -std=f2008 -fcheck=all -Og main.f90 && ./a.out
 at bot of deepest_call, str is "12345"
 at bot of interface_call, str is ""
 at bot of main, str is ""

It seems to me that this is a compiler error spanning a range of GCC versions (at least 4.8.5 to 8.2.0). So my first purpose for reporting it here is so people are aware of it.

According to francescalus on the StackOverflow post, the error does not occur on the very latest 10.0.0. However, I think it would be good to add this minimal example (or something similar) to the gfortran test suite to prevent the error from occurring again.
Comment 1 Thomas Koenig 2020-04-10 16:49:32 UTC
Unfortunately, the test case fails with different ways on
current trunk:

$ gfortran -g  a.f90
$ ./a.out
 at bot of deepest_call, str is "12345"

Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

Backtrace for this error:
#0  0x7f0a66c3059f in ???
        at /usr/src/debug/glibc-2.26-lp151.19.11.1.x86_64/signal/../sysdeps/unix/sysv/linux/x86_64/sigaction.c:0
#1  0x400c65 in __interface_call_m_MOD_interface_call
        at /tmp/a.f90:20
#2  0x400d99 in MAIN__
        at /tmp/a.f90:32
#3  0x400f0b in main
        at /tmp/a.f90:25
Speicherzugriffsfehler (Speicherabzug geschrieben)

(gdb) r a.f90 
Starting program: /tmp/a.out a.f90
 at bot of deepest_call, str is "12345"

Program received signal SIGSEGV, Segmentation fault.
_gfortran_string_len_trim (s=0x6068d0 "12345", len=<optimized out>) at ../../../gcc/libgfortran/intrinsics/string_intrinsics_inc.c:231
231               if (*((unsigned long*) (s + i + 1)) != blank_longword)
(gdb) p s
$1 = 0x6068d0 "12345"
(gdb) p i
$2 = 564082115390472183

Seems like uninitialzed memory for i.

Valgrind confirms this:

$ valgrind ./a.out
==5621== Memcheck, a memory error detector
==5621== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5621== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==5621== Command: ./a.out
==5621== 
 at bot of deepest_call, str is "12345"
==5621== Conditional jump or move depends on uninitialised value(s)
==5621==    at 0x50A29A5: _gfortran_string_len_trim (string_intrinsics_inc.c:188)
==5621==    by 0x50A2A87: _gfortran_string_trim (string_intrinsics_inc.c:168)
==5621==    by 0x400C65: __interface_call_m_MOD_interface_call (a.f90:20)
==5621==    by 0x400D99: MAIN__ (a.f90:32)
==5621==    by 0x400F0B: main (a.f90:25)

Not sure if this ever worked in a released version.
Comment 2 kargl 2020-04-10 22:36:24 UTC
(In reply to Thomas Koenig from comment #1)
> Unfortunately, the test case fails with different ways on
> current trunk:
> 
> $ gfortran -g  a.f90
> $ ./a.out
>  at bot of deepest_call, str is "12345"
> 
> Program received signal SIGSEGV: Segmentation fault - invalid memory
> reference.
> 
> Backtrace for this error:
> #0  0x7f0a66c3059f in ???
>         at
> /usr/src/debug/glibc-2.26-lp151.19.11.1.x86_64/signal/../sysdeps/unix/sysv/
> linux/x86_64/sigaction.c:0
> #1  0x400c65 in __interface_call_m_MOD_interface_call
>         at /tmp/a.f90:20
> #2  0x400d99 in MAIN__
>         at /tmp/a.f90:32
> #3  0x400f0b in main
>         at /tmp/a.f90:25
> Speicherzugriffsfehler (Speicherabzug geschrieben)
> 
> (gdb) r a.f90 
> Starting program: /tmp/a.out a.f90
>  at bot of deepest_call, str is "12345"
> 
> Program received signal SIGSEGV, Segmentation fault.
> _gfortran_string_len_trim (s=0x6068d0 "12345", len=<optimized out>) at
> ../../../gcc/libgfortran/intrinsics/string_intrinsics_inc.c:231
> 231               if (*((unsigned long*) (s + i + 1)) != blank_longword)
> (gdb) p s
> $1 = 0x6068d0 "12345"
> (gdb) p i
> $2 = 564082115390472183
> 
> Seems like uninitialzed memory for i.
> 
> Valgrind confirms this:
> 
> $ valgrind ./a.out
> ==5621== Memcheck, a memory error detector
> ==5621== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
> ==5621== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
> ==5621== Command: ./a.out
> ==5621== 
>  at bot of deepest_call, str is "12345"
> ==5621== Conditional jump or move depends on uninitialised value(s)
> ==5621==    at 0x50A29A5: _gfortran_string_len_trim
> (string_intrinsics_inc.c:188)
> ==5621==    by 0x50A2A87: _gfortran_string_trim (string_intrinsics_inc.c:168)
> ==5621==    by 0x400C65: __interface_call_m_MOD_interface_call (a.f90:20)
> ==5621==    by 0x400D99: MAIN__ (a.f90:32)
> ==5621==    by 0x400F0B: main (a.f90:25)
> 
> Not sure if this ever worked in a released version.

I doubt it ever worked.  It seems that the length is not getting
set properly for the returning string.  Should this be propagated
up the call change in the hidden string length argument.  Here's a
modified testcase where I print out lengths of str.

module deepest_call_m
   implicit none
   contains
      subroutine deepest_call(str)
         character(len=:), allocatable, optional :: str
         character(len=5) t
         t = '12345'
         if (present(str)) then
            str = t
            write(*,*) 'at bot of deepest_call, str is "'//trim(str)//'"'
         end if
         print *, 'len = ', len(str)
         print '(A)', 'Returning from deepest_call'
      end subroutine deepest_call
end module deepest_call_m

module interface_call_m
   implicit none
   contains
      subroutine interface_call(str)
         use deepest_call_m, only : deepest_call
         character(len=:), allocatable, optional :: str
         if (present(str)) then
            call deepest_call(str)
            print *, 'len = ', len(str)
            write(*,*) 'at bot of interface_call, str is "'//trim(str)//'"'
         end if
      end subroutine interface_call
end module interface_call_m

program main
   use interface_call_m, only : interface_call
   implicit none
   character(len=:), allocatable :: str
   call interface_call(str)
   write(*,*) 'at bot of main, str is "'//trim(str)//'"'
end program main

I get

% gfcx -o z -g a.f90 && ./z
 at bot of deepest_call, str is "12345"
 len =            5
Returning from deepest_call
 len =    134516966
Segmentation fault (core dumped)

len = 5 is in deepest_call and the correct value.
len = 134516966 seems to be a bit too large.
Comment 3 Steve Kargl 2020-04-10 23:10:49 UTC
Here's a better testcasei, which removes IO statement, which
makes it easier to read -fdump-tree-original.

module deepest_call_m
   implicit none
   contains
      subroutine deepest_call(str)
         character(len=:), allocatable, intent(out), optional :: str
         if (present(str)) then
            str = '12345'
            if (len(str) /= 5) stop 1
         end if
      end subroutine deepest_call
end module deepest_call_m

module interface_call_m
   implicit none
   contains
      subroutine interface_call(str)
         use deepest_call_m, only : deepest_call
         character(len=:), allocatable, intent(out), optional :: str
         if (present(str)) then
            call deepest_call(str)
            if (len(str) /= 5) stop 2
         end if
      end subroutine interface_call
end module interface_call_m

program main
   use interface_call_m, only : interface_call
   implicit none
   character(len=:), allocatable :: str
   call interface_call(str)
   if (len(str) /= 5) stop 3
end program main

Here's the -fdump-tree-original where I have removed
inconsequential code and re-ordered to help with thinking.
Comments are in-lined.

MAIN__ ()
{
  integer(kind=4) .str;
  character(kind=1)[1:.str] * str;

  str = 0B;
  interface_call (&str, &.str);  /* .str is not set to some value. */ 
}

interface_call (character(kind=1)[1:*_str] * * str, integer(kind=4) * _str)
{
  if (str != 0B)
    {
      {

        /* This is not good.  *_str has the value of .str from MAIN,
           which wasn't set. */

        character(kind=1)[1:*_str] * *D.3819;
        integer(kind=4) D.3820;
      
        /* Remove freeing from intent(out) attribute. */

        D.3819 = str != 0B ? str : 0B;
        D.3820 = str != 0B ? *_str : 0;

        /* Here D.3820 is 0. */
        deepest_call (D.3819, &D.3820);

	/* *_str should be set to D.3820, but isn't. */
      }
    }
}

deepest_call (character(kind=1)[1:*_str] * * str, integer(kind=4) * _str)
{
  if (str != 0B)
    {
      {
        integer(kind=4) D.3808;
        integer(kind=4) D.3809;

        /* Handle intent(out) and/or re-allocation on assign. */
 
        /* Set *_str to 5, which is the desired length. */
        *_str = 5;
        D.3808 = *_str;
        if (D.3808 > 0)
          {
	     /* Copy '12345' into str. */
          }
      }
    }
}

So, yep!  The string length is not propagated up the call chain.
Comment 4 Neil Carlson 2021-03-12 22:57:21 UTC
It would be great if somebody possessing the necessary skills could invest the time to fix this. For me this is bug breaks a common usage pattern of including optional stat, errmsg arguments to procedure interfaces.