Topic: More compile-time checking


Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1997/01/07
Raw View
Bradd W. Szonye wrote:
>
> Christopher Eltschka <celtschk@physik.tu-muenchen.de> wrote in article
> <32B15CBA.1663499D@physik.tu-muenchen.de>...
>
> [method of checking "assertions" at compile time deleted]
> > - the condition the parameters should hold is put into the declaration
> >   of the function
> > - if the compiler can check the condition at the calling statement,
> >   and if the condition is wrong, the compiler should give an
> >   compile-time error.
> > - if the compiler can't check the condition at conmpile time, it
> >   doesn't add any runtime checks; it's the responsibility of the
> >   programmer of the function to provide runtime checks or to leave
> >   the parameter otherwise unchecked for performance reasons.
>
> > As different compilers have different capabilities of compile time
> > checking, the only requirement would be that the compiler accepts the
> > syntax, while fails of the "interface assertions" don't need to
> > be flagged (while it is of course encouraged to do so).
>
> > Any opinions on this?
>
> Yes... I hope this doesn't sound rash, but I think your idea is terribly
> useful, or even possible. The fact that the compiler only checks things
> that are analyzable at compile time means that it can only find very, very
> simple errors. Anything that's isn't extremely (really extremely) simple
> will encounter an unsolvable problem, the "decidability" problem. It's not
> a matter of compilers not having sophisticated enough dataflow analysis:
> decidability is an NP problem with exponential complexity. The class of
> function calls simple enough to analyze at compile time will, in general,
> not be big enough to justify the increased cost of developing the feature.
> You'd really only be able to check constants defined fairly close to the
> function call, and assertions work just as well for that anyway. In fact,
> assertions catch many more problems, and much more cheaply.
>

I disagree:

Assume, you have a Vector class for mathematical vectors
(i.e. Numbering from 1 to N):

template<int N> class Vector
{
// ...
public:
  double operator[](int i); // i must be between 1 and N
// ...
};

And, inside your library you have (precompiled):

template<int N> double Vector<N>::operator[](int i)
{
  assert(i>=1 && i<=N);
// ...
}

And then you do a typo in your final program like

double f()
{
  Vector<4> v;
// ...
  return v[5]; // oops! should have been 4
}

Then the compiler will compile this without warning or error, and then
give a runtime error (which then has to be found with a debugger).
Moreover, if you just give a constant, you would even tend to use
a non-checking version of operator[] (I "know" that my value is correct,
so why do a time-consuming runtime check?). And it might be, that this
error occurs after having calculated for 10 hours by previous code.

The error would be less obvious, if you replace 4 and 5 in f() by
constants, say "Dimension" and "Coordinate", defined somewhere else,
and the error results f.ex. from changing the constant Dimension
from 6 to 4 without thinking about the Coordinate constant.

With the extension, the definition would be

template<int N> class Vector
{
// ...
public:
  double operator[](int i; i>=1 && i<=N);
// ...
};

And now the error in f() would be catched at *compile* time
(-> less time consuming). And, even if no run time check is done
(so you can get improved performance).

BTW, would you also argue that expressions like 1/0 should not
give a diagnostic? So why have this possibility for built-in operators,
but not for user defined operators/functions?

> If compilers could check parameters at compile time in this way, they could
> also tell you whether your program will crash or hang, the "halting
> problem." While such compilers might be nice, they would take years to
> compile anything.

I never said that a *full* analysis of the program should be done
(which might be impossible, if the result comes from a library
function).
But that you cant check the code

double f()
{
  double x;
  // some very complicated calculation for x
  return 1/x; // division by zero?
}

doesn't mean that you should not get a diagnostic for

double f()
{
  double x=0;
  return 1/x;
}

BTW, even in those cases the extension could be useful:

Assume you have

double f(double a, double b)
{
  double x;
  // some complicated calculation for x, involving a and b
  // But: from our math course we know, x will be zero (at least)
  // if a>5 and b=a+10
  return 1/x;
}

double g()
{
  double result;
  double hlp=f(6,16); // This will give division by zero in f, but
                      // the compiler can't check this
  // do calculation of result, using hlp
  return result;
}

The compiler has no chance to warn you about your mistake.

But if you change f to

double f(double a, double b; a<=5 || b!=a+10)
// ...

the compiler *will* warn you in g().

BTW, I know recognice that the extension would be much more useful, if
you are able to tell the compiler known conditions about the result
values. Maybe a syntax could be

double sqrt(double x; x>=0, return>=0);

this would catch errors like

double f(double x; x<0);

double x=f(sqrt(a)); // sqrt(a)>=0, but f expects argument<0

