Delphi XE8中TList<T>存在bug,需要解决方法

38

升级到XE8后,我们的一些项目开始破坏数据。看起来是TList实现中的一个错误。

program XE8Bug1;
{$APPTYPE CONSOLE}

uses
  System.SysUtils, Generics.Collections;

type
  TRecord = record
    A: Integer;
    B: Int64;
  end;

var
  FRecord: TRecord;
  FList: TList<TRecord>;

begin
  FList := TList<TRecord>.Create;
  FRecord.A := 1;
  FList.Insert(0, FRecord);
  FRecord.A := 3;
  FList.Insert(1, FRecord);
  FRecord.A := 2;
  FList.Insert(1, FRecord);
  Writeln(IntToStr(FList[0].A) + IntToStr(FList[1].A) + IntToStr(FList[2].A));

end.

这段代码在XE7及之前的版本中输出"123"(应该如此),但在XE8中输出"120"。也许有人知道一个快速修复方法?

更新:非官方修复程序在这里


9
通用集合在XE8中已被重新实现。也许在Emba公司它们没有进行任何单元测试。如果您所描述的是正确的(似乎很可能),那么您的解决方案是继续使用XE7。您确实需要提交一个错误报告。 - David Heffernan
10
看起来 Embarcadero 没有有效的测试制度。他们怎么会犯这种错误呢?这是一个基础类。一支运作良好的开发团队应该全面单元测试。这样的问题不应该通过测试。情况糟糕。 - David Heffernan
6
我需要额外支付费用来修复这个(4周前的)产品上的漏洞。 - robsoft
1
相关链接:http://stackoverflow.com/questions/30850911/firemonkey-listview-item-indexes-not-updating 我猜他们认为没有人需要使用Insert,所以从未测试过它。 - Jerry Dodge
显示剩余2条评论
1个回答

32

我发现现在TList<T>.Insert方法调用TListHelper.InternalInsertX取决于数据大小,在我的情况下:

procedure TListHelper.InternalInsertN(AIndex: Integer; const Value);
var
  ElemSize: Integer;
begin
  CheckInsertRange(AIndex);

  InternalGrowCheck(FCount + 1);
  ElemSize := ElSize;
  if AIndex <> FCount then
    Move(PByte(FItems^)[AIndex * ElemSize], PByte(FItems^)[(AIndex * ElemSize) + 1], (FCount - AIndex) * ElemSize);
  Move(Value, PByte(FItems^)[AIndex * ElemSize], ElemSize);
  Inc(FCount);
  FNotify(Value, cnAdded);
end;

我看到问题出现在第一个Move调用中。目的地应该是:
PByte(FItems^)[(AIndex + 1) * ElemSize]

不是

PByte(FItems^)[(AIndex * ElemSize) + 1]

啊!

终于,在我的项目中使用了Delphi XE7的System.Generics.Defaults.pas和System.Generics.Collections.pas单元,现在一切正常。

更新:据我所知,RTL没有受到影响,因为它不使用TList<T>.Insert来处理大小大于8的T(或者我错过了什么?)


3
通过恢复XE7实现来进行修复并不是一个坏主意。你会更加信任它。是的,你可以修补这个方法,但你是否想知道他们还做错了什么? - David Heffernan
1
尽管如果您更换整个单元,最好以一种方式进行,以使RTL/VCL/FMX等也使用固定单位。否则,您可能会受到此缺陷的其他表现的伤害。 - David Heffernan
经验不足的开发人员的口头禅:“为什么要看旧代码,当你可以用‘更好的’方式编写新代码”;-)(我遇到了太多这样的开发人员,Embarcadero竟然也有他们,这让人失望) - Hans
1
RTL 中有很多地方使用 TList<T>.Insert,这怎么可能没有被检测到呢? - LU RD
只有当T的大小大于8时,这个错误才会持续存在,因此RTL中受影响的地方很少,其中大多数都使用{$IFDEF POSIX}进行了舍入 :) - BofA
1
@BofA SizeOf(T)>8 并且类型是非托管的。也许Emba通过更喜欢动态分配类而不是自动分配记录来摆脱它。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接