Generics.Defaults: Bug

There is a bug in Generics.Defaults unit in Delphi 2009. If you read my previous post: Generic Defaults you should know already about the “3 bytes long” data types and how are those passed as const parameters to a function. I know I had problems with that (had to create a special case for those) so how did CodeGear developers get around that? Simply – they didn’t.

Consider this piece of code:

type
 TMyStruct = packed record
   a1, a2, a3 : Byte;
 end;

procedure TestDefaults()
var
  comp   : IComparer<TMyStruct>;
  s1, s2 : TMyStruct;
  I      : Integer;
begin
  comp := TComparer<TMyStruct>.Default;
  FillChar(s1, 3, 0);
  FillChar(s2, 3, 1);

  for I := 0 to 10000 do
      comp.Compare(s1, s2);
end;

After a few loops you will get a pretty ugly “Access Violation” because the stack is corrupted. This happens because the TComparer.Default; will select the “generic binary” support class. These are defined as following:

  function Equals_Binary(Inst: PSimpleInstance; 
      const Left, Right): Boolean;

  function Compare_Binary(Inst: PSimpleInstance; 
      const Left, Right): Integer;

  function GetHashCode_Binary(Inst: PSimpleInstance; 
      const Value): Integer;

Surely they expect Left and Right parameters to be passed as addresses in EDX/ECX registers. The problem is obvious: 3 byte data structures are passes in the stack! The called function is expected to cleanup the stack afterward using a “ret 8” instruction. This means that the stack keeps building up 2 integers on each call until it happens to address invalid memory (this is why I used a loop there).

I know it’s a very “exotic” case where you actually have to use a packed data-type (defaults are aligned to 4 bytes so there is no problem) but it’s still a bug.

More to come.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.