diff --git a/Checks/PWR089/README.md b/Checks/PWR089/README.md new file mode 100644 index 0000000..f0d1e5f --- /dev/null +++ b/Checks/PWR089/README.md @@ -0,0 +1,180 @@ +# PWR089: Remove unexpected arguments from procedure calls + +### Issue + +Calling a procedure while supplying excess arguments (i.e., more actual +arguments than the procedure has dummy arguments) can lead to compilation +failures or, if not caught, undefined behavior at runtime. + +### Actions + +Ensure the number of actual arguments supplied at the call site matches the +number of dummy arguments in the procedure. If additional information is +needed, adapt the procedure accordingly; for example, by adding new dummy +arguments and handling them in the implementation. + +### Relevance + +When calling a Fortran procedure, the caller is expected to match the +procedure's dummy argument list. 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 exceed the dummy arguments, the program is exposed to undefined +behavior. + +A common misconception is that excess arguments are simply ignored. This may +appear to work for some procedures, but it is not guaranteed. More +specifically, some Fortran features can cause the compiler to supply hidden +values to the called procedure; for example, character lengths, or array +descriptors. If excess arguments are provided, those hidden values can be +misinterpreted, resulting in 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 logs diagnostics for a simulation data field. The called +procedure expects three arguments, but the caller accidentally passes an +additional verbosity argument: + +```fortran showLineNumbers +! io.f90 +subroutine logField(name, step, value) + use iso_fortran_env, only: real32 + implicit none + + character(*), intent(in) :: name + integer, intent(in) :: step + real(kind=real32), intent(in) :: value + + write(*, *) trim(name), " step=", step, " value=", value +end subroutine logField +``` + +```fortran {6,10,17} showLineNumbers +! example.f90 +program call_with_excess_arguments + use iso_fortran_env, only: real32 + implicit none + + external :: logField + character(len=20) :: name + integer :: step + real(kind=real32) :: val + logical :: verbose + + name = "field" + step = 10 + val = 3.14 + verbose = .true. + + call logField(name, step, val, verbose) +end program call_with_excess_arguments +``` + +Because `logField()` is an `external` procedure, the call uses an implicit +interface. Compilers will typically allow this program to compile despite the +argument count mismatch, which can corrupt the call frame if hidden information +is passed for the `character` argument. In fact, a crash is triggered at +runtime: + +```txt +$ gfortran --version +GNU Fortran (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +$ gfortran io.f90 example.f90 +$ ./a.out + +Program received signal SIGSEGV: Segmentation fault - invalid memory reference. + +Backtrace for this error: +#0 0x76abe9223e59 in ??? +#1 0x76abe9222e75 in ??? +#2 0x76abe8e4532f in ??? + at ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0 +#3 0x76abe94d20f9 in ??? +#4 0x76abe94d2194 in ??? +#5 0x63e416093238 in ??? +#6 0x63e416093359 in ??? +#7 0x63e416093395 in ??? +#8 0x76abe8e2a1c9 in __libc_start_call_main + at ../sysdeps/nptl/libc_start_call_main.h:58 +#9 0x76abe8e2a28a in __libc_start_main_impl + at ../csu/libc-start.c:360 +#10 0x63e4160930f4 in ??? +#11 0xffffffffffffffff in ??? +``` + +A simple solution is to remove the excess `verbose` argument from the procedure +call: + +```fortran {15} showLineNumbers +! solution.f90 +program correct_call + use iso_fortran_env, only: real32 + implicit none + + external :: logField + character(len=20) :: name + integer :: step + real(kind=real32) :: val + + name = "field" + step = 10 + val = 3.14 + + call logField(name, step, val) +end program correct_call +``` + +Now, the program will behave correctly: + +```txt +$ gfortran io.f90 solution.f90 +$ ./a.out + field step= 10 value= 3.14000010 +``` + +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 + +- [PWR089 + examples](https://github.com/codee-com/open-catalog/tree/main/Checks/PWR089/) + +### 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/PWR089/benchmark/README.txt b/Checks/PWR089/benchmark/README.txt new file mode 100644 index 0000000..23dada9 --- /dev/null +++ b/Checks/PWR089/benchmark/README.txt @@ -0,0 +1,3 @@ +Supplying excess arguments is undefined behavior, even if the affected code +happens to work correctly. Thus, performance benchmarking isn't meaningful +since there is no technically correct baseline to measure against. diff --git a/Checks/PWR089/example.f90 b/Checks/PWR089/example.f90 new file mode 100644 index 0000000..21212cf --- /dev/null +++ b/Checks/PWR089/example.f90 @@ -0,0 +1,21 @@ +! PWR089: Remove unexpected arguments from procedure calls + +! NOT-PWR068: External procedure used to demonstrate how compilers can't catch +! the argument count mismatch +program call_with_excess_arguments + use iso_fortran_env, only: real32 + implicit none + + external :: logField + character(len=20) :: name + integer :: step + real(kind=real32) :: val + logical :: verbose + + name = "field" + step = 10 + val = 3.14 + verbose = .true. + + call logField(name, step, val, verbose) +end program call_with_excess_arguments diff --git a/Checks/PWR089/io.f90 b/Checks/PWR089/io.f90 new file mode 100644 index 0000000..c3e6c44 --- /dev/null +++ b/Checks/PWR089/io.f90 @@ -0,0 +1,12 @@ +! PWR089: Remove unexpected arguments from procedure calls + +subroutine logField(name, step, value) + use iso_fortran_env, only: real32 + implicit none + + character(*), intent(in) :: name + integer, intent(in) :: step + real(kind=real32), intent(in) :: value + + write(*, *) trim(name), " step=", step, " value=", value +end subroutine logField diff --git a/Checks/PWR089/solution.f90 b/Checks/PWR089/solution.f90 new file mode 100644 index 0000000..d5b899b --- /dev/null +++ b/Checks/PWR089/solution.f90 @@ -0,0 +1,19 @@ +! PWR089: Remove unexpected arguments from 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 :: logField + character(len=20) :: name + integer :: step + real(kind=real32) :: val + + name = "field" + step = 10 + val = 3.14 + + call logField(name, step, val) +end program correct_call diff --git a/README.md b/README.md index 508e425..d398bdd 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ designed to demonstrate: | [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) | | | ✓ | | | +| [PWR089](Checks/PWR089/) | Remove unexpected arguments from 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) | | ✓ | ✓ | ✓ | |