From 1dff15cfeb9550b3042be7538e3b2c697509e271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20G=2E=20Dieste?= Date: Tue, 24 Mar 2026 13:04:17 +0100 Subject: [PATCH] PWR068: Rewrite to highlight the correctness goal The current PWR068 leans too hard on "put procedures inside modules". This masks the actual underlying issue, which is calling procedures through implicit interfaces. This rewrite highlights the problem of implicit interfaces first. Then, "module procedures" are presented as the preferred approach to solve the problem, while also clarifying that other mechanisms might be a better fit for other specific scenarios (internal procedures leverage host association, C/C++ interoperable procedures require `interface` blocks, etc.). --- Checks/PWR068/README.md | 174 +++++++++++------- Checks/PWR068/benchmark/example.f90 | 4 +- Checks/PWR068/benchmark/solution.f90 | 4 +- Checks/PWR068/example.f90 | 4 +- Checks/PWR068/example_factorial.f90 | 4 +- Checks/PWR068/solution.f90 | 4 +- Checks/PWR068/solution_mod_factorial.f90 | 4 +- Checks/PWR068/solution_with_type_mismatch.f90 | 4 +- README.md | 2 +- 9 files changed, 125 insertions(+), 79 deletions(-) diff --git a/Checks/PWR068/README.md b/Checks/PWR068/README.md index 93461126..87702f9d 100644 --- a/Checks/PWR068/README.md +++ b/Checks/PWR068/README.md @@ -1,43 +1,77 @@ -# PWR068: Encapsulate procedures within modules to avoid the risks of calling implicit interfaces +# PWR068: Call procedures through explicit interfaces, preferably as module procedures ### Issue Calling a procedure without an explicit interface prevents the compiler from -verifying argument compatibility, increasing the risk of difficult-to-diagnose -runtime bugs. +verifying compatibility between the actual arguments at the call site and the +procedure's dummy arguments. As a result, argument mismatches may compile +without warning and later surface as incorrect results and +difficult-to-diagnose runtime bugs. ### Actions -To enhance code safety and reliability, encapsulate procedures within modules -to automatically provide an explicit interface at the point of the call. +To enhance code safety and reliability, ensure that procedure calls use +explicit interfaces. + +For most Fortran code, prefer defining procedures inside modules. Module +procedures automatically provide explicit interfaces to callers through `use` +association mechanisms, making it the safest and most maintainable approach for +most cases. + +Other mechanisms can also provide explicit interfaces when module procedures +are not the right fit. For example: + +- Internal procedures also provide explicit interfaces automatically through + host association. +- Explicit `interface` blocks are helpful for C/C++ interoperable procedures, + dummy procedures, and procedure pointers. ### Relevance -Fortran allows procedures to be called without explicit information about the -number of expected arguments, order, or properties such as their type. In such -cases, an _implicit interface_ is used. The caller simply provides a list of -memory addresses, which the called procedure assumes point to variables -matching the dummy arguments. This can easily lead to issues such as: +Fortran allows procedure calls even when the caller has no information about +the number, order, or properties of the dummy arguments (`type`, `kind`, +`rank`, attributes, etc.). In such cases, the call uses an _implicit +interface_. + +With an implicit interface, the caller simply provides a list of memory +addresses, and the called procedure interprets them according to its dummy +argument declarations. Because the compiler does not know enough about the +procedure at the call site, it isn't able to detect problems such as: + +- **Wrong number of arguments:** + - Missing actual arguments may cause the procedure to access unrelated + memory, leading to undefined behavior. + - Excess actual arguments may also lead to undefined behavior; for example, + by interfering with hidden information passed by the compiler, such as + descriptors for array arguments. + +- **Incompatible argument characteristics:** Passing actual arguments whose + characteristics are incompatible with those of the dummy arguments. For + example, passing a `real` where an `integer` is expected, or using a `real32` + `kind` where `real64` is required. + +- **Swapped arguments:** Accidentally changing the order of arguments can also + introduce incompatibilities even when the number of arguments is correct. + +> [!TIP] +> To learn more about these issues and the importance of explicit interfaces, +> see [PWR083](../PWR083/), [PWR088](../PWR088/), and [PWR089](../PWR089/). -- **Type mismatches:** Passing variables of one type (e.g., `real`) as another -type (e.g., `integer`) causes errors due to different internal representations. +In contrast, when a procedure call is made through an explicit interface, the +compiler can verify argument compatibility at compile time, catching any errors +before they reach runtime. -- **Missing arguments:** - - **Input arguments:** Omitted arguments are initialized to undefined - values, resulting in unpredictable behavior. - - **Output arguments:** Writing omitted arguments results in invalid memory - accesses, potentially crashing the program. +For most Fortran code, the best way to avoid implicit interfaces is to define +procedures inside modules. The interface is automatically derived from the +procedure definition, remaining consistent at all times. -In contrast, a procedure with an explicit interface informs the compiler about -the expected arguments, allowing it to perform the necessary checks at the -point of the call during compile-time. The preferred approach to ensure a -procedure has an explicit interface is to encapsulate it within a module, as -illustrated below. +Alternatives for less common scenarios are discussed after the code examples +below. ### Code examples -The following program calculates the factorial of a number. To simulate a real -project with multiple source files, the main program and the factorial +The following program calculates the factorial of a number. To reflect a common +project layout with multiple source files, the main program and the factorial procedure are in different files: ```fortran {4,5} showLineNumbers @@ -85,10 +119,10 @@ The compiler cannot catch this bug during compilation because the called procedure has an implicit interface: it is an `external` element defined in another source file. -A simple solution is to encapsulate the procedure within a module. This informs -the compiler about the exact location where the called subroutine is defined, -enabling it to verify the provided arguments against the actual dummy -arguments. +A simple solution is to encapsulate the procedure within a module. This makes +an explicit interface available to callers through `use` association +mechanisms, allowing the compiler to verify the provided arguments against the +actual dummy arguments. Moving the `factorial` subroutine to a module is as simple as: @@ -151,32 +185,47 @@ $ ./a.out Factorial of 5 is 120 ``` -> [!NOTE] -> The previous example demonstrates how calls to `external` procedures are -> performed through implicit interfaces. The same problem would occur if -> `factorial` were an implicitly declared procedure, as shown in the following -> example: -> -> ```fortran {5} showLineNumbers -> program test_implicit_interface -> use iso_fortran_env, only: real32 -> real(kind=real32) :: number, result -> -> number = 5 -> call factorial(number, result) -> print *, "Factorial of", number, "is", result -> end program test_implicit_interface -> ``` +The problem is not limited to `external` procedures. Any call made without an +explicit interface carries the same risk. For example, in the following program +there is no `implicit none` statement, so `factorial` is an implicit entity +that also lacks an explicit interface when called: + +```fortran {5} showLineNumbers +program test_implicit_interface + use iso_fortran_env, only: real32 + real(kind=real32) :: number, result + + number = 5 + call factorial(number, result) + print *, "Factorial of", number, "is", result +end program test_implicit_interface +``` + +> [!TIP] +> Internal procedures also provide explicit interfaces automatically through +> host association. They are a good choice when a procedure is only needed +> within a single host procedure: > -> Note the absence of `implicit none`, allowing the symbol `factorial` to be -> interpreted as an implicitly declared entity. +> ```fortran showLineNumbers +> subroutine sub() +> call internal() +> contains +> subroutine internal() +> ! statements... +> end subroutine internal +> end subroutine sub +> ``` > [!WARNING] -> It's possible to manually define explicit interfaces using the `interface` -> construct at the call site. However, this approach introduces risks. The -> procedure's definition must be duplicated, but there's no mechanism to ensure -> this replica matches the actual definition of the original procedure, which -> can easily lead to errors: +> A handwritten `interface` block can provide an explicit interface, but also +> introduces risks. The interface must duplicate the target procedure's +> definition, but the compiler does not verify whether this replica matches the +> actual specification of the procedure or not. +> +> In this example, the interface declared manually for `factorial` is +> incorrect; the dummy arguments are `real` instead of `integer`. The compiler +> will accept the call because it is only checked against the handwritten +> `interface`, producing an incorrect result during execution: > > ```fortran {6,7} showLineNumbers > program test_implicit_interface @@ -198,23 +247,20 @@ Factorial of 5 is 120 > end program test_implicit_interface > ``` > -> In this example, the manually declared interface for `factorial` is -> incorrect; the dummy arguments are declared as `real` instead of `integer`. -> This error won't be caught at compile time, and will still result in an -> unexpected output during execution. +> Generally, manual `interface` blocks should be reserved for cases where +> module procedures aren't applicable, such as calling interoperable C/C++ +> procedures, dummy procedures, and procedure pointers. > [!TIP] -> When interoperating between Fortran and C/C++, it's necessary to manually -> define explicit interfaces for the C/C++ procedures to call. Although this is -> not a perfect solution, since there are no guarantees that these interfaces -> will match the actual C/C++ procedures, it's still best to make the -> interfaces as explicit as possible. This includes specifying details such as -> argument intents, to help the Fortran compiler catch early as many issues as -> possible. +> Many modern Fortran features require explicit interfaces, including +> assumed-shape arrays, optional arguments, procedure arguments, and more. +> Avoiding implicit interfaces is therefore also a prerequisite for writing +> robust modern Fortran. > [!TIP] -> If modifying legacy code is not feasible, create a module procedure that wraps -> the legacy procedure as an indirect approach to ensure argument compatibility. +> If modifying legacy code is not feasible, consider creating module procedures +> that wrap the legacy ones. This provides safer interfaces for call sites +> while preserving existing implementations intact. ### Related resources diff --git a/Checks/PWR068/benchmark/example.f90 b/Checks/PWR068/benchmark/example.f90 index af4e128a..49008ee3 100644 --- a/Checks/PWR068/benchmark/example.f90 +++ b/Checks/PWR068/benchmark/example.f90 @@ -1,5 +1,5 @@ -! PWR068: Encapsulate procedures within modules to avoid the risks of calling -! implicit interfaces +! PWR068: Call procedures through explicit interfaces, preferably as module +! procedures function euclidean_distance_f(x1, y1, x2, y2) use iso_c_binding, only : c_double diff --git a/Checks/PWR068/benchmark/solution.f90 b/Checks/PWR068/benchmark/solution.f90 index 84ec05d3..42ee5d4d 100644 --- a/Checks/PWR068/benchmark/solution.f90 +++ b/Checks/PWR068/benchmark/solution.f90 @@ -1,5 +1,5 @@ -! PWR068: Encapsulate procedures within modules to avoid the risks of calling -! implicit interfaces +! PWR068: Call procedures through explicit interfaces, preferably as module +! procedures module vector_utils implicit none diff --git a/Checks/PWR068/example.f90 b/Checks/PWR068/example.f90 index 26d6e5f4..659f9ac3 100644 --- a/Checks/PWR068/example.f90 +++ b/Checks/PWR068/example.f90 @@ -1,5 +1,5 @@ -! PWR068: Encapsulate procedures within modules to avoid the risks of calling -! implicit interfaces +! PWR068: Call procedures through explicit interfaces, preferably as module +! procedures program test_implicit_interface use iso_fortran_env, only: real32 diff --git a/Checks/PWR068/example_factorial.f90 b/Checks/PWR068/example_factorial.f90 index b58735ab..132b403f 100644 --- a/Checks/PWR068/example_factorial.f90 +++ b/Checks/PWR068/example_factorial.f90 @@ -1,5 +1,5 @@ -! PWR068: Encapsulate procedures within modules to avoid the risks of calling -! implicit interfaces +! PWR068: Call procedures through explicit interfaces, preferably as module +! procedures pure subroutine factorial(number, result) implicit none diff --git a/Checks/PWR068/solution.f90 b/Checks/PWR068/solution.f90 index a841718e..87ac4859 100644 --- a/Checks/PWR068/solution.f90 +++ b/Checks/PWR068/solution.f90 @@ -1,5 +1,5 @@ -! PWR068: Encapsulate procedures within modules to avoid the risks of calling -! implicit interfaces +! PWR068: Call procedures through explicit interfaces, preferably as module +! procedures program test_explicit_interface use mod_factorial, only: factorial diff --git a/Checks/PWR068/solution_mod_factorial.f90 b/Checks/PWR068/solution_mod_factorial.f90 index 32b89a09..4b374fec 100644 --- a/Checks/PWR068/solution_mod_factorial.f90 +++ b/Checks/PWR068/solution_mod_factorial.f90 @@ -1,5 +1,5 @@ -! PWR068: Encapsulate procedures within modules to avoid the risks of calling -! implicit interfaces +! PWR068: Call procedures through explicit interfaces, preferably as module +! procedures module mod_factorial implicit none diff --git a/Checks/PWR068/solution_with_type_mismatch.f90 b/Checks/PWR068/solution_with_type_mismatch.f90 index d9266496..9b97770e 100644 --- a/Checks/PWR068/solution_with_type_mismatch.f90 +++ b/Checks/PWR068/solution_with_type_mismatch.f90 @@ -1,5 +1,5 @@ -! PWR068: Encapsulate procedures within modules to avoid the risks of calling -! implicit interfaces +! PWR068: Call procedures through explicit interfaces, preferably as module +! procedures program test_explicit_interface use iso_fortran_env, only: real32 diff --git a/README.md b/README.md index 2ca749c9..690d9d44 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ designed to demonstrate: | [PWR060](Checks/PWR060/) | Consider loop fission to separate gather memory access pattern | optimization | | | | | ✓ | ✓ | ✓ | | | [PWR062](Checks/PWR062/) | Consider loop interchange by removing accumulation on array value | optimization | | | | | ✓ | ✓ | ✓ | | | [PWR063](Checks/PWR063/) | Avoid using legacy Fortran constructs | correctness, modernization, security | [CWE-477](https://cwe.mitre.org/data/definitions/477.html), [CWE-1075](https://cwe.mitre.org/data/definitions/1075.html), [CWE-1119](https://cwe.mitre.org/data/definitions/1119.html) | [6.27](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.28](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.31](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.54](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.58](https://j3-fortran.org/doc/year/23/23-241.pdf) | | | | ✓ | | | -| [PWR068](Checks/PWR068/) | Encapsulate procedures within modules to avoid the risks of calling implicit interfaces | correctness, modernization, security | [CWE-628](https://cwe.mitre.org/data/definitions/628.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), [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.34](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.53](https://j3-fortran.org/doc/year/23/23-241.pdf) | [DCL07-C](https://wiki.sei.cmu.edu/confluence/display/c/DCL07-C.+Include+the+appropriate+type+information+in+function+declarators), [DCL31-C](https://wiki.sei.cmu.edu/confluence/display/c/DCL31-C.+Declare+identifiers+before+using+them), [EXP37-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP37-C.+Call+functions+with+the+correct+number+and+type+of+arguments) | | | ✓ | | | +| [PWR068](Checks/PWR068/) | Call procedures through explicit interfaces, preferably as module procedures | correctness, modernization, security | [CWE-628](https://cwe.mitre.org/data/definitions/628.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), [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.34](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.53](https://j3-fortran.org/doc/year/23/23-241.pdf) | [DCL07-C](https://wiki.sei.cmu.edu/confluence/display/c/DCL07-C.+Include+the+appropriate+type+information+in+function+declarators), [DCL31-C](https://wiki.sei.cmu.edu/confluence/display/c/DCL31-C.+Declare+identifiers+before+using+them), [EXP37-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP37-C.+Call+functions+with+the+correct+number+and+type+of+arguments) | | | ✓ | | | | [PWR069](Checks/PWR069/) | Use the keyword only to explicitly state what to import from a module | correctness, modernization, security | | [6.21](https://j3-fortran.org/doc/year/23/23-241.pdf) | [DCL23-C](https://wiki.sei.cmu.edu/confluence/display/c/DCL23-C.+Guarantee+that+mutually+visible+identifiers+are+unique) | | | ✓ | | ✓[^1] | | [PWR070](Checks/PWR070/) | Declare array dummy arguments as assumed-shape arrays | correctness, modernization, security | [CWE-130](https://cwe.mitre.org/data/definitions/130.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) | [API02-C](https://wiki.sei.cmu.edu/confluence/display/c/API02-C.+Functions+that+read+or+write+to+or+from+an+array+should+take+an+argument+to+specify+the+source+or+target+size) | | | ✓ | | | | [PWR071](Checks/PWR071/) | Prefer real(kind=kind_value) for declaring consistent floating types | modernization, portability, security | [CWE-1102](https://cwe.mitre.org/data/definitions/1102.html), [CWE-1339](https://cwe.mitre.org/data/definitions/1339.html) | [6.2](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.4](https://j3-fortran.org/doc/year/23/23-241.pdf), [6.6](https://j3-fortran.org/doc/year/23/23-241.pdf) | [FLP00-C](https://wiki.sei.cmu.edu/confluence/display/c/FLP00-C.+Understand+the+limitations+of+floating-point+numbers) | | | ✓ | | |