[fpc-devel] crash with TCuckooD2<string, string>

Martin Frb lazarus at mfriebe.de
Wed Apr 8 14:10:50 CEST 2026


is TCuckooD2<string, string> allowed?

See the code below. It's brute force testing some of the dictionaries.

Testing with 2.2.3 and 3.3.1 (both from early this year) I get a crash for
    TCuckooD2<string, string>
   (not tested any of those that follow later in the list).

The crash happens after "Run2" did
   Dict.clear;

when it attempts to add a new element.





program test_dict;
{$mode objfpc}{$H+}

uses Generics.Collections, SysUtils;

generic procedure Run<T>;
var
   Dict: T;
   i: Integer;
   GotExcept: Boolean;

   procedure Add(ATestVal: integer);
   begin
     Dict.Add(IntToStr(ATestVal), 'Val'+IntToStr(ATestVal));
   end;
   function TryAdd(ATestVal: integer): boolean;
   begin
     result := Dict.TryAdd(IntToStr(ATestVal), 'Val'+IntToStr(ATestVal));
   end;
   procedure Del(ATestVal: integer);
   begin
     Dict.Remove(IntToStr(ATestVal));
   end;
   procedure Check(ATestVal: integer);
   var
     ValGotten: string;
   begin
     if not Dict.ContainsKey(IntToStr(ATestVal)) then
       raise Exception.Create('missing key');
     if Dict[IntToStr(ATestVal)] <> 'Val'+IntToStr(ATestVal) then
       raise Exception.Create('wrong value');
     if not Dict.TryGetValue(IntToStr(ATestVal), ValGotten) then
       raise Exception.Create('TryGetValue failed');
     if ValGotten <> 'Val'+IntToStr(ATestVal) then
       raise Exception.Create('wrong tried value');
   end;
   procedure CheckNot(ATestVal: integer);
   var
     ValGotten: string;
   begin
     if  Dict.ContainsKey(IntToStr(ATestVal)) then
       raise Exception.Create('unexpected key');
     if Dict.TryGetValue(IntToStr(ATestVal), ValGotten) then
       raise Exception.Create('TryGetValue unexpected');
   end;
   procedure CheckCount(AnExp: integer);
   begin
     if not Dict.Count = AnExp then
       raise Exception.Create('wrong count');
   end;

