How to detect uninitialized arguments?

Silverfrost is free for personal use - see Silverfrost Fortran (FTN95) Personal Edition. See Is Fortran “Memory Safe”? - Fortran UK for a table comparing error checking in different compilers.

Yes, VMS Software Inc. was started by (and still filled with) many former DEC people - I know many of them, Their compilers are LLVM-based, using the DEC front-ends.

1 Like

For what it’s worth. Cray Compiler Environment produces this set of messages by default.

$ ftn unused.f90 

    call foo(r, s)
ftn-7212 ftn: WARNING TEST, File = unused.f90, Line = 6 
  Variable "c" is used before it is defined.

        print *, x, y
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 14 
  Variable "y" is used before it is defined.

        i = i + 1  ! issue #3
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 17 
  Variable "i" is used before it is defined.

        a = a + 1. ! issue #4
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 18 
  Variable "a" is used before it is defined.

        b = c      ! issue #5
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 19 
  Variable "c" is used before it is defined.

Cray Fortran : 22 source lines
Cray Fortran : 0 errors, 5 warnings, 0 other messages, 0 ansi
Cray Fortran : "explain ftn-message number" gives more information about each message.

Thanks for reporting on the diagnostic capabilities of the Cray compiler. It seems to be among the most comprehensive. (Curiously, it warns about the use of the uninitialized argument y in the print statement, but does not detect the same problem for x.)

The more compiler results I see, the more surprised I am. I can’t understand why (apparently) no compiler warns about the improper use of intent(out) dummy arguments. These errors are really hard to track down by hand, even with unit tests… Yet, this should be a “piece of cake” for a compiler. Maybe LFortran (@certik) can pave the way? :wink:

In the general case it’s an NP Hard problem. Consider the following:

subroutine possible_problem(something, something_else, out1, out2)
  logical, intent(in) :: something, something_else
  integer, intent(out) :: out1, out2

  if (something) then
    out1 = 42
  else
    out2 = 21
  end if
  if (something_else) then
    print *, out1
  else
    print *, out2
  end if
end subroutine

Should a compiler warn about the possible uses before define or not?

Wouldn’t it be easy (not NP hard) to always warn about deterministic cases?

Then worry about contingent cases later, after we develop a general method to solve NP hard problems in n*log(n) time. :smiley: (which no doubt will be achieved using Fortran)

In some simple cases yeah, and some compilers it appears do warn about some of them. That said, sometimes detecting whether I’m in the easy case can be a hard problem too.

First, I would like to state that the conditional cases you mention are, indeed, a real-life problem. I had about 20+ errors in this procedure, because of a similar type of pattern.

My difficulty in understanding why compilers don’t warn about the improper use of intent(out) arguments is not because its detection is computationally trivial, but rather because:

  • If the compiler does not spot these problems, then the programmer must some how visually parse the code, while mentally applying the same kind of approximate/generic detection algorithm. Surely, a computer program can do that better and faster than most humans…
  • The problem (an intent(out) which should be intent(inout)) can remain hidden. As mentioned in the first post, my (overall) unit tests passed in debug-(linux/windows) and also in release-linux. Only the combination release-windows failed. One might argue this is obvious, but then I ask: how many of us run the CI tests only in debug (fpm build; fpm test)?

IMO, the compiler should warn about such potential cases. For those who code in Python with a static linter and type checker, you have certainly encountered warnings like “variable ‘x’ is unbound.” For example, you can play with the case below and see what happens. It is not perfect, but clearly what happens inside the if constructs is being considered.

def foo(something: bool) -> int:
    # x = None
    if (something):
        x = 1
    else:
        x = x + 2
    return x

The exact case that you mention is, however, a bit subtil. One could debate if printing an uninitialized variable has the same severity level as using an uninitialized variable on the RHS of an assignment. In other words, I would not be shocked if printing an uninitialized variable passed undetected, but I would be in the latter case.

I disagree that compilers should warn of potential cases, at least by default. This would result in a deluge of user complaints and requests to turn off such a feature. I could see it as an optional, additional level of checking.

When I was on VAX FORTRAN, the way we did this was flow analysis, tracing references backwards until we either reached a (possible) definition or the start of the program unit. I am sure there are other techniques possible.

Who mentioned “default”? The only occurrence of that word I could find is in @tmj’s post.

The problem - in my view - is that apparently no compiler has the ability to detect the problem raised in the post, no matter how many flags one uses… :frowning:

BTW, mojo seems to do a fair job:

fn foo(switch: Bool) -> Int:
    var i: Int
    var j: Int
    if switch:
        i = 1
    # else:
    #     i = 0
    j = i + 1
    return j

image

Maybe fpt can do this? According to this page it can catch “variables read before they are assigned a value”.

The key is for this feature to be in the compiler and to be configurable.

I do think most such warnings should be on by default, as a user who develops Fortran code that’s what I would want. However it must also be possible to easily turn it off for people who use the compiler to compile the code but are not developers: packaging, HPC support teams, etc. A good question is who should be the default user of the compiler. I would think the actual Fortran developer. But that’s why we have multiple compilers and each compiler can make a choice that makes the most sense for them.

