Skip to content

1. The linter ​

quad gets an old neighbour: a Romberg integrator in fixed-form FORTRAN 77, the kind of file every long-lived code has somewhere, with a test that pins its result:

f77
C     ROMBERG INTEGRATION: TRAPEZOID ESTIMATES WITH 1, 2, 4, ... STEPS,
C     EXTRAPOLATED UNTIL TWO DIAGONAL VALUES AGREE WITHIN TOL.
      DOUBLE PRECISION FUNCTION ROMB(F, A, B, TOL)
      DOUBLE PRECISION F, A, B, TOL
      EXTERNAL F
      DOUBLE PRECISION R(10,10), H, S
      INTEGER I, J, K, N
      H = B - A
      R(1,1) = 0.5D0*H*(F(A)+F(B))
      N = 1
      DO 30 I = 2, 10
         H = 0.5D0*H
         S = 0.0D0
         DO 10 K = 1, N
            S = S + F(A+DBLE(2*K-1)*H)
   10    CONTINUE
         R(I,1) = 0.5D0*R(I-1,1) + H*S
         N = 2*N
         DO 20 J = 2, I
            R(I,J) = R(I,J-1)
     &             + (R(I,J-1)-R(I-1,J-1))/(4.0D0**(J-1)-1.0D0)
   20    CONTINUE
         IF (DABS(R(I,I)-R(I-1,I-1)) .LT. TOL) GOTO 40
   30 CONTINUE
      I = 10
   40 ROMB = R(I,I)
      RETURN
      END
fortran
!< Romberg's method reaches 1e-10 on sin(x) over [0, pi].
program test_romberg
!< Romberg's method reaches 1e-10 on sin(x) over [0, pi].
use quad_rules, only : R8P
use integrands, only : sine
implicit none
real(R8P), external   :: romb               !< The legacy integrator.
real(R8P), parameter  :: pi = acos(-1._R8P) !< Pi.
real(R8P)             :: estimate           !< Its result.

estimate = romb(sine, 0._R8P, pi, 1.e-10_R8P)
print '(A, ES10.3)', 'romberg error: ', abs(estimate - 2._R8P)
if (abs(estimate - 2._R8P) > 1.e-9_R8P) error stop 'romberg did not converge'
endprogram test_romberg
console
$ fobis test --filter test_romberg 2>&1 | tail -n 4
  PASS  test_romberg                   (0.00 s)

  Results: 1 passed, 0 failed, 0 skipped
  Total:   0.00 s

From this track on quad is a git repository (git init, one commit): the review of chapter 3 needs one.

Linting ​

fobis lint checks the sources of a mode (the default one here), with its -D defines: an #ifdef branch the build does not compile is not checked either.

console
$ fobis lint
src/integrate_mpi.F90:4: COR040 [warning] 'use mpi' has no 'only:' list
src/quad_rules.F90:10: COR002 [warning] interface body function 'integrand' has no 'implicit none'
src/romberg.f:3: COR001 [warning] function 'romb' has no 'implicit none'
src/romberg.f:3: COR031 [warning] function 'romb' is not in a module
src/romberg.f:4: COR010 [warning] dummy argument 'a' of function 'romb' has no intent
src/romberg.f:4: COR010 [warning] dummy argument 'b' of function 'romb' has no intent
src/romberg.f:4: COR010 [warning] dummy argument 'tol' of function 'romb' has no intent
src/romberg.f:4: COR030 [warning] 'f' is declared external in function 'romb'
src/romberg.f:11: OBS011 [warning] labelled do loop (obsolescent): use do ... end do
src/romberg.f:14: OBS011 [warning] labelled do loop (obsolescent): use do ... end do
src/romberg.f:19: OBS011 [warning] labelled do loop (obsolescent): use do ... end do
src/romberg.f:23: MDN021 [warning] old relational operator .lt.: use symbolic operators
src/romberg.f:23: OBS005 [warning] specific intrinsic name 'dabs' (obsolescent): use the generic name
test/test_romberg.F90:7: COR030 [warning] 'romb' is declared external in program 'test_romberg'

