Is pointing to a target dummy argument safe after return?

The simplest case:

call foo(a,p)

subroutine foo(aa,pp)
implicit none
real, intent(in), target :: aa(:)
real, pointer :: pp(:)
pp => aa
end subroutine

Is the actual pointer p guaranteed to point to the actual array a after the call to foo()? I would say no, in case there is some copy-in for aa (unlikely in practice, but not impossible)

The standard only provides usable interpretation for the case where the actual argument a has the TARGET attribute. But there is no means to enforce that, thus the usage has potential pitfalls.

In my case, the actual argument has indeed the target attribute. So you mean that in this case it is supposed to be safe (which means that the compiler is not authorized to make a copy-in)?

My understanding is the standard mostly takes a soft-influence - advisory - stance on such aspects, not just this one, and extends considerable leeway to the processors.

Given your concern with copy-in, I suggest the CONTIGUOUS attribute which will provide the compiler as well as the programmer with additional requirements and further safety i.e., assuming the data are contiguous in your actual use case:

subroutine foo(aa,pp)
   real, intent(in), contiguous, target :: aa(:)
   real, contiguous, pointer :: pp(:)
   .
end subroutine

I think the answer is that yes, it is safe in this case. The returned pointer is required to point to the original data (assuming a statement like pp=>aa exists somewhere in your original code).

However, if you were to add contiguous attributes to the dummy argument, then I think this will require the compiler to make a temporary copy of the data if it is not originally contiguous, so in that case the returned pointer would not point to the original data. I think the compiler is required to warn you about the mismatch of attributes. Maybe others can clarify this point.

Interesting question! I don’t know what the standard has to say on this, but at least ifort gives me an invalid pointer if I force a situation with copy-in:

Consider this example:

program main
    implicit none

    integer, allocatable :: i(:)
    integer, allocatable :: indices(:)
    integer, pointer :: p(:)

    i = [1, 2, 3, 4, 5, 6]
    call sub(i, p)
    print *, loc(i(1))
    print *, loc(p(1))
    indices = [1, 2, 5]
    call sub(i(indices), p)
    print *, loc(p(1))
    print *, 'p(1) = ', p(1)

contains

    subroutine sub(x, p)
        integer, target, intent(in) :: x(:)
        integer, pointer, intent(out) :: p(:)

        p => x
    end subroutine
end program

Output from one run:

         2605282179200
         2605282179200
          913231050816
 p(1) =  -1596982848

Now if I change x to intent(inout) the compiler wil complain:

main.f90(13): error #7710: An array section having a vector subscript is not permitted if dummy argument has intent [IN]OUT.   [I]
    call sub(i(indices), p)
-------------^

Having the same requirement for dummy arguments with target would avoid the situation in the example above, but even that can easily be avoided by adding another layer between the copy-in and the pointer assignment where the dummy argument does not have the target attribute.

@plevold I tried your code with gfortran, and the pointer remains valid, although it doesn’t point to the original data (it could not, anyway):

      140441710247104
      140441710247104
      140441710247152
 p(1) =            1

So it looks like at first that the pointer survived and still point to the copied-in data, but it’s quite unlikely that this data is valid: this is on the stack, and the stack has to be freed upon return as far as I know. I checked that by appending a few lines with another pointer tp you main code:

    call sub(i(indices), q)
    print *, loc(q(1))
    print *, 'p(1) = ', p(1)
    print *, 'q(1) = ', p(1)

And the whole code gives:

      140393777740992
      140393777740992
      140393777741040
 p(1) =            1
      140393777741040
 p(1) =            5
 q(1) =            5

So as expected, the area pointed by p(:) has been reused and overwritten during the second call to sub.

See also the discussion Why/When is the `target` attribute part of the characterisics of a procedure

IMO, one should never use the target attribute for a dummy argument, when the pointer pointing to it is supposed to be valid after the scope had been left. It should work (in theory,) if the actual argument indeed has the target attribute (but only then!), but no compiler I know gives you any warning/error, if you actually forget to specify that attribute for the actual argument. So, sooner or later, it is bound to fail due to oversight (and it can be a mess to find the bug…)

I always specify the pointer attribute for the dummy argument in those cases, as all compilers I know will ensure that the actual argument has either the pointer or the target attribute. So you are safely pass back pointers to them as they are warranted to be valid also after the end of the scope and you won’t be hit by any copy-in/copy-out issues.

4 Likes

In the example of @plevold, having target specified on both the actual and the dummy arguments is not enough. It is fully understandable that it can not work in such a case where copy-in can not be avoided, but how can we be sure that it work when copy-in is not required?

Does someone has the part of the standard that ensures that?

I learnt something today, I didn’t know that a non pointer actual argument could be passed to a pointer dummy! I assume that there is some hidden casting here?

1 Like

@aradi ,

You may want to clarify this works only when the received argument is intent(in).

Yes, thank you, indeed I should have added, that it only works with pointer, intent(in) arguments. Fortunately (?) the intent(in) attribute of a pointer still allows the modification of the pointers target (as it only prohibits to change the pointer association itself), so that it is not really a constraint for most use cases.

The problem is the vector subscript. Note C.10.4, point 2 in the F2018 standard says:

When execution of a procedure completes, any pointer that remains defined and that is associated with a dummy
argument that has the TARGET attribute and is either a scalar or an assumed-shape array, remains associated
with the corresponding actual argument if the actual argument has the TARGET attribute and is not an array
section with a vector subscript.

