[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