Search code examples
fortranstandardsundefined-behavioroptional-parameterssanitizer

Is passing an absent assumed-shape array for an optional argument of another procedure allowed?


In this minimal example, is it allowed to pass the optional dummy argument y of test_wrapper that may not be present as actual argument for the corresponding optional dummy argument y of test?

program main
    implicit none
    real :: x = 5.0
    call test_wrapper(x)

contains
    subroutine test_wrapper(x, y)
        implicit none
        real, intent(in) :: x
        real, dimension(:), intent(out), optional :: y
        call test(x, y)
    end subroutine test_wrapper

    subroutine test(x, y)
        implicit none
        real, intent(in) :: x
        real, dimension(:), intent(out), optional :: y
        if (present(y)) then
            y = x
        end if
    end subroutine test
end program

UndefinedBehaviourSanitizer raises an error, indicating that it is not: https://godbolt.org/z/nKj1h6G9r

In this Fortran standards document (Section 15.5.2.12, "Argument presence and restrictions on arguments not present" on page 311) it says:

  1. An optional dummy argument that is not present is subject to the following restrictions.
    1. If it is a data object, it shall not be referenced or be defined. If it is of a type that has default initialization, the initialization has no effect.
    2. [...]
    3. [...]
    4. [...]
    5. A designator with it as the base object and with one or more subobject selectors shall not be supplied as an actual argument.
    6. [...]
    7. If it is a pointer, it shall not be allocated, deallocated, nullified, pointerassigned, or supplied as an actual argument corresponding to an optional nonpointer dummy argument.
    8. If it is allocatable, it shall not be allocated, deallocated, or supplied as an actual argument corresponding to an optional nonallocatable dummy argument.
    9. [...]
  2. Except as noted in the list above, it may be supplied as an actual argument corresponding to an optional dummy argument, which is then also considered not to be present.

I'm struggling to read the standardese in that list, so perhaps one of the items in it that I don't fully understand prohibits this for assumed-shape arrays? But to my mind, none of the restrictions would apply for this case.

But interestingly, UBSan only seems to raise the error if using dimension(:), i.e. if y is an assumed-shape array. Anything else like dimension(2), dimension(n) with an added size parameter n, allocatable, pointer or nothing do not seem to trigger UBSan.


Solution

  • There is no additional restriction on the use of assumed-shape absent optional dummy arguments. It is allowed to have a not-present assumed-shape array argument as an actual argument for an optional dummy argument in another procedure unless another restriction prevents it. (That subsequent dummy argument will be treated as not present.)

    As noted, none of the restrictions listed mentions "assumed shape". In particular, none of those you quote (as Ian Bush comments) applies in this case. Which leaves "except as noted in the list above, it may be supplied..." being permissive.

    If you want to check further, the assumed-shape argument y of each subroutine is an ordinary dummy variable (and subject to the rules of F2018 15.5.2.4).

    gfortran 7 does not complain. It may be relevant that this version does not understand -std=f2018.

    After reporting the issue on the GCC bug tracker it has been confirmed that this was indeed a bug in gfortran that UBSan was correct to reveal. A patch that fixes the issue was submitted in the development of GCC 14 and backported for the GCC 13.3 release.

    For completeness, let's run through why the restrictions (all, not just the ones quoted in the question) don't apply. I won't quote the restrictions, so the curious will need to look up the text of those which aren't in the question.

    1. Neither y is referenced or defined when not present (appearing as an actual argument isn't referencing or defining).
    2. Neither y appears in a pointer assignment (and neither is a pointer).
    3. Neither y is a procedure or procedure pointer.
    4. y of test_wrapper is not used as an actual argument for a non-optional dummy argument; y of test is not used as an actual argument.
    5. In test_wrapper the actual argument is y itself, not a subobject of y; y in test is not used as an actual argument.
    6. Although arrays, neither y is used an actual argument in reference to an elemental procedure.
    7. Neither y is a pointer.
    8. Neither y is allocatable.
    9. Neither y has a length type parameter (and especially not one inquired of).
    10. Neither y is used as a selector.
    11. Neither y is used in a procedure designator.
    12. Neither y is used in a procedure component reference.

    Versions of gfortran before 13.3 (prior to the patch) show the same issues for the simpler program below:

    program main
        implicit none
        call test_wrapper
    
    contains
        subroutine test_wrapper(y)
            real, dimension(1), intent(out), optional :: y
            call test(y)
        end subroutine test_wrapper
    
        subroutine test(y)
            real, dimension(:), intent(out), optional :: y
            if (present(y)) y=0  ! Used to silence unrelated warning
        end subroutine test
    end program