18

I don't understand why this very simple code failed? I'm on Delphi Tokyo release 2.

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Generics.Collections;

procedure Main;
var
  aQueue: TQueue<TBytes>;
  aBytes: TBytes;
begin
  aQueue := TQueue<TBytes>.create;

  aBytes := TEncoding.UTF8.GetBytes('abcd');
  aQueue.Enqueue(aBytes);
  aBytes := aQueue.Dequeue;
  Writeln(Length(aBytes)); // outputs 4 as expected

  aBytes := TEncoding.UTF8.GetBytes('abcd');
  aQueue.Enqueue(aBytes);
  aBytes := aQueue.Dequeue;
  Writeln(Length(aBytes)); // outputs 0
end;

begin
  Main;
  Readln;
end.

Is this a bug?

NOTE: The code works correctly on XE4, but fails also on Berlin.

vostock
  • 229
  • 1
  • 7
  • the guy who vote -1, can he share in the comment why he vote -1 ? same for the other who will do the same ... – vostock Apr 19 '18 at 13:46
  • no no, it's this exact code (copy past on a blank new project) ! nothing else ... i m on delphi tokyo release 2 – vostock Apr 19 '18 at 13:52
  • @DavidHeffernan : on with version of tokyo you are ? – vostock Apr 19 '18 at 13:53
  • I'm not using Tokyo, but the question did not state a specific Delphi version. – David Heffernan Apr 19 '18 at 13:55
  • ok, so on tokyo it's failed, i just try to launch it on xe4 and on xe4 it's work ... someone can confirm ? – vostock Apr 19 '18 at 13:57
  • Btw, a proper [MCVE] could be compiled directly. Make it with a console app and output using Writeln. I will edit your question to demonstrate. – David Heffernan Apr 19 '18 at 14:00
  • @DavidHeffernan : i try on berlin, it's failed also on berlin ... what version are you using ? – vostock Apr 19 '18 at 14:00
  • FWIW, your code works correctly also in XE7. You need to visit the quality portal of the supplier, and file an error report, if it isn't there already. – Tom Brunberg Apr 19 '18 at 14:00
  • @David, glad you finally received an MCVE. Now, could you think more about searchability of this question? About the way how to search it by the compiler error message? Or do you think that someone is able to find their problem by _"Is delphi TQueue buggy?"_ Not speaking that it doesn't contain what is wrong with the code and as such can be closed. If you are asking for why I say this; it is because you are many times strict just for an MCVE but may not think much about the question in general ;-) vostock, it was me because of what I said in this comment (fix it and I'll change). – Victoria Apr 19 '18 at 16:52
  • @Victoria: I updated the title so you can revert your vote now ;) but admit writing -1 without saying anything is just wasting of time ... at least david say because he can't reproduce it and thus help a lot to understand that 1) this was really a bug and 2) don't affect all version. how many developer i advise to come here, they ask a question and get -x on their first question without any comment, get immediately blacklisted and never come here again thinking this community is useless (or made of savant monkey as you prefer) ... on this point so is totally absurd or stupid as you prefer – vostock Apr 19 '18 at 17:10
  • @vostock, vote removed. But it's still a closable question. Think about a [question like this](https://stackoverflow.com/q/49921281/8041231) though. There is no MCVE and I wasn't able to reproduce because of my fault. Yet it's searchable for others and clearly describes the problem. That's a question that deserves more than your score. If you added even what compiler rejects, I would even upvote.. But thank you anyway! ;-) – Victoria Apr 19 '18 at 17:21
  • @Victoria: Sometime i think too much verbosity kill the question... thanks to you too ! – vostock Apr 19 '18 at 17:31
  • @vostock, thank you for that! Me and many others here appreciate that, I guess. But try to think also about searchability for the others with the same problem as well ;-) – Victoria Apr 19 '18 at 17:36
  • @Victoria: i agree but unfortunately google (sick please everyone stop using google) will be soon able to do this job for you ... try this as example : https://cloud.google.com/vision/ post any picture and he can found the number of cake in it, if the woman is nude or doing something sexy, the type of car, the plane, the tree, etc. if you gave a picture of a cake it's close to gave you the number of calories! IA is coming, fast ... in the next 5 years we will not care anymore of choosing good keyword ... – vostock Apr 19 '18 at 18:37
  • 1
    Yikes. I'm used to environments where "is the standard library buggy" questions are normally closed with a "no, it's you". – chrylis -cautiouslyoptimistic- Apr 19 '18 at 19:02
  • 4
    @chrylis welcome to delphi – David Heffernan Apr 19 '18 at 20:20
  • @chrylis, yes, welcome to Delphi tag. Where double meter is used by users. Some questions doesn't even need to include problem description to be highly rated whilst some are downvoted by the same users for pointless details :) – Victoria Apr 20 '18 at 04:43
  • @Victoria You are right about the title, I should have improved that when editing. But you also could have done so. – David Heffernan Apr 20 '18 at 06:45