14 finding(s) in 10 file(s): COR001 x1, COR002 x1, COR010 x3, COR030 x2, COR031 x1, COR040 x1, MDN021 x1, OBS005 x1, OBS011 x3
Explain a rule with: fobis lint --explain <ID>
[exit status 1]

Every finding names a rule. The first two are about the code of the previous tracks: use mpi imports every name of the MPI module, and the abstract interface of quad_rules has no implicit none. The rest is the legacy file. --explain says why a rule exists, and what to write instead:

console
$ fobis lint --explain COR002
COR002 interface-implicit-typing [warning]

An interface body has no `implicit none`.

An interface body is a separate scoping unit: it does not inherit the `implicit none` of the module
or procedure that contains the interface block. Dummy arguments left undeclared in the body are
typed implicitly, and the interface then disagrees with the procedure it describes.

Reported:

    interface
      subroutine callback(x)
        real :: x
      end subroutine callback
    end interface

Preferred:

    interface
      subroutine callback(x)
        implicit none
        real, intent(inout) :: x
      end subroutine callback
    end interface

Equivalent Fortitude rule: C002

References (read with: fobis kb show <id>):
    fortran-std:ch19-scope-association#anti-patterns

Suppress one occurrence with:  ! fobis: ignore[COR002] <reason>

The references point to the knowledge packs, searchable with fobis kb.

Choosing the rules ​

--select and --ignore take rule ids or prefixes (OBS, obsolescent; MDN, modernisation; COR, correctness; OMP, ACC, MPI, parallel code; ...). Selecting a family explicitly also turns on its opt-in rules, the ones whose findings are matters of style or project policy:

console
$ fobis lint --select OBS,MDN
src/integrate_mpi.F90:4: MDN201 [warning] use mpi: prefer use mpi_f08
src/romberg.f:4: MDN001 [warning] double precision declaration: use real with a named kind parameter
src/romberg.f:6: MDN001 [warning] double precision declaration: use real with a named kind parameter
src/romberg.f:9: MDN002 [warning] double precision literal: use a kind suffix (1.0_rk)
src/romberg.f:11: OBS011 [warning] labelled do loop (obsolescent): use do ... end do
src/romberg.f:12: MDN002 [warning] double precision literal: use a kind suffix (1.0_rk)
src/romberg.f:13: MDN002 [warning] double precision literal: use a kind suffix (1.0_rk)
src/romberg.f:14: OBS011 [warning] labelled do loop (obsolescent): use do ... end do
src/romberg.f:17: MDN002 [warning] double precision literal: use a kind suffix (1.0_rk)
src/romberg.f:19: OBS011 [warning] labelled do loop (obsolescent): use do ... end do
src/romberg.f:20: MDN002 [warning] double precision literal: use a kind suffix (1.0_rk)
src/romberg.f:23: MDN021 [warning] old relational operator .lt.: use symbolic operators
src/romberg.f:23: OBS005 [warning] specific intrinsic name 'dabs' (obsolescent): use the generic name

13 finding(s) in 10 file(s): MDN001 x2, MDN002 x5, MDN021 x1, MDN201 x1, OBS005 x1, OBS011 x3
Explain a rule with: fobis lint --explain <ID>
[exit status 1]

The rule reference lists them all; a fobos [lint] section sets a project's selection once.

For CI ​

--format github writes GitHub Actions annotations (shown on the pull request's lines), sarif the format of GitHub code scanning, json a document for scripts:

console
$ fobis lint --format github --select COR040
::warning file=src/integrate_mpi.F90,line=4,endLine=4,title=COR040 use-all::'use mpi' has no 'only:' list
[exit status 1]

The exit status is 1 when anything is found (--exit-zero keeps it 0).

What you learned

fobis lint, --explain, --select/--ignore, the output formats. Reference: lint, rules.

Next: 2. Suppressions, baselines and fixes.