Gem #132 : Erroneous Execution - Part 1

by Bob Duff —AdaCore

Let's get started...

Ada is pretty good about requiring compilers to detect errors at compile time, or failing that, at run time. However, there are some kinds of errors that are infeasible to detect. The Reference Manual calls such errors "erroneous execution".

RM-1.1.5(10) defines the term:

...[T]he implementation need not detect such errors either prior to or during run time. ...[T]here is no language-specified bound on the possible effect of erroneous execution; the effect is in general not predictable.

An example of erroneous execution is suppressing a check that fails. For example:

pragma Suppress (All_Checks); -- Or use the -gnatp switch
A (X) := A (X) + 1;

If X is out of bounds, then the above results in erroneous execution. That means that the program can do anything at all. In explaining the meaning of erroneousness, people often like to talk of spectacular disasters: "It might erase your system disk!" "Your keyboard might catch on fire!" "Nasal demons!"

I think that's somewhat misleading. For one thing, if you're running under an operating system, with proper protections set up, erroneous execution will not erase your system disk. The point is that Ada doesn't ensure that, but the operating system does. Likewise, Ada doesn't prevent your keyboard from catching on fire, but we hope the computer manufacturer will.

One disaster that actually might happen is that the above code will overwrite some arbitrary memory location. Whatever variable was stored there might be destroyed. That's a disaster because it can take hours or even days to track down such bugs. If you're lucky, you'll get a segmentation fault right away, making the bug much easier to figure out.

But the worst thing of all is not keyboard fires, nor destroyed variables, nor anything else spectacular. The worst thing an erroneous execution can cause is for the program to behave exactly the way you wanted it to, perhaps because the destroyed memory location wasn't being used for anything important. So what's the problem? If the program works, why should we care if some pedantic language lawyer says it's being erroneous?

To answer that question, note this common debugging technique: You have a large program. You make a small change (to fix a bug, to add a new feature, or just to make the code cleaner). You run your regression tests, and something fails. You deduce that the cause of the new bug is the small change you made. Because the change is small relative to the size of the whole program, it's easy to figure out what the problem is.

With erroneousness, that debugging technique doesn't work. Somebody wrote the above erroneous program (erroneous if X is out of bounds, that is). It worked just fine. Then a year later, you make some change totally unrelated to the "A (X) := A (X) + 1;" statement. This causes things to move around in memory, such that now important data is destroyed. You can no longer assume that your change caused the bug; you have to consider the entire program text.

The moral of the story is: Do not write erroneous programs.