Normally in my experience, when you have a warning there’s some recommended code change you can implement to squash it, either to bring the code in line with the standard, or to be explicit rather than implicit, or to remove some deprecated feature, etc. What would be the appropriate code change to squash this kind of warning? Would it recommend that you provide a valid default value to all variables through all possible paths?

For what is worth, this is one of the most undeveloped part of the fortran compilers. The typical C compiler of any sort will start painting the terminal if you enable -Wall -Wextra and tell you all about variables. This is especially the case with the ones setup in the first iteration after they appear in the normal branch. It turns out that this is a favorite summer sport in Fortran 77 codebases I have been reading so far, something like the following (very basic example off the cuff, it is much much worse in *PACK codebases);

if (iter > 1)
{
    val = myvar;
    ...
    myvar *= 2.0; 
} else {
    ....  // Other code initializers
    myvar = 1.0;
}

All compilers I’ve tried catch these as “maybe uninitialized”. I don’t care if it is false positive since this is my algorithmic mistake in the first place to write such unmaintainable code. It is basically tracking its first use and whether it has been defined previously. It does not have to solve a nonlinear graph problem.

In fact we have fixed many segfaults due to C compiler nagging. And the compiler never promises to find all possible cases anyways. But it cannot not try. I even turn on -Wpedantic which makes almost everything complain and tells you what is removed or what is coming in C17, C23 and so on.

But other than that, this is a feature that is a must in compilers. Legacy or otherwise this is unacceptable that nothing is emitted. No salesman stopped traveling because they are solving an NP-hard problem. With all respect, I think Fortran compilers are extremely lax at passing terrible code unnoticed. When I approach the compiler forums, I’ve been always told about legacy codebase but in turn this is poisoning the future code.

@ilayn ,

Your specific points bringing up a C compiler are not pertinent, the thread revolves around INTENT(OUT) semantics in Fortran and is specific to Fortran.

Same idea applies to intent semantics. If I have this signature

      recursive subroutine dqawfe(f,a,omega,integr,epsabs,limlst,
     *   limit,maxp1,result,abserr,neval,ier,rslst,erlst,ierlst,lst,
     *   alist,blist,rlist,elist,iord,nnlog,chebmo)

and some of them are marked as out but their values are used before initialized, you can raise a warning. Similarly if they are marked as in but they are being set to some values then you can raise another maybe-wrong-intent warning.

They don’t have to be perfect, they need to draw attention. Instead I am opening an editor, highlighting all occurrences and hunting them on which side of the equal sign they show up. If nothing shows up but they are sent to directly to another function, then the hunt continues. The machine should be doing this for me. Unfortunately, the distance between what language offers and what compilers can catch is way too big to be productive.

If a warning is on by default, it must be a robust warning that takes care of all corner cases, so it is never spurious. I think you never want an uninitialized variable in a code, so I would say initializing it is the right fix. If there was some corner case where you want to use uninitialized variable, then one could design some syntax or pragma to tell the compiler that you know it is not initialized and are fine with it. To make it explicit.

@HugoMVale The Cray compiler doesn’t report variables both since we will suppress multiple of the same message of this type; you get notified of “y”, and would get notified of “x” once you address the issue with “y”.

For this specific use case I see where it can be a bit confusing so we have changed the default behaviour with a upcoming release. Note that at default optimization, the line numbers and order of output can be a bit confusing unless you consider inlining and cloning.

$ ftn unused.f90 

    call foo(r, s)
ftn-7212 ftn: WARNING TEST, File = unused.f90, Line = 6 
  Variable "a" is used before it is defined.
ftn-7212 ftn: WARNING TEST, File = unused.f90, Line = 6 
  Variable "c" is used before it is defined.
ftn-7212 ftn: WARNING TEST, File = unused.f90, Line = 6 
  Variable "i" is used before it is defined.

        print *, x, y
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 14 
  Variable "y" is used before it is defined.
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 14 
  Variable "x" is used before it is defined.

        i = i + 1  ! issue #3
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 17 
  Variable "i" is used before it is defined.

        a = a + 1. ! issue #4
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 18 
  Variable "a" is used before it is defined.

        b = c      ! issue #5
ftn-7212 ftn: WARNING FOO, File = unused.f90, Line = 19 
  Variable "c" is used before it is defined.

Cray Fortran : 22 source lines
Cray Fortran : 0 errors, 8 warnings, 0 other messages, 0 ansi
Cray Fortran : "explain ftn-message number" gives more information about each message.

2 Likes

Some Fortran constructions only take heed of attributes of data objects and ignore their value, so in those cases an uninitialised variable can be employed. I would like to see an explicit “UNDEFINE var” statement that directly causes var to become undefined. One can emulate it with a CALL to an immediately-returning subroutine with an INTENT(OUT) dummy, but the idea is for the compiler to be encouraged to make note of the undefinition and use that fact in optimization to prune the entire basic block where known-undefined vars are subsequently referenced (“it was never meant for this to happen, therefore the compiler can assume that the condition that sent us here is never true”). Or use the same fact in a debugging mode to stop the program with a message. Doesn’t that accord with the Fortran tradition of trusting the programmer’s competence but also not denying a helping hand to the less confident?

1 Like