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) | | | ✓ | | |