Sage-Code Laboratory
index<--

Fortran Errors

Do not expect for a try...catch block that is available in modern languages. In standard Fortran, when there is an error, the program just stop working and automaticly exit. The only exception is write, where you can use err=label to make a jump in case of error.

Topics & Page bookmarks

The recent versions of Fortran have error management for specific operations that can fail, but you can recover from error and stop program from crashing. Modern Fortran has introduced four main areas for error capture:

  1. File handling and I/O
  2. IEEE floating point errors
  3. Dynamic allocation
  4. Command line operations
  5. Rising and handling errors

File handling and I/O

All the external file handling statements and I/O operations can take iostat and iomsg clauses. iostat is an integer which returns a non-zero value if there is an error, in which case, the character variable assigned to iomsg will return a brief error message.

example: error.f95
program error
  integer :: io_stat
  character (len=256) :: io_msg
  ! try to open a file that do not exist  
  open (file='myFile.dat', unit=10, &
        access = "append", status="old", &
        iostat=io_stat, iomsg=io_msg)
  ! analyze the error
  if (io_stat/=0) then
    write (*,*) "Open myFile.dat failed."
    write (*,*) "iostat = ", io_stat
    write (*,*) "iomsg = ", trim(io_msg)
  else
    write (*,*) "File was open succesfuly."
    close (unit=10)
  end if
end program
console
&ft;./error
 Open myFile.dat failed.
 iostat =            2
 iomsg = Cannot open file myFile.dat: No such file or directory

Notes:

  1. In modern fortran, the err=lable clause is still supported but should not be used.
  2. The non-zero integers and the messages are depending on the compiler.
  3. The intrinsic module, iso_fortran_env, gives access to two important values: iostat_end and iostat_eor.
  4. If iostat is non-zero, this is a signal of an error but the execution will not stop, except if you call stop statement explicit.

IEEE floating point errors

This is a big topic, but in essence modern Fortran provides access to three intrinsic modules: IEEE_arithmetic, IEEE_exceptions and IEEE_features. These features can be used to intercept errors such as divide by zero and overflow but at the expense of some performance.

The IEEE_features module controls access to the features the programmer may require, by use association in the scoping unit where the programmer places the use statement


subroutine test
    use, intrinsic :: ieee_features
    
    ! ...
end subroutine

Dynamic allocation

Modern Fortran allows run-time allocation and deallocation of arrays of any type, and a typical error might be to try to dynamically allocate an array so large that there is not enough memory, or an attempt to deallocate an array which is not already allocated. There are optional clauses stat and errmsg which can be used to prevent program failure and allow the programmer to take evasive action.


real, allocatable, dimension (:) :: x
integer :: my_stat
character (256) :: my_errmsg

allocate (x(100000000), stat=my_stat, errmsg=my_errmsg)
if (my_stat/=0) then
    write(*,*) 'Failed to allocate memory for x'
    write(*,*) 'stat: ', my_stat
    write(*,*) 'errmsg: ', trim(my_errmsg)
end if

Command line operations

In this example, below the external program "test.exe" is executed in a separated process. You can capture the error code and message in local variables and then use this information to take next action: Print the error message and stop the program.


integer :: my_cmdstat
character (256) :: my_cmdmsg

call execute_command_line('test.exe', cmdstat=my_cmdstat, cmdmsg=my_cmdmsg )
if (my_cmdstat/=0) stop

Rising and handling errors

The best practice in Fortran is to prevent errors in the first place by using preconditions. These are conditional statements you can make on arguments so that you do not encounter an error.

Though there is no official framework to handle errors in Fortran, there is third-party software that you can depend upon. Please investigate before implementing your own system.

Raising Error

To find runtime errors you need to create additional code for testing. This can be data driven test or unit test. You can run the unit test, fix the code and run again until you eliminate all possible errors.

The third practice is to enable a subprogram to raise an error. For this you must create an error result or output. You must detect the error and analize it using logic expressions in the main program. This is called "error handling".

Error Status

You can define error codes as numeric parameters. Usually we use uppercase letters to define these codes. A function can return an error code. A subroutine ca have an input/output argument that can signal an error and also an optional output argument that give you back the error message.

After the call, if the status return is present, the caller can decide how to handle the error. If it is absent, you can stop the program with an error message and status > 0.

This is is an effective design that lets the caller choose either a simple procedural method, or some form of error handling is necesary. The problem with this system is that you don't know exactly where the error was signaled. What line of code.

example: raise.f95
module raise
  public process
contains

  subroutine process(test, err)
    integer, intent(in) :: test
    integer, intent(inout) :: err
    if (test > 10) then
       err = ERR_INVALID; return
    else if (test < 5) then
       err = ERR_DEFAULT; return
    else
       do i=1, test 
          write (*,'(i3)', advance="no") i
       end do
       print *, ""
       err = ERR_NONE
    end if;
  end subroutine

end module
console: raise.f95
program main
  use raise
  ! define 3 error coddes
  integer, parameter :: &
    ERR_NONE = 0, &
    ERR_DEFAULT = 1, &
    ERR_INVALID = 2
  integer:: count, error
  write (*,'(a7)', advance="no") "count:"
  read *, count
  call process (count, error)
  if (error == ERR_NONE) then
     print *, "done. no errors!"
  else if (error == ERR_DEFAULT) then
     print *, "too small: < 5"
  else if (error == ERR_INVALID) then
     print *, "too large: > 10"
  end if;
contains

end program
console

>gfortran raise.f95 -o raise
>./raise
 count:1
 too small: < 5
>./raise
 count:7
  1  2  3  4  5  6  7 
 done. no errors!
> ./raise
 count:12
 too large: > 10
>./raise
 count:10
  1  2  3  4  5  6  7  8  9 10 
 done. no errors!
>

Derived Type

Modern fortran enable you to define derived type "error". This can have a code and a message. Any subroutine or function can return a variable of type "error". After calling the subroutine you can analyze the error object and hanlde the error.


type :: ErrorType
    integer :: code, line
    character(len=256) :: message
end type

External References

Lucking Tech Notes: Fortran Error Handling


Read next: Parallel Computing