Skip to main content
GitHub

PRC02-F. Avoid implicit interfaces by using module procedures

Programmers shall encapsulate procedures within modules to ensure they have explicit interfaces.

In Fortran, a procedure defined externally (an "external procedure") has an implicit interface by default, unless an interface block is manually provided in the calling scope. When a procedure has an implicit interface, the compiler cannot verify that the arguments passed in the call match the dummy arguments defined in the procedure. The compiler assumes that the characteristics (types, ranks, etc.) and number of arguments provided by the caller are correct.

If the caller passes arguments that do not match the procedure's definition (e.g., passing a real variable to an integer argument), the program may experience:

  • Memory corruption occurs when reading or writing to undefined memory locations due to type size mismatches.
  • Incorrect results due to interpreting bit patterns as the wrong data type (e.g., interpreting floating-point bits as integers).
  • Runtime instability can lead to unpredictable behavior or crashes.

Encapsulating procedures within modules automatically generates an explicit interface for that procedure. When a module is used (through the use statement), the compiler has access to the full definition of the procedure and can enforce strict type checking at compile time.

Noncompliant Code Example

In this noncompliant example, the subroutine factorial is defined as an external procedure (not inside a module). The main program calls it. Because the interface is implicit, the compiler assumes the call is valid. However, the main program passes real arguments, while the subroutine expects integer arguments. This results in undefined behavior (incorrect calculation) at runtime.

Non-compliant code
! compute_factorial_lib.f90
pure subroutine compute_factorial(n, result)
    implicit none
    integer, intent(in) :: n
    integer, intent(out) :: result
    integer :: i

    result = 1
    do i = 2, n
        result = result * i
    end do
end subroutine compute_factorial
Non-compliant code
! main.f90
program test_implicit_interface
    use iso_fortran_env, only: real32
    implicit none
    external :: compute_factorial
    real(kind=real32) :: number, result

    number = 5
    call compute_factorial(number, result)
    print *, "Factorial of ", number, "is", result
end program test_implicit_interface

Compliant Solution

In this compliant solution, the subroutine is moved inside a module . The main program uses the module. This provides an explicit interface, allowing the compiler to detect the type mismatch during compilation. The developer is forced to fix the types in the main program to match the subroutine definition.

Compliant code
! coompute_factorial_lib.f90
module mod_compute_factorial
    implicit none
contains
    pure subroutine compute_factorial(n, result)
        implicit none
        integer, intent(in)  :: n
        integer, intent(out) :: result
        integer :: i

        result = 1
        do i = 2, n
            result = result * i
        end do
    end subroutine compute_factorial
end module mod_compute_factorial

:: ::code-block{quality="good"}

! main.f90
program test_explicit_interface
    use iso_fortran_env, only: real32
    use mod_compute_factorial, only: compute_factorial
    implicit none
    real(kind=real32) :: number, result

    number = 5
    call compute_factorial(number, result)
    print *, "Factorial of", number, "is", result
end program test_explicit_interface

::

Noncompliant Code Example

While it is possible to provide an explicit interface using an interface block, this approach is error-prone. If the external procedure definition changes, the interface block in the calling program must be manually updated. If they diverge, the compiler may not detect the error, leading to the same runtime issues as an implicit interface.

Non-compliant code
program manual_interface_risk
    implicit none

    ! Noncompliant: Manually defined interface
    interface
        subroutine calc(a)
            real, intent(in) :: a
        end subroutine calc
    end interface

    call calc(5.0)
end program manual_interface_risk

Risk Assessment

Calling procedures with implicit interfaces disables the compiler's ability to verify that arguments match the procedure's definition. This can lead to undefined behavior, including memory corruption, incorrect results, runtime instability, or crashes. The risk is not limited to mixed-type arithmetic: any mismatch in argument characteristics can cause errors that are undetectable at compile time.

RecommendationSeverityLikelihoodDetectableRepairablePriorityLevel
PRC02-FHighProbableYesNoP12L1

Attachments: