Skip to main content
GitHub

MSC03-F. Do not depend on undefined behavior

Undefined behavior refers to the execution of a nonconforming Fortran program in which the standard requirements are violated due to erroneous constructs or invalid use of data. When a program exhibits undefined behavior, a Fortran compiler is free to produce any result: correct output, incorrect output, crashes, silent data corruption, or behavior that varies between optimization levels or platforms.

The guidelines addressing undefined behavior are organized at two levels. MSC03-F provides the general requirement that programs must not rely on behavior that is undefined by the Fortran standard. Additional guidelines address specific and recurring causes of undefined behavior, including the use of uninitialized variables; invalid array subscripts; arithmetic operations whose results are not representable, such as integer overflow; and invalid arithmetic operations, such as division by zero. The specialized guidelines are organized under the correponding sections of the coding standard, for instance, DCL (Declarations and Initialization) and ARR (Arrays).

Noncompliant Code Example (Integer Overflow)

In this noncompliant example, a code attempts to detect integer overflow by checking whether the condition (.not. (a + 100_int32 > a)) is true. Apparently, the expression (a + 100_int32 > a) must always be true. However, the addition a + 100_int32 must first be evaluated. If its mathematical result is outside the range representable by integer(int32), the program has already violated the requirements of the Fortran standard. The program therefore cannot rely on the result of the overflowing expression to determine whether the overflow occurred.

Non-compliant code
program koo
  use iso_fortran_env, only : int32
  implicit none

  call foo(huge(0_int32))

contains

  subroutine foo(a)
    integer(int32), intent(in) :: a
    integer(int32) :: tmp

    if (.not. (a + 100_int32 > a)) then
      error stop "Integer overflow detected"
    end if
    
    tmp = a + 100_int32
    print *, tmp, a
  end subroutine foo

end program koo

Furthermore, for a standard-conforming execution in which a + 100_int32 is representable, adding the positive value 100_int32 necessarily produces a value greater than a. As a result, an optimizing compiler may reason that a + 100_int32 > a is always true and it can can eliminate the error stop branch entirely. The programmer cannot rely on the integer result becoming negative to detect overflow.

Implementation Details (Linux)

Given the noncomplaint code example, the following settings of gfortran and flang result in incorrect results for gfortran and in the intended program behavior for flang:

$ gfortran --version
GNU Fortran (Ubuntu 14.2.0-4ubuntu2~24.04.1) 14.2.0
$ gfortran -O2 msc03-f-ncce-integer-overflow.f90 -o msc03-f-ncce-integer-overflow
$ ./msc03-f-ncce-integer-overflow
 call subroutine foo with the int32 dummy argument huge(int32)
 a =   2147483647  ; tmp =  -2147483549
$ flang-new --version
Ubuntu flang version 20.1.2 (0ubuntu1~24.04.3)
$ flang-new -O2 msc03-f-ncce-integer-overflow.f90 -o msc03-f-ncce-integer-overflow
$ ./msc03-f-ncce-integer-overflow
 call subroutine foo with the int32 dummy argument huge(int32)
Fortran ERROR STOP: Integer overflow detected

Compliant Solution (Integer Overflow)

In the compliant solution, the condition to detect the integer overflow uses the intrinsic function huge(a) that returns the largest integer value representable by integer(int32). The expression a > huge(a) - 100_int32 determines whether adding 100_int32 would exceed the representable range without first performing the potentially overflowing addition a + 100_int32. Therefore, the arithmetic operation is guaranteed to produce a representable integer value that is conformant to the Fortran Standard.

Compliant code
program koo
  use iso_fortran_env, only : int32
  implicit none

  call foo(huge(0_int32))

contains

  subroutine foo(a)
    integer(int32), intent(in) :: a
    integer(int32) :: tmp

    if (a > huge(a) - 100_int32) then
      error stop "Overflow would occur"
    end if

    tmp = a + 100_int32
    print *, tmp, a
  end subroutine foo

end program koo

Implementation Details (Linux)

Given the complaint code example, the following settings of gfortran and flang result in the intended program behavior for both compilers:

