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.
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.
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.
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.
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.
| Rule | Severity | Likelihood | Detectable | Repairable | Priority | Level |
|---|---|---|---|---|---|---|
| MSC03-F | High | Likely | No | No | P9 | L2 |