diff --git a/Checks/PWR088/README.md b/Checks/PWR088/README.md new file mode 100644 index 0000000..9bf3137 --- /dev/null +++ b/Checks/PWR088/README.md @@ -0,0 +1,149 @@ +# PWR088: Add missing arguments to procedure calls + +### Issue + +Calling a procedure while omitting one or more required arguments (i.e., dummy +arguments that are not `optional`) can lead to compilation failures or, if not +caught, undefined behavior at runtime. + +### Actions + +Ensure all arguments required by the procedure (i.e., not marked `optional`) +are supplied at the call site. If a call with fewer arguments is actually +needed, adapt the procedure accordingly; for example, by converting arguments +to `optional` and handling the missing scenarios in the implementation. + +### Relevance + +When calling a Fortran procedure, the caller is expected to supply all +mandatory arguments. If an explicit procedure interface is available at the +call site, compilers can typically detect and report a mismatch in the number +of supplied arguments during compilation. + +However, Fortran also allows calling procedures without information about the +expected arguments; in such cases, an _implicit interface_ is used. The caller +effectively passes a list of memory addresses, which the called procedure +interprets according to its dummy argument declarations. If the actual +arguments are fewer than the dummy arguments, the result is undefined behavior +and may manifest as incorrect results, "random" behavior, or even crashes. + +> [!TIP] +> To enhance code safety and reliability, ensure procedures provide +> explicit interfaces to their callers. Check the [PWR068 entry](../PWR068/) +> for more details! + +### Code examples + +The following example advances a simulation time by `numberSteps * dt`. The +procedure expects four arguments, but the caller accidentally passes only +three: + +```fortran {7,11} showLineNumbers +! simulation.f90 +pure subroutine updateSimulationTime(numberSteps, dt, prevTime, newTime) + use iso_fortran_env, only: real32 + implicit none + + integer, intent(in) :: numberSteps + real(kind=real32), intent(in) :: dt + real(kind=real32), intent(in) :: prevTime + real(kind=real32), intent(out) :: newTime + + newTime = prevTime + numberSteps * dt +end subroutine updateSimulationTime +``` + +```fortran {6,13} showLineNumbers +! example.f90 +program call_with_missing_arguments + use iso_fortran_env, only: real32 + implicit none + + external :: updateSimulationTime + integer :: numberSteps + real(kind=real32) :: prevTime, newTime + + numberSteps = 10 + prevTime = 0.0 + + call updateSimulationTime(numberSteps, prevTime, newTime) + print *, "New time = ", newTime +end program call_with_missing_arguments +``` + +Because `updateSimulationTime()` is an `external` procedure, the call uses an +implicit interface. Compilers will typically allow this program to compile +despite the argument count mismatch, producing an incorrect result at runtime: + +```txt +$ gfortran --version +GNU Fortran (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +$ gfortran simulation.f90 example.f90 +$ ./a.out + New time = -2.23126931E+24 +``` + +A simple solution is to pass the missing `dt` in the procedure call: + +```fortran {8,11,14} showLineNumbers +! solution.f90 +program correct_call + use iso_fortran_env, only: real32 + implicit none + + external :: updateSimulationTime + integer :: numberSteps + real(kind=real32) :: dt, prevTime, newTime + + numberSteps = 10 + dt = 0.1 + prevTime = 0.0 + + call updateSimulationTime(numberSteps, dt, prevTime, newTime) + print *, "New time = ", newTime +end program correct_call +``` + +Now, the program will produce the correct result: + +```txt +$ gfortran simulation.f90 solution.f90 +$ ./a.out + New time = 1.00000000 +``` + +Ultimately, it is recommended to encapsulate all procedures within modules so +that callers have explicit interfaces. This enables the compiler to verify the +provided arguments against the actual dummy arguments, preventing +difficult-to-diagnose runtime bugs. + +> [!TIP] +> Check the [PWR068 entry](../PWR068/) for more details on implicit and +> explicit interfaces! + +### Related resources + +- [PWR088 + examples](https://github.com/codee-com/open-catalog/tree/main/Checks/PWR088/) + +### References + +- ["Implicit +Interfaces"](https://people.cs.vt.edu/~asandu/Courses/MTU/CS2911/fortran_notes/node44.html), +Adrian Sandu. [last checked February 2026] + +- ["More on Implicit +Interfaces"](https://people.cs.vt.edu/~asandu/Courses/MTU/CS2911/fortran_notes/node181.html), +Adrian Sandu. [last checked February 2026] + +- ["Explicit +Interfaces"](https://people.cs.vt.edu/~asandu/Courses/MTU/CS2911/fortran_notes/node182.html), +Adrian Sandu. [last checked February 2026] + +- ["Doctor Fortran Gets +Explicit!"](https://web.archive.org/web/20130803094211/http://software.intel.com/en-us/forums/topic/275071), +Steve Lionel. [last checked February 2026] + +- ["Doctor Fortran Gets Explicit - Again! +"](https://web.archive.org/web/20130113070703/http://software.intel.com/en-us/blogs/2012/01/05/doctor-fortran-gets-explicit-again), +Steve Lionel. [last checked February 2026] diff --git a/Checks/PWR088/benchmark/README.txt b/Checks/PWR088/benchmark/README.txt new file mode 100644 index 0000000..3f4b906 --- /dev/null +++ b/Checks/PWR088/benchmark/README.txt @@ -0,0 +1,3 @@ +Omitting required arguments leads to undefined behavior. Thus, performance +benchmarking isn't meaningful since there isn't a correct baseline to measure +against. diff --git a/Checks/PWR088/example.f90 b/Checks/PWR088/example.f90 new file mode 100644 index 0000000..5bd6746 --- /dev/null +++ b/Checks/PWR088/example.f90 @@ -0,0 +1,18 @@ +! PWR088: Add missing arguments to procedure calls + +! NOT-PWR068: External procedure used to demonstrate how compilers can't catch +! the argument count mismatch +program call_with_missing_arguments + use iso_fortran_env, only: real32 + implicit none + + external :: updateSimulationTime + integer :: numberSteps + real(kind=real32) :: prevTime, newTime + + numberSteps = 10 + prevTime = 0.0 + + call updateSimulationTime(numberSteps, prevTime, newTime) + print *, "New time = ", newTime +end program call_with_missing_arguments diff --git a/Checks/PWR088/simulation.f90 b/Checks/PWR088/simulation.f90 new file mode 100644 index 0000000..bd0ebfd --- /dev/null +++ b/Checks/PWR088/simulation.f90 @@ -0,0 +1,13 @@ +! PWR088: Add missing arguments to procedure calls + +pure subroutine updateSimulationTime(numberSteps, dt, prevTime, newTime) + use iso_fortran_env, only: real32 + implicit none + + integer, intent(in) :: numberSteps + real(kind=real32), intent(in) :: dt + real(kind=real32), intent(in) :: prevTime + real(kind=real32), intent(out) :: newTime + + newTime = prevTime + numberSteps * dt +end subroutine updateSimulationTime diff --git a/Checks/PWR088/solution.f90 b/Checks/PWR088/solution.f90 new file mode 100644 index 0000000..2a9c7ee --- /dev/null +++ b/Checks/PWR088/solution.f90 @@ -0,0 +1,19 @@ +! PWR088: Add missing arguments to procedure calls + +! NOT-PWR068: External procedure used to demonstrate how compilers can't catch +! the argument count mismatch +program correct_call + use iso_fortran_env, only: real32 + implicit none + + external :: updateSimulationTime + integer :: numberSteps + real(kind=real32) :: dt, prevTime, newTime + + numberSteps = 10 + dt = 0.1 + prevTime = 0.0 + + call updateSimulationTime(numberSteps, dt, prevTime, newTime) + print *, "New time = ", newTime +end program correct_call diff --git a/README.md b/README.md index 609872f..508e425 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ designed to demonstrate: | [PWR080](Checks/PWR080/) | Conditionally initialized variables can lead to undefined behavior | correctness, portability, security | [CWE-758](https://cwe.mitre.org/data/definitions/758.html), [CWE-908](https://cwe.mitre.org/data/definitions/908.html), [CWE-909](https://cwe.mitre.org/data/definitions/909.html) | [6.13](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.22](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.56](https://j3-fortran.org/doc/year/23/23-241.pdf) | [EXP33-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP33-C.+Do+not+read+uninitialized+memory), [EXP34-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP34-C.+Do+not+dereference+null+pointers), [MSC15-C](https://wiki.sei.cmu.edu/confluence/display/c/MSC15-C.+Do+not+depend+on+undefined+behavior) | [EXP53-CPP](https://wiki.sei.cmu.edu/confluence/spaces/cplusplus/pages/88046609/EXP53-CPP.+Do+not+read+uninitialized+memory) | ✓ | ✓ | ✓ | | | [PWR081](Checks/PWR081/) | Uninitialized output arguments can lead to undefined behavior | correctness, portability, security | [CWE-758](https://cwe.mitre.org/data/definitions/758.html), [CWE-908](https://cwe.mitre.org/data/definitions/908.html), [CWE-909](https://cwe.mitre.org/data/definitions/909.html) | [6.13](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.22](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.56](https://j3-fortran.org/doc/year/23/23-241.pdf) | [EXP33-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP33-C.+Do+not+read+uninitialized+memory), [EXP34-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP34-C.+Do+not+dereference+null+pointers), [MSC15-C](https://wiki.sei.cmu.edu/confluence/display/c/MSC15-C.+Do+not+depend+on+undefined+behavior) | | | ✓ | | | | [PWR083](Checks/PWR083/) | Match the types of dummy and actual arguments in procedure calls | correctness, security | [CWE-628](https://cwe.mitre.org/data/definitions/628.html) | [6.11](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.32](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.53](https://j3-fortran.org/doc/year/23/23-241.pdf) | [EXP37-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP37-C.+Call+functions+with+the+correct+number+and+type+of+arguments) | | | ✓ | | | +| [PWR088](Checks/PWR088/) | Add missing arguments to procedure calls | correctness, security | [CWE-628](https://cwe.mitre.org/data/definitions/628.html) | [6.32](https://j3-fortran.org/doc/year/23/23-241.pdf) | [EXP37-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP37-C.+Call+functions+with+the+correct+number+and+type+of+arguments) | | | ✓ | | | | [PWD002](Checks/PWD002/) | Unprotected multithreading reduction operation | correctness, security | [CWE-366](https://cwe.mitre.org/data/definitions/366.html), [CWE-820](https://cwe.mitre.org/data/definitions/820.html) | [6.61](https://j3-fortran.org/doc/year/23/23-241.pdf) | [CON07-C](https://wiki.sei.cmu.edu/confluence/display/c/CON07-C.+Ensure+that+compound+operations+on+shared+variables+are+atomic), [CON43-C](https://wiki.sei.cmu.edu/confluence/display/c/CON43-C.+Do+not+allow+data+races+in+multithreaded+code) | | ✓ | ✓ | ✓ | | | [PWD003](Checks/PWD003/) | Missing array range in data copy to the GPU | correctness, security | [CWE-131](https://cwe.mitre.org/data/definitions/131.html), [CWE-758](https://cwe.mitre.org/data/definitions/758.html) | [6.56](https://j3-fortran.org/doc/year/23/23-241.pdf) | [MSC15-C](https://wiki.sei.cmu.edu/confluence/display/c/MSC15-C.+Do+not+depend+on+undefined+behavior) | | ✓ | ✓ | ✓ | | | [PWD004](Checks/PWD004/) | Out-of-memory-bounds array access | correctness, security | [CWE-125](https://cwe.mitre.org/data/definitions/125.html), [CWE-787](https://cwe.mitre.org/data/definitions/787.html) | [6.8](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.9](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.10](https://j3-fortran.org/doc/year/23/23-241.pdf) | [ARR30-C](https://wiki.sei.cmu.edu/confluence/display/c/ARR30-C.+Do+not+form+or+use+out-of-bounds+pointers+or+array+subscripts) | | ✓ | ✓ | ✓ | |