begin
   RandSeed := 1; // make the test reproducible

   Dict := T.Create;

   CheckNot(0);
   CheckNot(1);
   CheckCount(0);

   Add(-999);
   Check(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(1);

   Del(-999);
   CheckNot(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(0);

   // re-add
   Add(-999);
   Check(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(1);

   if not TryAdd(-998) then raise Exception.Create('try add failed');
   Check(-998);
   Check(-999);
   CheckCount(2);
   if TryAdd(-999) then raise Exception.Create('try add unexpected');
   if TryAdd(-998) then raise Exception.Create('try add unexpected');
   Check(-998);
   Check(-999);
   CheckCount(2);


   for i :=   0 to  99 do Add(i);
   for i :=   0 to  99 do Check(i);
   for i := 100 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);
   CheckCount(102);
   Del(99);
   CheckCount(101);

   for i := 200 to 999 do Add(i);
   for i := 200 to 999 do Check(i);
   for i :=   0 to  98 do Check(i);
   for i :=  99 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);

   for i :=1000 to 9999 do Add(i);
   for i := 200 to 9999 do Check(i);
   for i :=   0 to  98 do Check(i);
   for i :=  99 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);

   Dict.TrimExcess;

   for i := 200 to 9999 do Check(i);
   for i :=   0 to  98 do Check(i);
   for i :=  99 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);

   Dict.Clear;
   CheckCount(101);
   for i :=   0 to  999 do CheckNot(i);


   GotExcept := False;
   Add(-999);
   try
     Add(-999);
   except
     GotExcept := True;
   end;
   if not GotExcept then
     raise exception.Create('did not reject dup');
   Check(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(1);

   Dict.AddOrSetValue('998', 'B');
   if Dict['998'] <> 'B' then
     raise Exception.Create('TryOrSet failed');
   Check(-999);
   CheckCount(2);

   Dict.AddOrSetValue('999', 'A');
   if Dict['999'] <> 'A' then
     raise Exception.Create('TryOrSet failed');
   if Dict['998'] <> 'B' then
     raise Exception.Create('TryOrSet failed');
   CheckCount(2);

   Dict.Destroy;
end;


generic procedure Run2<T>;
var
   Dict: T;
   i: Integer;
   GotExcept: Boolean;

   procedure Add(ATestVal: integer);
   begin
     Dict.Add(IntToStr(ATestVal), 'Val'+IntToStr(ATestVal));
   end;
   procedure Del(ATestVal: integer);
   begin
     Dict.Remove(IntToStr(ATestVal));
   end;
   procedure Check(ATestVal: integer);
   var
     ValGotten: string;
   begin
     if not Dict.ContainsKey(IntToStr(ATestVal)) then
       raise Exception.Create('missing key');
     if Dict[IntToStr(ATestVal)] <> 'Val'+IntToStr(ATestVal) then
       raise Exception.Create('wrong value');
     if not Dict.TryGetValue(IntToStr(ATestVal), ValGotten) then
       raise Exception.Create('TryGetValue failed');
     if ValGotten <> 'Val'+IntToStr(ATestVal) then
       raise Exception.Create('wrong tried value');
   end;
   procedure CheckNot(ATestVal: integer);
   var
     ValGotten: string;
   begin
     if  Dict.ContainsKey(IntToStr(ATestVal)) then
       raise Exception.Create('unexpected key');
     if Dict.TryGetValue(IntToStr(ATestVal), ValGotten) then
       raise Exception.Create('TryGetValue unexpected');
   end;
   procedure CheckCount(AnExp: integer);
   begin
     if not Dict.Count = AnExp then
       raise Exception.Create('wrong count');
   end;

begin
   RandSeed := 1; // make the test reproducible

   Dict := T.Create;

   CheckNot(0);
   CheckNot(1);
   CheckCount(0);

   Add(-999);
   Check(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(1);

   Del(-999);
   CheckNot(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(0);

   // re-add
   Add(-999);
   Check(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(1);

   Add(-998); // TryAdd not avail
   Check(-998);
   Check(-999);
   CheckCount(2);


   for i :=   0 to  99 do Add(i);
   for i :=   0 to  99 do Check(i);
   for i := 100 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);
   CheckCount(102);
   Del(99);
   CheckCount(101);

   for i := 200 to 999 do Add(i);
   for i := 200 to 999 do Check(i);
   for i :=   0 to  98 do Check(i);
   for i :=  99 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);

   for i :=1000 to 9999 do Add(i);
   for i := 200 to 9999 do Check(i);
   for i :=   0 to  98 do Check(i);
   for i :=  99 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);

   Dict.TrimExcess;

   for i := 200 to 9999 do Check(i);
   for i :=   0 to  98 do Check(i);
   for i :=  99 to 199 do CheckNot(i);
   for i := -99 to  -1 do CheckNot(i);

   Dict.Clear;
   CheckCount(101);
   for i :=   0 to  999 do CheckNot(i);

   GotExcept := False;
//exit; // //////////////////////////////// NEXT LINE CRASH
   Add(-999);  // fails for TCuckooD2
   try
     Add(-999);
   except
     GotExcept := True;
   end;
   if not GotExcept then
     raise exception.Create('did not reject dup');
   Check(-999);
   CheckNot(0);
   CheckNot(1);
   CheckCount(1);

   Dict.AddOrSetValue('998', 'B');
   if Dict['998'] <> 'B' then
     raise Exception.Create('TryOrSet failed');
   Check(-999);
   CheckCount(2);

   Dict.AddOrSetValue('999', 'A');
   if Dict['999'] <> 'A' then
     raise Exception.Create('TryOrSet failed');
   if Dict['998'] <> 'B' then
     raise Exception.Create('TryOrSet failed');
   CheckCount(2);

   Dict.Destroy;
end;



begin
   specialize Run<specialize TDictionary<string, string> >();
   specialize Run<specialize TObjectOpenAddressingLP<string, string> >();
   specialize Run<specialize TOpenAddressingLPT<string, string> >();
   specialize Run<specialize TOpenAddressingQP<string, string> >();
   specialize Run<specialize TOpenAddressingDH<string, string> >();
   specialize Run2<specialize TCuckooD2<string, string> >();
   specialize Run2<specialize TCuckooD4<string, string> >();
   specialize Run2<specialize TCuckooD6<string, string> >();
   specialize Run2<specialize TFastHashMap<string, string> >();
   specialize Run2<specialize THashMap<string, string> >();

end.



More information about the fpc-devel mailing list