Again, this is certainly not for complicated conditions or expressions,
but this is just a typical sort of error (probably the last line should
have read

double x=f(-sqrt(a));

instead).

Of course you could do a compiler that does check the conditions so
completely that you would need hours of time to compile. But similarly,
you could in principle write a compiler, which would optimize so much,
that given a program that calculates the first 15 digits of pi
(without any user input, like "How many digits do you want?"), it would
return code that simply writes out the string "3.14159265358979"
without doing any calculation (because it did the calculation at compile
time already). It would probably need a long time to compile a program
calculating the first two million digits instead. But this extreme would
not be a reason against optimization - only against "over-optimization".
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "F. van Leeuwen" <fvl@iaehv.nl>
Date: 1996/12/31
Raw View
> Christopher Eltschka <celtschk@physik.tu-muenchen.de> writes:
>

[Cut out. See previous postings]

>
> I think that if the compiler can determine that the code would cause an
> assertion failure 100% of the time, it can generate a compile-time
> error.  (It can certainly generate a warning.)

But what about constructs like:

switch(i) {
   case 1: ....
           break;
   case 2: ....
           break;
   (etcetera)
   default:
           assert(FALSE);
           break;
}

I certainly wouldn't have the compiler generate a warning for me there,
let alone an error message. So I think this is not a good solution.

Frank.



