[fpc-devel] Initialize/Finalize management operators and Default intrinsic

Sven Barth pascaldragon at googlemail.com
Mon Apr 11 23:36:48 CEST 2016


On 11.04.2016 15:27, Maciej Izak wrote:
> 2016-04-11 14:38 GMT+02:00 Sven Barth <pascaldragon at googlemail.com
> <mailto:pascaldragon at googlemail.com>>:
> 
>     There is another problem that occurred to.me <http://to.me> after my
>     previous mail. Have you ever heard of the "static initialization
>     order fiasco"? I've stumbled upon that a few weeks ago at work with
>     C++ and was glad that we don't have that problem in Pascal, but with
>     the new management operators and the need to have global variables
>     be initialized correctly as well this would change - which would be
>     very bad as this is a hard to debug problem.
> 
> Even worse, currently is possible to achieve some similar... I've
> experienced from another side - "static finalization order fiasco" for
> Generics.Collections (fixed 24 Dec 2015
> https://github.com/dathox/generics.collections/commit/fda586932bd80ef58c08f8ebf5a24316ca4ccca5
> ). 
> 
> I can confirm that "static initialization/finalization order fiasco"
>  bugs are really hard to debug. In my case for Generics.Collections was
> something like this (maybe not the same but very similar):
> 
> === X ===
> program X;
> 
> uses
>   UnitA, ..., UnitZ, GC, Generics.Collections, Unit1;
> ...
> === GC ===
> ...
> interface
> var
>   G: Contnrs.TObjectList;
> implementation
> ...
> initialization
>   G := Contnrs.TObjectList.Create(True);
> finalization
>   G.Free; // !!! 
> end.
> 
> === Unit1 ===
> ... somewhere in code ...
> var
>   d: TDictionary<string, string>;
> begin
>   d := TDictionary<string, string>.Create;
>   G.Add(d);
> === END CODE ===
> 
> at line marked by "!!!" was memory corruption, because any of manual
> interface used by dictionary was already destroyed in static/class
> destructor in Generics.Defaults... Really hard to find that kind of bug.
> What was funny the corruption was visible only on android... Anyway bug
> is fixed :) 

My example would be this:

=== unit1 begin ===

unit unit1;

interface

function GetSomething: PSomeType;

implementation

var
  gSomething: TSomeType;
  { assume TSomeType is a record that contains an initializer
     method that simply zeros the record and it also has a
     TObject field called "inst" }

function GetSomething: PSomeType;
begin
  if not Assigned(gSomething.inst) then
    gSomething.inst := TObject.Create;
  Result := @gSomething;
end;

{ gSomething will implicitely be initialized here }
end.

=== unit1 end ===

=== unit2 begin ===

unit unit2;

interface

implementation

uses
  unit1;

initialization
  Writeln(GetSomething^.inst.ToString);
end.

=== unit2 end ===

Now depending on the order of the execution of the two initialization
sections we'll either have correct behavior (unit1 before unit2) or
we'll have a memory leak (unit2 before unit1), because gSomething's
initializer in unit1 will fill the instance variable that got assigned
during unit2's initialization (the GetSomething() call) with zeros.
In a simple program the order can be controlled easily, but in a more
complex scenario (think the compiler itself or Lazarus) this might no
longer be the case and it's essentially random for the user.

I know this is a rather constructed example, but it's similar to the C++
code we had at work, so it's code that can happen in the real world.
If we don't find a way to solve this problem then I'm afraid that I
won't include your changes in trunk, cause I don't want to open that can
of worms.

Regards,
Sven



More information about the fpc-devel mailing list