2 Answers2

18

This is indeed a bug. The code works correctly in XE7, but not XE8. In XE8 the output is 0 for both attempts.

Certainly the XE8 generic collections were very buggy and subsequent releases fixed many of the defects. Clearly not all have been fixed.

In XE8 Embarcadero attempted to address the issue of generic bloat caused by weaknesses in their compile/link model. Unfortunately, instead of tackling the problem at the root, they chose instead to address the issue in the library code for generic collections. Doing so they completely broke many of the generic collection classes, proving that their unit testing was weak. And of course, by addressing the problem this way they failed to address the issue of generic bloat for classes other than those in the generic collections. All in all, a sorry story that is seemingly still not over.

loki has just submitted a bug report: RSP-20400.

Note that this bug report is incorrect because (at least according to Stefan Glienke) the bug has been fixed in Tokyo 10.2.3. So upgrading to 10.2.3 should be the simplest way to resolve the problem.

Perhaps this bug report is more appropriate: RSP-17728.

Writing a generic queue isn't even difficult. Here's one that is known to work:

type
  TQueue<T> = class
  private
    FItems: TArray<T>;
    FCount: Integer;
    FFront: Integer;
  private
    function Extract(Index: Integer): T; inline;
    function GetBack: Integer; inline;
    property Back: Integer read GetBack;
    property Front: Integer read FFront;
    procedure Grow;
    procedure RetreatFront; inline;
  public
    property Count: Integer read FCount;
    procedure Clear;
    procedure Enqueue(const Value: T);
    function Dequeue: T;
    function Peek: T;
  public
    type
      TEnumerator = record
      private
        FCollection: TQueue<T>;
        FCount: Integer;
        FCapacity: Integer;
        FIndex: Integer;
        FStartIndex: Integer;
      public
        class function New(Collection: TQueue<T>): TEnumerator; static;
        function GetCurrent: T;
        property Current: T read GetCurrent;
        function MoveNext: Boolean;
      end;
  public
    function GetEnumerator: TEnumerator;
  end;

function GrownCapacity(OldCapacity: Integer): Integer;
var
  Delta: Integer;
begin
  if OldCapacity>64 then begin
    Delta := OldCapacity div 4
  end else if OldCapacity>8 then begin
    Delta := 16
  end else begin
    Delta := 4;
  end;
  Result := OldCapacity + Delta;
end;

{ TQueue<T> }

function TQueue<T>.Extract(Index: Integer): T;
begin
  Result := FItems[Index];
  if IsManagedType(T) then begin
    Finalize(FItems[Index]);
  end;
end;

function TQueue<T>.GetBack: Integer;
begin
  Result := Front + Count - 1;
  if Result>high(FItems) then begin
    dec(Result, Length(FItems));
  end;
end;

procedure TQueue<T>.Grow;
var
  Index: Integer;
  Value: T;
  Capacity: Integer;
  NewItems: TArray<T>;
begin
  Capacity := Length(FItems);
  if Count=Capacity then begin
    SetLength(NewItems, GrownCapacity(Capacity));
    Index := 0;
    for Value in Self do begin
      NewItems[Index] := Value;
      inc(Index);
    end;

    FItems := NewItems;
    FFront := 0;
  end;
end;

procedure TQueue<T>.RetreatFront;
begin
  inc(FFront);
  if FFront=Length(FItems) then begin
    FFront := 0;
  end;
end;

procedure TQueue<T>.Clear;
begin
  FItems := nil;
  FCount := 0;
end;

procedure TQueue<T>.Enqueue(const Value: T);
begin
  Grow;
  inc(FCount);
  FItems[Back] := Value;
end;

function TQueue<T>.Dequeue: T;
var
  Index: Integer;
begin
  Assert(Count>0);
  Result := Extract(Front);
  RetreatFront;
  dec(FCount);
end;

function TQueue<T>.Peek: T;
begin
  Assert(Count>0);
  Result := FItems[Front];