[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: James Kanze <james-albert.kanze@vx.cit.alcatel.fr>
Date: 1997/01/02
Raw View
"F. van Leeuwen" <fvl@iaehv.nl> writes:

|>  > Christopher Eltschka <celtschk@physik.tu-muenchen.de> writes:
|>  >
|>
|>  [Cut out. See previous postings]
|>
|>  >
|>  > I think that if the compiler can determine that the code would cause an
|>  > assertion failure 100% of the time, it can generate a compile-time
|>  > error.  (It can certainly generate a warning.)
|>
|>  But what about constructs like:
|>
|>  switch(i) {
|>     case 1: ....
|>             break;
|>     case 2: ....
|>             break;
|>     (etcetera)
|>     default:
|>             assert(FALSE);
|>             break;
|>  }
|>
|>  I certainly wouldn't have the compiler generate a warning for me there,
|>  let alone an error message. So I think this is not a good solution.

The warning (or error) would only occur if the compiler could prove that
the assertion must be executed.  Thus, for example, if the statement
immediately preceding the switch were "i = 100", and there was no case
100.

After some consideration, I don't believe that the compiler can legally
generate an error (and refuse to compile the program) in such cases, if
for no other reason that the function may never be called.  In such
cases, I would expect the compiler to generate a warning.  (Note that in
such cases, many compilers will inform you that they are deleting the
code for case 1, etc. because it can never be reached.)

--
James Kanze         home:     kanze@gabi-soft.fr        +33 (0)3 88 14 49 00
                    office:   kanze@vx.cit.alcatel.fr   +33 (0)1 69 63 14 54
GABI Software, Sarl., 8 rue des Francs Bourgeois, F-67000 Strasbourg, France
       -- Conseils en informatique industrielle --


[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1996/12/17
Raw View
I'd like to have an extention to C++ that would allow to check more
things at compile time.
Assume the following situation:

void f(int n)
{
  assert(n>0); // (1)
// ...
}

void g(int m, int n)
{
  assert(n<0); // (2)
  f(m);
  f(0); // (1) will fail at runtime
  f(n); // (1) will fail at runtime (because else (2) had failed)
}

int main()
{
  g(2,-1);
  g(2,2);   // (2) will fail at runtime
  g(-1,-2); // (1) will fail at runtime
  return 0;
}

Now, I'd like to have an extention to the syntax like this:

void f(int n; n>0) // allows compile-time checking, where possible
{
  assert(n>0);
// ...
}

void g(int m, int n; n<0)
{
  assert(n<0);
  f(m); // Ok: The compiler can't know what m will be
  f(0); // compile time error: 0>0 is false
  f(n); // compile time error: n is known to be <0, so n>0 is false
}

int main()
{
  g(2,-1); // Ok
  g(2,2);  // compile time error: 2<0 is false
  g(-1,-2); // Ok: g doesn't put restrictions to first parameter
            // (BTW, (1) will fail at runtime)
  return 0;
}

So the rule is:
- the condition the parameters should hold is put into the declaration
  of the function
- if the compiler can check the condition at the calling statement,
  and if the condition is wrong, the compiler should give an
  compile-time error.
- if the compiler can't check the condition at conmpile time, it
  doesn't add any runtime checks; it's the responsibility of the
  programmer of the function to provide runtime checks or to leave
  the parameter otherwise unchecked for performance reasons.

As different compilers have different capabilities of compile time
checking, the only requirement would be that the compiler accepts the
syntax, while fails of the "interface assertions" don't need to
be flagged (while it is of course encouraged to do so).

This extension would allow to get more errors to be catched at compile
time which else would have been catched at runtime through assertions
only.

As now there are no ';'s allowed in parameter lists, the syntax
given above (condition follows parameters, separated by ';') should
work.

Any opinions on this?
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: James Kanze <james-albert.kanze@vx.cit.alcatel.fr>
Date: 1996/12/19
Raw View
Christopher Eltschka <celtschk@physik.tu-muenchen.de> writes:

    [...]
|>  Now, I'd like to have an extention to the syntax like this:
|>
|>  void f(int n; n>0) // allows compile-time checking, where possible
|>  {
|>    assert(n>0);
|>  // ...
|>  }

Presumably, you want this to be valid in the function prototype as
well.  Otherwise, it is already in the language, in a slightly different
syntax.  (To specify that the parameter n must be strictly greater than
0, for example, you write "assert( n > 0 ) ;" as the first statement.
The problem is, of course, that this syntax doesn't extend to function
prototypes, which cannot contain statements.)

    [...]
|>  So the rule is:
|>  - the condition the parameters should hold is put into the declaration
|>    of the function
|>  - if the compiler can check the condition at the calling statement,
|>    and if the condition is wrong, the compiler should give an
|>    compile-time error.

I think that if the compiler can determine that the code would cause an
assertion failure 100% of the time, it can generate a compile-time
error.  (It can certainly generate a warning.)

|>  - if the compiler can't check the condition at conmpile time, it
|>    doesn't add any runtime checks; it's the responsibility of the
|>    programmer of the function to provide runtime checks or to leave
|>    the parameter otherwise unchecked for performance reasons.

Interesting question: if the compiler can determine that an assertion
must fail, can it generate an error even if NDEBUG is defined?  (I don't
think so, but it can certainly generate a warning.)

|>  As different compilers have different capabilities of compile time
|>  checking, the only requirement would be that the compiler accepts the
|>  syntax, while fails of the "interface assertions" don't need to
|>  be flagged (while it is of course encouraged to do so).

I'd love to see how you formulate that in standardese.  You have defined
a situation where whether you have a error requiring a diagnostic or a
conforming program is implementation defined.

|>  This extension would allow to get more errors to be catched at compile
|>  time which else would have been catched at runtime through assertions
|>  only.
|>
|>  As now there are no ';'s allowed in parameter lists, the syntax
|>  given above (condition follows parameters, separated by ';') should
|>  work.
|>
|>  Any opinions on this?

In theory, a good lint can do these tests already.  I like the basic
idea, but I'm not sure that it is worth adding something to the language
to do it.  (Note that to be really effective, the implementation should
be able to do value assertion propagation across module boundaries.  An
implementation which can do that is also capable of seeing all of the
assert's, even those in different modules, and so potentially has all of
the information it needs already.)

--
James Kanze         home:     kanze@gabi-soft.fr        +33 (0)3 88 14 49 00
                    office:   kanze@vx.cit.alcatel.fr   +33 (0)1 69 63 14 54
GABI Software, Sarl., 8 rue des Francs Bourgeois, F-67000 Strasbourg, France
       -- Conseils en informatique industrielle --


[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: James Kuyper <kuyper@wizard.net>
Date: 1996/12/19
Raw View
James Kanze wrote:
>
> Christopher Eltschka <celtschk@physik.tu-muenchen.de> writes:
>
>     [...]
> |>  Now, I'd like to have an extention to the syntax like this:
> |>
> |>  void f(int n; n>0) // allows compile-time checking, where possible
> |>  {
> |>    assert(n>0);
> |>  // ...
> |>  }
>
> Presumably, you want this to be valid in the function prototype as
> well.  Otherwise, it is already in the language, in a slightly different
> syntax.  (To specify that the parameter n must be strictly greater than
> 0, for example, you write "assert( n > 0 ) ;" as the first statement.
> The problem is, of course, that this syntax doesn't extend to function
> prototypes, which cannot contain statements.)
>
>     [...]
> |>  So the rule is:
> |>  - the condition the parameters should hold is put into the declaration
> |>    of the function
> |>  - if the compiler can check the condition at the calling statement,
> |>    and if the condition is wrong, the compiler should give an
> |>    compile-time error.
>
> I think that if the compiler can determine that the code would cause an
> assertion failure 100% of the time, it can generate a compile-time
> error.  (It can certainly generate a warning.)
>

Isn't assert() required to produce a run-time error, and not a
compile-time one (as long as its argument is valid)?

I like this suggestion a lot, assuming that it would be a part of the
function prototype.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]