Bug 85537 - [F08] Invalid memory reference at runtime when calling subroutine through procedure pointer
Summary: [F08] Invalid memory reference at runtime when calling subroutine through pro...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: fortran (show other bugs)
Version: 4.8.5
: P3 normal
Target Milestone: 9.0
Assignee: janus
URL:
Keywords: accepts-invalid, ice-on-invalid-code
Depends on:
Blocks: F2008
  Show dependency treegraph
 
Reported: 2018-04-26 11:27 UTC by Tiziano Müller
Modified: 2019-03-27 23:05 UTC (History)
2 users (show)

See Also:
Host:
Target: x86_64-pc-linux-gnu
Build:
Known to work:
Known to fail: 4.8.5, 5.5.0, 6.5.0, 7.3.0, 8.0.1
Last reconfirmed: 2018-04-26 00:00:00


Attachments
Assembly for test case (2.82 KB, text/plain)
2019-03-24 22:58 UTC, Thomas Koenig
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Tiziano Müller 2018-04-26 11:27:48 UTC
Given the following code, the runtime generates a segfault on the same function when called through the procedure pointer instead of a direct function call:

  module m
     implicit none
  contains
     subroutine foo()
        integer :: a
  
        abstract interface
           subroutine ibar()
           end subroutine
        end interface
  
        procedure(ibar), pointer :: bar_ptr => bar_impl
  
        a = 0
        call bar_impl()
        call bar_ptr()
  
     contains
        subroutine bar_impl()
           write (*,*) "foo"
           a = a + 1
        end subroutine
  
     end subroutine
  end module
  
  program main
     use m
  
     call foo()
  end program

it will generate the following output:

$ ./segfault_minimal_rep 
 foo
 foo

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

Backtrace for this error:
#0  0x7f34795e7bbd in ???
#1  0x7f34795e6e03 in ???
#2  0x7f3478b02fdf in ???
#3  0x400788 in bar_impl
	at /users/tiziano/tmp/segfault_minimal_rep.f90:21
#4  0x4007c6 in __m_MOD_foo
	at /users/tiziano/tmp/segfault_minimal_rep.f90:16
#5  0x4007d1 in MAIN__
	at /users/tiziano/tmp/segfault_minimal_rep.f90:30
#6  0x400808 in main
	at /users/tiziano/tmp/segfault_minimal_rep.f90:28