end;

function TQueue<T>.GetEnumerator: TEnumerator;
begin
  Result := TEnumerator.New(Self);
end;

{ TQueue<T>.TEnumerator }

class function TQueue<T>.TEnumerator.New(Collection: TQueue<T>): TEnumerator;
begin
  Result.FCollection := Collection;
  Result.FCount := Collection.Count;
  Result.FCapacity := Length(Collection.FItems);
  Result.FIndex := -1;
  Result.FStartIndex := Collection.Front;
end;

function TQueue<T>.TEnumerator.GetCurrent: T;
var
  ActualIndex: Integer;
begin
  ActualIndex := (FStartIndex + FIndex) mod FCapacity;
  Result := FCollection.FItems[ActualIndex];
end;

function TQueue<T>.TEnumerator.MoveNext: Boolean;
begin
  inc(FIndex);
  Result := FIndex<FCount;
end;
David Heffernan
  • 572,264
  • 40
  • 974
  • 1,389
  • Thanks david, you confirm what i was thinking ... but how i can make it working in the middle time ? – vostock Apr 19 '18 at 14:06
  • 2
    My advice is to use a collection library that works. I know I don't use the RTL generic collections and have written my own. That's a big task, so I would recommend Spring4D's collections. – David Heffernan Apr 19 '18 at 14:07
  • This really makes me consider downgrading our projects from 10 Seattle back to XE7. – Günther the Beautiful Apr 19 '18 at 15:37
  • @GünthertheBeautiful I've never updated our company product beyond XE7 for that reason. I'm also now completely distanced from Generics.Collections, my codebase does not refer to it once. – David Heffernan Apr 19 '18 at 15:40
  • I find a better way to get ride of this bug: use AnsiString instead of TBytes :) in fact their is no real reason to make a difference between then (except when we want to make everything complicated) ... – vostock Apr 19 '18 at 15:49
  • you can not get ride of Generics.Collections :( it's used in more than 482 unit in the delphi source code ... it's part of the root of delphi :( – vostock Apr 19 '18 at 15:50
  • Using `AnsiString` is a bad idea in my view. You've got many better solutions than that. – David Heffernan Apr 19 '18 at 15:54
  • If (and I'm saying IF not WHEN) you are going down the route of storing binary data in a string, you should at least use RawByteString instead of AnsiString... But generally one should refrain from using strings to store binary data... – HeartWare Apr 19 '18 at 22:05
12

To add to David's answer, the bug is in the Enqueue method. The top branch should be handling all reference counted managed types.

  if IsManagedType(T) then
    if (SizeOf(T) = SizeOf(Pointer)) and (GetTypeKind(T) <> tkRecord) then
      FQueueHelper.InternalEnqueueMRef(Value, GetTypeKind(T))
    else
      FQueueHelper.InternalEnqueueManaged(Value)
  else

But here we see that dynamic arrays are conspicuously missing in InternalEnqueueMref, which falls through without doing anything:

procedure TQueueHelper.InternalEnqueueMRef(const Value; Kind: TTypeKind);
begin
  case Kind of
    TTypeKind.tkUString: InternalEnqueueString(Value);
    TTypeKind.tkInterface: InternalEnqueueInterface(Value);
{$IF not Defined(NEXTGEN)}
    TTypeKind.tkLString: InternalEnqueueAnsiString(Value);
    TTypeKind.tkWString: InternalEnqueueWideString(Value);
{$ENDIF}
{$IF Defined(AUTOREFCOUNT)}
    TTypeKind.tkClass: InternalEnqueueObject(Value);
{$ENDIF}
  end;
end;

It's so egregious, in fact, that the compiler actually produces no code for Enqueue when compiled (other than preamble) since the futility of the exercise can be determined from the types at compile time.

Project1.dpr.15: aQueue.Enqueue(aBytes);
0043E19E 8B45F8           mov eax,[ebp-$08]
0043E1A1 8945F4           mov [ebp-$0c],eax
0043E1A4 8B45FC           mov eax,[ebp-$04]
0043E1A7 83C008           add eax,$08
0043E1AA 8945F0           mov [ebp-$10],eax
Project1.dpr.16: aBytes := aQueue.Dequeue;
0043E1AD 8D45EC           lea eax,[ebp-$14]

This bug, therefore, would be expected to affect TQueue<T> for T being any type of dynamic array.

J...
  • 28,957
  • 5
  • 61
  • 128