A Bug in the GNAT or How Iterators Killed Finalization



February 28th, 2021 by Diana Coman

The GNAT in question is any version of a bunch really but since all of them are inflated to a monstruous 360M size even when archived, there is perhaps no surprise that most of that code is better left unused if one can't directly discard it. Fortunately, at least some parts can be routinely and specifically excluded by means of specifying a hefty list of "restrictions" that gprbuild duly enforces. As it's currently the case with almost everything around, the *more* restrictions you can keep in place explicitly, the *better* - for the final result but also for your own safety and sanity, too! In this specific case, better turns out to include even getting rid of buggy code in GNAT itself, not to mention avoiding weird errors and compilation fails that could easily eat up days to hunt down otherwise.

As I have always started any Ada project with ALL possible restrictions active and then relaxed some of them as, when and indeed only if absolutely needed, I am sure I have in fact missed a lot of opportunity for more exciting programming sessions - so many bugs and obscure errors waiting in those 360M of code for their chance to shine and so few chances for it when I really refuse to "need" unbounded this or tagged that, finalize this or iterate the other1. Still, as on one hand the gfx pipeline really can't quite do without dynamic memory allocation2 and on the other hand I had probably grown a bit too comfortable with Ada otherwise, I finally had a hefty dose of GNAT-excitement and bug hunting. It all started with this wonderfully informative error suddenly bringing all compilation to a halt, just as I was in the process of replacing3 the prototype gfx-pipeline with the production version that had otherwise compiled and worked perfectly on its own but now suddenly... failed:

main_gfx.adb:22:09: instantiation error at a-convec.adb:2006
main_gfx.adb:22:09: instantiation error at gfx_types.ads:53
main_gfx.adb:22:09: "" is undefined

The above 3 lines are *all* of the "error", too, I didn't trim anything away. Basically the second line is the entry point from my code - a simple instantiation of a generic package (gfx_types) that otherwise instantiates in turn a package from the standard containers library - a vector, implemented apparently in that a-convec.adb. A look at a-convec.adb4 reveals an unexpected and disgusting degree of infestation with iterators but otherwise no direct screams of bloody murder in any more specific terms:


return It : constant Iterator :=
(Limited_Controlled with
Container => V,
Index => No_Index)

The first and foremost question is of course just why exactly is there anything related to iterators in the first place, when I am *not* using any such things anywhere in my code (and for good reason - they are implemented in the sort of "helping" way that suddenly requires a whole additional bunch of restrictions to be lifted because... well, because the iterators say so, ok?). Sneaking suspicions thus aroused, I checked again the restrict.adc of the project that was failing. Sure enough, it turned out that I had forgotten to revert a previous ill-advised relaxation of the "No_Finalization" restriction. For the record, that removal of the No_Finalization restriction was itself the result of this other time when my undying optimism insisted that the Controlled_Type with its guaranteed Finalization can actually be useful - in practice it turned out more trouble than it's worth it AND it insisted bringing in with it the whole tagged mess so I ditched it but apparently the harm was still done since I had forgotten to restore also this restriction (the rest of the bunch I had restored and yes, I checked all of them now again, indeed).

Turning on again the No_Finalization restriction meant that my whole project compiled again happily and perfectly, not to mention to my huge relief (despite this brief account, it took a bit longer in practice to home in on the precise restriction that was missing). What happens there is simply that the No_Finalization restriction disables the whole of "controlled" and "limited controlled" type and their derivations meaning in turn that the buggy code that forced iterators down my throat is not included - basically, by means of enforcing this restriction, I get yet again to not have the same problems that everyone has.

Once my code compiled again, I was still left with the unwanted but unavoidable task of figuring out just what the trouble was anyway - because this is not the sort of thing to just ignore "because it works now". So I read a bit more around that GNAT code and then set out the smallest possible example code to reproduce the trouble. Sure enough, it was reproduced swiftly, but this time with a more informative message that suddenly ringed a bell too since I had seen it only a few days before (and naively thought at that time that it had to do with something else entirely):

smallest]$ gprbuild
using project file fail.gpr
gcc -c -O2 -fdump-scos -gnata -fstack-check -gnatyd -gnatym -fdata-sections -ffunction-sections -gnatwr -gnatw.d -gnatec=smallest/restrict.adc main.adb
+===========================GNAT BUG DETECTED==============================+
| GPL 2016 (20160515-49) (x86_64-pc-linux-gnu) Storage_Error stack overflow or erroneous memory access|
| Error detected at a-convec.adb:2006:7 [p1.ads:7:9 [main.adb:7:9]] |
| Please submit a bug report by email to report@adacore.com. |
| GAP members can alternatively use GNAT Tracker: |
| http://www.adacore.com/ section 'send a report'. |
| See gnatinfo.txt for full info on procedure for submitting bugs. |
| Use a subject line meaningful to you and us to track the bug. |
| Include the entire contents of this bug box in the report. |
| Include the exact command that you entered. |
| Also include sources listed below. |
| Use plain ASCII or MIME attachment(s). |
+==========================================================================+