And this is one more reason, not to use the TARGET attribute for dummy arguments, if you want to use the pointer to it outside of the scope. You might fail in several ways, and the compiler won’t help you. However, if you modify the code as shown below, you get a “safe” version, where the 2nd call triggers a compiler error (at least ifort and nag complain, gfortran 11.2 seems to let it pass, though).

program main
    implicit none

    integer, allocatable,target :: i(:)   ! Note the TARGET attribute here
    integer, allocatable :: indices(:)
    integer, pointer :: p(:)

    i = [1, 2, 3, 4, 5, 6]
    call sub(i, p)   ! Compiles without problem and using p is safe as it really points to i(:)
    indices = [1, 2, 5]
    call sub(i(indices), p)  ! Fails to compile (using p would not be safe anyway)
    print *, 'p(1) = ', p(1)

contains

    subroutine sub(x, p)
        integer, pointer, intent(in) :: x(:)    ! Note the POINTER attribute here
        integer, pointer, intent(out) :: p(:)

        p => x
    end subroutine
end program
1 Like

@certik, do you think LFortran can eventually consider alerting users in such a scenario if they seek that, perhaps in a pedantic mode?

1 Like

I think this is printing 64-bit addresses with a 32-bit function. It is probably returning just the low-order 32 bits of the address. That may be sufficient for this discussion, but in general information is being lost. Instead, the correct solution is to use c_loc(), but the return type c_ptr must then use transfer() in order to print its value.

Good point, I added it to the list here: Create pedantic/strict mode · Issue #363 · lfortran/lfortran · GitHub

2 Likes

@certik @FortranFan I think, it will be difficult to issue proper warnings / errors because most scenarios involving a dummy argument with a target attribute are standard conforming, so the compiler is not supposed to complain:

  • Passing actual argument with target attribute: OK
  • Passing actual argument without target attribute: OK
  • Passing array with vector indices as actual argument: OK
  • Associating pointers with the dummy argument and using them during the subroutine execution: OK for all possible actual arguments with or without target attribute.

The only scenario, which is dangerous and non standard-conforming is:

  • Passing an actual argument without the target attribute (or with vector subscripts) and using a pointer associated with the corresponding dummy (with the target attribute) after the subroutine has finished, as those pointers become undefined at that point.

But how do you want to catch this reliably at compile time? When you compile the routine, you don’t know, whether the actual argument will or will not have the target attribute. When you compile the caller, you don’t know, whether the routine you call will “store” a pointer associated with the dummy (e.g. by using the save attribute or storing it in a derived type instance variable, if it is a type bound procedure). You would have to compile caller and routine at the same time, in order to detect the dangerous case.

IMO, this is more a problem of the standard.One should probably simply declare pointers pointing to a dummy argument (or actually any object) with the target attribute undefined, once the scope of the declaration has finished. Without exceptions and with no dependency on what kind of dummy argument was passed. Then people would be forced to always use the pointer attribute for the dummy argument, if they wish to a use pointer beyond the lifetime of a subroutine. This would be didactically much clearer, would definitely cause less surprises and the right usage could be probably much easier detected at compile time.

@aradi There are several possible solutions to this:

  • Restrict the standard in “pedantic” mode, so that it is always 100% safe. This thread presented several such approaches.
  • Track all pointers and notice when they become dangling, which is not a compile time error, but a runtime error, but at least you still get an error (in Debug mode) instead of a segfault.
  • You can notice when the function stores the pointer which will still be alive when the function exits, and the compiler can keep track of this recursively, similar to the “pure” and “simple” attributes. If the function is not “pointer safe”, then the compiler could require the target attribute to be set for the argument.
  • There is also an option to track lifetimes at compile time like Rust does, but automatically, however that’s still more of a research problem I suspect.
1 Like

It seems like this would eliminate many legitimate uses for pointer assignments, while also, forcing programmers to use pointers more than they would otherwise. I personally prefer to avoid pointers as much as possible (e.g. using allocatables instead), so I would not like for the language to force me to go backwards in that respect.

@RonShepard No, it would eliminate only one single currently allowed scenario, which happens if all following 3 conditions are met:

  1. a pointer is associated with a dummy argument having the target attribute,
  2. and this pointer is used after the subroutine which defined the dummy argument has finished,
  3. and the passed actual argument has the target attribute.

This is a very special combination. It is also very error-prone, as forgetting to declare the target attribute for the passed actual argument (failing to meet condition 3 above) makes your code non-conforming according to the current standard.

Every other scenario would be valid (or invalid) exactly the same way, as it is now. It definitely won’t change in any way, how you use allocatable objects or allocated pointers.

Also, please note, that having a dummy argument with the pointer, intent(in) attribute does not force you to declare the pointer attribute for the passed actual argument. It is enough to declare the target attribute for the passed actual argument, so you still can pass in an allocatable object. So, there is absolutely no need to use pointers at the callers side.

@certik I like this idea a lot. That would effectively mean, that the routine signature becomes an additional property (similar to pure and simple), which can be tracked transitively. Edge cases might be hard to detect/decide though. (I could store the pointer in a module variable, but not use this module variable outside of this routine anywhere. I could pass it to a C-routine, etc.) But it is probably worth the effort and is the most robust one.

The runtime check is for sure possible, at least one compiler (NAG) does this already. (This is how I managed to realize and to get rid of the few target/pointer misuses in our code a few years ago…) The problem is, however, that if a library containing the subroutine with the target dummy is external and was compiled in production mode, you probably won’t be able catch the misuse with run-time checks, even if the caller itself is compiled in debug-mode.

1 Like