Segmentation fault (core dumped)
Comment 1 Richard Biener 2018-04-26 11:54:36 UTC
Confirmed with GCC 7.
Comment 2 Dominique d'Humieres 2018-04-26 14:15:58 UTC
The test works for me with 4.8.5. The change occurred between revisions r2370089 (2016-06-04, OK) and r237310 + one patch (2016-06-10, wrong code).
Comment 3 Tiziano Müller 2018-04-26 15:09:40 UTC
(In reply to Dominique d'Humieres from comment #2)
> The test works for me with 4.8.5. The change occurred between revisions
> r2370089 (2016-06-04, OK) and r237310 + one patch (2016-06-10, wrong code).

Ok, then openSuSE must have some patches on their 4.8.5 which introduce the bug, since I get:

tiziano@tcpc18 ~/tmp $ gfortran --version
GNU Fortran (SUSE Linux) 4.8.5
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

tiziano@tcpc18 ~/tmp $ gfortran -o segfault_minimal_rep segfault_minimal_rep.f90 -ggdb
tiziano@tcpc18 ~/tmp $ ./segfault_minimal_rep 
 foo
 foo

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

Backtrace for this error:
#0  0x7f9bb46c7607 in ???
#1  0x7f9bb46c686d in ???
#2  0x7f9bb3be3fdf in ???
#3  0x4007bc in bar_impl
	at /users/tiziano/tmp/segfault_minimal_rep.f90:21
#4  0x4007f1 in __m_MOD_foo
	at /users/tiziano/tmp/segfault_minimal_rep.f90:16
#5  0x4007fc in MAIN__
	at /users/tiziano/tmp/segfault_minimal_rep.f90:30
#6  0x400832 in main
	at /users/tiziano/tmp/segfault_minimal_rep.f90:28
Segmentation fault (core dumped)
Comment 4 janus 2018-05-18 10:32:49 UTC
(In reply to Dominique d'Humieres from comment #2)
> The test works for me with 4.8.5. The change occurred between revisions
> r2370089 (2016-06-04, OK) and r237310 + one patch (2016-06-10, wrong code).

I see runtime segfaults with every gfortran version that I tried, starting from 4.6.

4.4 does not compile the example due to the proc-ptr init.
Comment 5 janus 2018-05-18 10:36:10 UTC
However I don't see any failures with this variant:

program main

   call foo()

contains

   subroutine foo()
      integer :: a

      abstract interface
         subroutine ibar()
         end subroutine
      end interface

      procedure(ibar), pointer :: bar_ptr => bar_impl

      a = 0
      call bar_impl()
      call bar_ptr()

   end subroutine


   subroutine bar_impl()
      write (*,*) "foo"
      a = a + 1
   end subroutine

end program
Comment 6 janus 2018-05-20 20:14:05 UTC
(In reply to janus from comment #5)
> However I don't see any failures with this variant:

Sorry, I was a bit too quick in submitting this. It's really not a good example, since it's missing an "implicit none", so "bar_impl" supposedly gets its own private version of "a".

The following variant fails again in the same way as the original test case:


program main

   implicit none
   integer :: a

   call foo()

contains

   subroutine foo()

      abstract interface
         subroutine ibar()
         end subroutine
      end interface

      procedure(ibar), pointer :: bar_ptr => bar_impl

      a = 0
      call bar_impl()
      call bar_ptr()

   end subroutine


   subroutine bar_impl()
      write (*,*) "foo"
      a = a + 1
   end subroutine

end program


I don't quite understand what's going on. Apparently the reference to the variable 'a' somehow goes wrong. But I don't see how the procedure pointer makes a difference here.
Comment 7 Thomas Koenig 2019-03-24 22:58:38 UTC
Created attachment 46016 [details]
Assembly for test case

Looking at the assembly of the test case in comment #6 (with -g -fverbose-asm
only):

.LBE2:
# a.f90:29:       a = a + 1
	.loc 1 29 0
	movl	(%rbx), %eax	# CHAIN.4_10(D)->a, _11
	addl	$1, %eax	#, _12
	movl	%eax, (%rbx)	# _12, CHAIN.4_10(D)->a

When called through a procedure pointer, rbx no longer contains the
address of a.

Looking further up, we have

	movq	%r10, %rbx	# CHAIN.4, CHAIN.4
	movq	%r10, -552(%rbp)	# CHAIN.4,
.LBB2:

so %r10 is clobbered. This happens in the call of _gfortran_st_write.

Looking at the ABI, %r10 is not supposed to be preserved.

Data point: This works on POWER9.

This looks like a target bug to me.
Comment 8 Thomas Koenig 2019-03-24 23:01:43 UTC
Resetting priority to default, setting component to rtl-optimization and adding
failing target.
Comment 9 Thomas Koenig 2019-03-24 23:06:35 UTC
(In reply to Dominique d'Humieres from comment #2)
> The test works for me with 4.8.5. The change occurred between revisions
> r2370089 (2016-06-04, OK) and r237310 + one patch (2016-06-10, wrong code).

The only Fortran commits in that range are r237105 and r237108,
neither of them appear to touch the area in question.
Comment 10 Thomas Koenig 2019-03-25 07:21:27 UTC
Bisecting this leads to

/home/ig25/Gcc/Bisect-bin/./gcc/xgcc -B/home/ig25/Gcc/Bisect-bin/./gcc/ -xc -S -c /dev/null -fself-test
../../Bisect/gcc/input.c:1154: FAIL: ASSERT_STREQ (exp_filename, LOCATION_FILE (loc))
cc1: interner Compiler-Fehler: in fail, bei selftest.c:44
0x190e417 selftest::fail(char const*, int, char const*)
        ../../Bisect/gcc/selftest.c:44
0x191b6d9 assert_loceq
        ../../Bisect/gcc/input.c:1154
0x191bb1c test_builtins
        ../../Bisect/gcc/input.c:1213
0x191bc76 selftest::input_c_tests()
        ../../Bisect/gcc/input.c:1248
0x18b1e5c selftest::run_tests()
        ../../Bisect/gcc/selftest-run-tests.c:52
0xe8e510 toplev::run_self_tests()
        ../../Bisect/gcc/toplev.c:2048
Bitte senden Sie einen vollständigen Fehlerbericht auf Englisch ein;
inclusive vorverarbeitetem Quellcode, wenn es dienlich ist.

at r237159 :-(

Trying

Index: gcc/input.c
===================================================================
--- gcc/input.c (Revision 237159)
+++ gcc/input.c (Arbeitskopie)
@@ -1151,7 +1151,7 @@
 assert_loceq (const char *exp_filename, int exp_linenum, int exp_colnum,
              location_t loc)
 {
-  ASSERT_STREQ (exp_filename, LOCATION_FILE (loc));
+  // ASSERT_STREQ (exp_filename, LOCATION_FILE (loc));
   ASSERT_EQ (exp_linenum, LOCATION_LINE (loc));
   ASSERT_EQ (exp_colnum, LOCATION_COLUMN (loc));
 }

to let this continue...
Comment 11 Thomas Koenig 2019-03-25 23:05:19 UTC
r237104 fails for me, testing r237008.
Comment 12 Thomas Koenig 2019-03-25 23:30:22 UTC
(In reply to Thomas Koenig from comment #11)
> r237104 fails for me, testing r237008.

r237007 also fails, as do earlier versions of the compiler.

Not a regression then, but rather a design fault - seems like
we need to do some magic to tell the middle end that we could
access global variables (i.e. make sure that %r10 contains the
right address).
Comment 13 Richard Biener 2019-03-27 09:22:30 UTC
The issue seems to be that the indirect call doesn't set up the static
chain and this is because appearantly bar_ptr has static storage duration:

foo ()
{
  static voidD.27 (*<T5c1>) (void) bar_ptrD.3873 = bar_implD.3867;
  integer(kind=4)D.8 D.3885;
  voidD.27 (*<T5c1>) (void) bar_ptr.2_1;
  integer(kind=4)D.8 _2;

  <bb 2> :
  _2 = 0;
  CHAIN.5_4(D)->aD.3880 = _2;
  bar_impl (); [static-chain: CHAIN.5_4(D)]
  bar_ptr.2_1 = bar_ptrD.3873;
  bar_ptr.2_1 ();
  return;

I think this isn't supported:

void foo()
{
  int a;
  int bar()
    {
      return a;
    }

  static int (*bp)() = bar;
  bp();
}

> ./cc1 -quiet t.c
t.c: In function ‘foo’:
t.c:9:24: error: initializer element is not constant
    9 |   static int (*bp)() = bar;
      |                        ^~~


I'm not sure how bar_ptr ends up statically allocated, that must be an
error on the FE side - I'd have expected a SAVE to be needed?  And
the FE indeed accepts

      procedure(ibar), pointer, save :: bar_ptr => bar_impl

but it should probably reject that.  The IL it emits is the same, with
or without the save.

TREE_STATIC is set here:

  if (!sym->attr.use_assoc
        && (sym->attr.save != SAVE_NONE || sym->attr.data
              || (sym->value && sym->ns->proc_name->attr.is_main_program)))
    TREE_STATIC (decl) = 1;

beacuse sym->attr.save == SAVE_IMPLICIT

As said, the testcase is invalid if it needs SAVE since that prolongs
lifetime of the static chain over the duration of the contained function.
Comment 14 Richard Biener 2019-03-27 09:24:48 UTC
Probably missed handling in add_init_expr_to_sym which needs to special case
procedure-pointers to nested functions?
Comment 15 janus 2019-03-27 10:06:40 UTC
(In reply to Richard Biener from comment #13)
> And the FE indeed accepts
> 
>       procedure(ibar), pointer, save :: bar_ptr => bar_impl
> 
> but it should probably reject that.

Indeed this is only valid since Fortran 2008:

$ gfortran c0.f90 -std=f2003
c0.f90:12:53:

   12 |       procedure(ibar), pointer :: bar_ptr => bar_impl
      |                                                     1
Error: Fortran 2008: non-NULL pointer initialization at (1)
Comment 16 Tiziano Müller 2019-03-27 10:19:21 UTC
can confirm, changing the reproducer to

    procedure(ibar), pointer :: bar_ptr => null()
    bar_ptr => bar_impl

makes it generate valid code with the warning

    Warning: trampoline generated for nested function 'bar_impl' [-Wtrampolines]
Comment 17 janus 2019-03-27 10:28:33 UTC
(In reply to Tiziano Müller from comment #16)
> can confirm, changing the reproducer to
> 
>     procedure(ibar), pointer :: bar_ptr => null()
>     bar_ptr => bar_impl
> 
> makes it generate valid code


Also this is only allowed since Fortran 2008:

       bar_ptr => bar_impl
                 1
Error: Fortran 2008: Internal procedure ‘bar_impl’ is invalid in procedure pointer assignment at (1)
Comment 18 janus 2019-03-27 10:33:41 UTC
(In reply to Richard Biener from comment #13)
> As said, the testcase is invalid if it needs SAVE since that prolongs
> lifetime of the static chain over the duration of the contained function.

I agree.

Looking into Fortran 2008, one finds:

R1217 initial-proc-target is procedure-name

and

C1220(R1217) The procedure-name shall be the name of a nonelemental external or module procedure, or a specific intrinsic function listed in 13.6 and not marked with a bullet (•).

Note that this does not list internal procedures.
Comment 19 janus 2019-03-27 10:52:11 UTC
I'll take care of fixing this.
Comment 20 janus 2019-03-27 11:57:18 UTC
(In reply to janus from comment #18)
> C1220(R1217) The procedure-name shall be the name of a nonelemental external
> or module procedure, or a specific intrinsic function listed in 13.6 and not
> marked with a bullet (•).
> 
> Note that this does not list internal procedures.

In contrast to the corresponding restriction for procedure pointer assignments:

C729 (R740) A procedure-name shall be the name of an internal, module, or dummy procedure, a procedure pointer, an external procedure that is accessed by use or host association and is referenced in the scoping unit as a procedure or that has the EXTERNAL attribute, or a specific intrinsic function listed in 13.6 and not marked with a bullet (•).
Comment 21 janus 2019-03-27 12:38:12 UTC
Another related test case with a dummy procedure in a proc-ptr init, which currently ICEs, but should be rejected:


module m
    implicit none
contains
    subroutine foo(dbar)
      interface
        subroutine dbar()
        end subroutine
      end interface

      procedure(dbar), pointer :: bar_ptr => dbar

      call bar_ptr()

    end subroutine
end module

program main
    use m
    implicit none
    call foo(bar_impl)

  contains

    subroutine bar_impl()
      integer :: a = 0
      write (*,*) "foo"
      a = a + 1
    end subroutine
    
end program
Comment 22 janus 2019-03-27 22:40:53 UTC
Author: janus
Date: Wed Mar 27 22:40:22 2019
New Revision: 269980

URL: https://gcc.gnu.org/viewcvs?rev=269980&root=gcc&view=rev
Log:
fix PR 85537

2019-03-27  Janus Weil  <janus@gcc.gnu.org>

	PR fortran/85537
	* expr.c (gfc_check_assign_symbol): Reject internal and dummy procedures
	in procedure pointer initialization.

2019-03-27  Janus Weil  <janus@gcc.gnu.org>

	PR fortran/85537
	* gfortran.dg/dummy_procedure_11.f90: Fix test case.
	* gfortran.dg/pointer_init_11.f90: New test case.

Added:
    trunk/gcc/testsuite/gfortran.dg/pointer_init_11.f90
Modified:
    trunk/gcc/fortran/ChangeLog
    trunk/gcc/fortran/expr.c
    trunk/gcc/testsuite/ChangeLog
    trunk/gcc/testsuite/gfortran.dg/dummy_procedure_11.f90
Comment 23 janus 2019-03-27 23:05:34 UTC
Fixed on trunk with r269980. Closing. Thanks for the report!