$ gfortran --version
GNU Fortran (Ubuntu 14.2.0-4ubuntu2~24.04.1) 14.2.0
$ gfortran -O2 msc03-f-ce-integer-overflow.f90 -o msc03-f-ce-integer-overflow
$ ./msc03-f-ce-integer-overflow
 call subroutine foo with the int32 dummy argument huge(int32)
ERROR STOP Overflow would occur

Error termination. Backtrace:
#0  0x7fffff4c7e59 in ???
#1  0x7fffff4c8a71 in ???
#2  0x55555555520d in MAIN__
#3  0x5555555550be in main
$ flang-new --version
Ubuntu flang version 20.1.2 (0ubuntu1~24.04.3)
$ flang-new -O2 msc03-f-ce-integer-overflow.f90 -o msc03-f-ce-integer-overflow
$ ./msc03-f-ce-integer-overflow
 call subroutine foo with the int32 dummy argument huge(int32)
Fortran ERROR STOP: Overflow would occur

Noncompliant Code Example (Uninitialized Variable)

In this noncompliant example, the variable factor is not initialized along all possible execution paths of the IF-ELSEIF-ELSE construct. Although some branches assign a value to factor, at least one execution path leaves it undefined. Consequently, any subsequent reference to factor along that path accesses an undefined variable, resulting in undefined behavior under the Fortran standard.

Non-compliant code
program main
  use iso_fortran_env, only: real32
  implicit none

  real(kind=real32) :: array(4)
  array = [0.25, 0.25, 0.25, 0.25]

  print *, "Sum is:", transform_and_sum(array, 3)

contains

  pure real(kind=real32) function transform_and_sum(array, option)
    implicit none

    real(kind=real32), intent(in) :: array(:)
    integer, intent(in) :: option

    real(kind=real32) :: sum
    real(kind=real32) :: factor
    integer :: i

    sum = 0.0

    if (option == 1) then
      factor = 0.5              ! factor initialized in if branch
    else if (option == 2) then
      factor = 2.0              ! factor initialized in else-if branch
    end if                      ! missing initializatin of factor in else branch

    do i = 1, size(array, 1)
      sum = sum + array(i) * factor
    end do

    transform_and_sum = sum
  end function transform_and_sum

end program main

Implementation Details (Linux)

Consider the following compiler settings for gfortran and flang that do not report there is an else execution path where the variable factor is not initialized. As a result, the use of factor in the loop body is undefined and the behavior of the program is undefined.

$ gfortran --version
GNU Fortran (Ubuntu 14.2.0-4ubuntu2~24.04.1) 14.2.0
$ gfortran -Wall dcl03-f-ncce.f90
$ flang-new --version
Ubuntu flang version 20.1.2 (0ubuntu1~24.04.3)
$ flang-new -pedantic dcl03-f-ncce.f90

Compliant Solution (Uninitialized Variable)

In the compliant solution, the code prevents undefined behavior because factor is assigned a defined value before it is referenced, regardless of which branch is executed.

Compliant code
program main
  use iso_fortran_env, only: real32
  implicit none

  real(kind=real32) :: array(4)
  array = [0.25, 0.25, 0.25, 0.25]

  print *, "Sum is:", transform_and_sum(array, 3)

contains

  pure real(kind=real32) function transform_and_sum(array, option)
    implicit none

    real(kind=real32), intent(in) :: array(:)
    integer, intent(in) :: option

    real(kind=real32) :: sum
    real(kind=real32) :: factor
    integer :: i

    sum = 0.0

    if (option == 1) then
      factor = 0.5             ! factor initialized in if branch
    else if (option == 2) then
      factor = 2.0             ! factor initialized in else-if branch
    else
      factor = 1.0             ! factor initialized in else branch
    end if

    do i = 1, size(array, 1)
      sum = sum + array(i) * factor
    end do

    transform_and_sum = sum
  end function transform_and_sum

end program main

Risk Assessment

Undefined behavior can produce incorrect results, silent data corruption, crashes, or nondeterministic behavior that varies across compilers or platforms. Programmers should ensure that the code avoids undefined behavior in all cases.

RuleSeverityLikelihoodDetectableRepairablePriorityLevel
MSC03-FHighLikelyNoNoP9L2