如何解决 Delphi 10 中 TList<_AnyDynamicArrays_> 的 bug?

9

我是一个有用的助手,可以进行文本翻译。

我在 Delphi 10 Seattle Update 1 中发现了一个错误。我们来看以下代码:

procedure TForm1.Button1Click(Sender: TObject);
begin
//----------We crash here----------------
  FList.Items[0] := SplitString('H:E', ':');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FList := TList<TStringDynArray>.Create;
  FList.Add(SplitString('H:E', ':'));
  FList.Items[0] := SplitString('H:E', ':');
end;

乍一看,TList<T>似乎没有正确管理其包含的动态数组的生命周期,但如果以64位编译,则它可以正常工作,只有在32位崩溃(我明白这并不意味着64位不存在该错误...)。
请注意,使用了SplitString函数,因为它是我能想到的第一个返回动态数组的函数。原始问题出现在TList<TBookmark>上,该问题表现相同。
可以通过像这样重写Button1Click程序来解决该错误:
procedure TForm1.Button1Click(Sender: TObject);
var MyArray : TStringDynArray;
begin
  MyArray := FList.Items[0];
  FList.Items[0] := SplitString('H:E', ':');
  //----------Yeah! We don't crash anymore!-----------
end;

但是逐一修改我的所有应用程序以解决这个 bug 并不是我首选的选项。如果可能的话,我更愿意找到问题所在的例程并在内存中打补丁。

如果有人遇到这个问题并找到了解决方法,我将不胜感激。否则,如果我找到了一个适当的解决方法,我会发布我的方法。

如果问题仍然存在于柏林,请留言告知。


柏林更新2中出现了相同的错误。 - LU RD
1
从报告的关于通用 TList 的错误来看,它似乎是一个雷区。 - LU RD
1个回答

8

毕竟,这个漏洞在64位系统中仍然存在。对于TStringDynArray,它没有崩溃,但是对于其他动态数组类型会崩溃。

问题的源代码位于Generics.Collections中的以下代码:

procedure TListHelper.DoSetItemDynArray(const Value; AIndex: Integer);
type
  PBytes = ^TBytes;
var
  OldItem: Pointer;
begin
  OldItem := nil;
  try
    CheckItemRangeInline(AIndex);

    TBytes(OldItem) := PBytes(FItems^)[AIndex];
    PBytes(FItems^)[AIndex] := TBytes(Value);

    FNotify(OldItem, cnRemoved);
    FNotify(Value, cnAdded);
  finally
    DynArrayClear(OldItem, FTypeInfo); //Bug is here.
  end;
end;

发生的情况是,DynArrayClear 接收到了错误的 TypeInfo。对于 TList<TStringDynArray>,传递的是 TypeInfo(TArray<TStringDynArray>) 而不是 TypeInfo(TStringDynArray)。根据我的观察,正确的调用应该是:
DynArrayClear(OldItem, pDynArrayTypeInfo(NativeInt(FTypeInfo) + pDynArrayTypeInfo(FTypeInfo).Name).elType^);

该过程是私有的,这使得拦截变得复杂。我利用了记录助手仍然可以访问Delphi 10中记录的私有部分的事实来拦截它。我猜这对柏林版本的用户来说会更加复杂。

function TMyHelper.GetDoSetItemDynArrayAddr: TDoSetItemDynArrayProc;
begin
  Result := Self.DoSetItemDynArray;
end;

希望Embarcadero总有一天能解决这个问题...


如果您使用 ElType,则会得到 pDynArrayTypeInfo(NativeInt(FTypeInfo) + pDynArrayTypeInfo(FTypeInfo).Name).elType^ - LU RD
1
这个辅助工具好像没有为每个特殊情况编写单元测试…… - David Heffernan
@LURD 如果你的意思是我可以直接使用 pDynArrayTypeInfo(FTypeInfo).elType^,那么不行。虽然 pDynArrayTypeInfo(FTypeInfo).Name 被定义为一个字节,但它实际上是一个短字符串,这在 _DynArrayClear 的代码中可以看出来。 - Ken Bourassa
1
ElType is a property in TListHelper. The getter is defined as: function TListHelper.GetElType: Pointer; begin Result := PDynArrayTypeInfo(PByte(FTypeInfo) + PDynArrayTypeInfo(FTypeInfo).name).elType^; end; Just use DynArrayClear(OldItem,ElType); - LU RD

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