Please include these source files with error report
Note that list may not be accurate in some cases,
so please double check that the problem can still
be reproduced with the set of files listed.
Consider also -gnatd.n switch (see debug.adb).

smallest/main.adb
smallest/p1.ads

compilation abandoned
gprbuild: *** compilation phase failed

For the record, the code to reproduce the above is very small indeed: all you need is to define a Vector type from within a generic package that you instantiate in your main (defining the Vector type directly within your main will *work*). My test code has a main.adb and a p1 package without a body even (hence just a file, p1.ads) with the following content:

main

with Ada.Text_IO; use Ada.Text_IO;
with P1;

procedure main is
precision: constant Natural := 16;
type FType is digits precision;
package P is new P1(T => FType);
begin
Put_Line("Calling P...");
end main;

p1.ads

with Ada.Containers.Vectors;

generic
type T is digits<>;
package P1 is

package fail is new Ada.Containers.Vectors( Index_Type => Natural,
Element_Type => T);
use fail;

end P1;

In a nutshell, the above is the story of how the buggy must-have-iterators implementation of Vectors in GNAT's standard Ada.Containers library effectively kills with one single shot the use of any Finalization5 since you can't have both in your program without either finding a different way perhaps to avoid that bug or otherwise, more likely, simply running into trouble as above.

On the bright side, the GNAT implementation of Finalization had already managed to convince me fully that it sucks and so overall nothing useful is lost, a new restriction is firmly in place and otherwise concrete evidence is gained as to the clear benefits of restricting rather than permitting anything by default. If only GNAT came with a way to also discard all that buggy and useless code that I have to keep away with restrictions...


  1. All containers from the standard library, such as vectors, lists or hashmaps provide iterators because they are supposedly so much better and more convenient and more wonderful than ... you know, iterating the thing the way you want it when you want it. So much better in fact that they bring then a whole lot of trouble with them too, from having to ensure that the iterated elements don't change unexpectedly to pulling in the whole tagged pie aka the "Object Oriented" mud stuck to Ada because otherwise it was probably suspiciously clean or something. 

  2. This is mainly because of the whole rasterizer approach - the moment one *has to* produce a full list of all vertices and normals and whatnots at every point of a model made out of several surfaces too, there is little choice but to allow dynamic memory allocation too - allocating the maximum (which can be calculated, certainly) needed to cover all cases is really a huge overkill and generally worse, namely a stack overflow. 

  3. I never really want surprises but there are such times when I want surprises even less than usual - that very moment when I'm replacing important parts of a complex mechanism and therefore the whole is eviscerated and both my hands as well as my head are quite full of it certainly qualifies for one of the worst possible times to have surprises. 

  4. Which can be in various places deep in gnat, depending on the exact version you have but if you installed Adacore's gnat to the default location, you can find it at /usr/gnat/lib/gcc/x86_64-pc-linux-gnu/4.9.4/rts-sjlj/adainclude/a-convec.adb or (if you are not using sjlj) at /usr/gnat/lib/gcc/x86_64-pc-linux-gnu/4.9.4/rts-native/adainclude/a-convec.adb. The two files are identical, according to diff. 

  5. In brief, no Finalization means no controlled types and their ilk. In detail, Adacore's docs of GNAT contain the following description of the No_Finalization restriction:

    5.1.31. No_Finalization

    [GNAT] This restriction disables the language features described in chapter 7.6 of the Ada 2005 RM as well as all form of code generation performed by the compiler to support these features. The following types are no longer considered controlled when this restriction is in effect:

    Ada.Finalization.Controlled

    Ada.Finalization.Limited_Controlled

    Derivations from Controlled or Limited_Controlled

    Class-wide types

    Protected types

    Task types

    Array and record types with controlled components

    The compiler no longer generates code to initialize, finalize or adjust an object or a nested component, either declared on the stack or on the heap. The deallocation of a controlled object no longer finalizes its contents. 

Comments feed: RSS 2.0

2 Responses to “A Bug in the GNAT or How Iterators Killed Finalization”

  1. [...] be told, I do not even really want to write (yet again!) about bugs and poor implementations and unexpected errors of all sorts. But given how easily I find them these [...]

  2. [...] GNAT implementation of the Ada standard, mostly because I had no choice1. A few years and a lot of experience with said GNAT implementation later, I know for a fact that the I/O packages of GNAT such as Direct_IO and Sequential_IO are [...]

Leave a Reply