在Stream.read中使用Longint计数和Int64大小,这样做是否危险?

9

我正在研究TMemoryStream类,并发现以下程序:

procedure TMemoryStream.LoadFromStream(Stream: TStream);
var
  Count: Longint;
begin
  Stream.Position := 0;
  Count := Stream.Size; // <-- assigning Int64 to Longint
  SetSize(Count);
  if Count <> 0 then Stream.ReadBuffer(FMemory^, Count);
end;

我经常看到这种模式,其中Int64被赋值给Longint。
我的理解是,在32位和64位Windows中,Longint为4个字节,而Int64为8个字节,因此如果我的文件大小为$1 FFFF FFFF == 8589934591 == 8 GB,那么这个程序将无法读取,因为最终的计数将是$ FFFF FFFF == -1
我不明白为什么允许这样做,也许没有考虑到这一点(也许没有多少人尝试读取一个8+ GB的文件)。

1
@user2864740 如果超过2GB,这个程序也会失败。我看到的很混乱。 - Nasreddine Galfout
3
我会尽力进行翻译。以下是需要翻译的内容:“I'd call it a bug. I noticed that "unsafe typecast" warning is set to False by default. But even after setting it to True, I got no warning for the clearly unsafe assignment (even with explicit typecast). _In my experience, Delphi has always been dodgy with it's warnings. I know var and out parameters wreaked havoc with "uninitialised warnings" in older versions. And it doesn't warn about with :P_”我会称其为一个错误。我注意到,“不安全的类型转换”警告默认设置为False。但是即使将其设置为True,我在明显不安全的分配(即使使用显式类型转换)时也没有收到警告。就我的经验而言,Delphi的警告一直不太可靠。我知道在早期版本中,** var out 参数会对“未初始化警告”造成严重影响。而且它不会对 with **发出警告 :P - Disillusioned
2
我不会这样做。我会使用文件映射。 - Victoria
1
@RemyLebeau OP 以 TMemoryStream 为例。但问题适用于任何 Int64Longint 的组合。未能发现这些差异可能导致意外的生产错误。强类型语言的目的是尝试避免这种情况的发生。适当的编译器警告并非不合理的期望。 - Disillusioned
1
@NasreddineAbdelillahGalfout 在我之前的评论基础上进行扩展。DVD文件通常大于1 GB。如果您必须在观看电影之前始终将整个文件加载到内存中,那么这不会让人感到沮丧吗?其中有一个重要的线索。;) 即计划从一开始就进行动态处理。您的文件结构不应以任何方式期望您加载整个文件以便进行处理。这将是一个更“友好”的设计。 - Disillusioned
显示剩余14条评论
1个回答

1

我已经提交了一个工单,据说在东京10.2版中已经修复了这个问题。这是64位编译的一个问题。

https://quality.embarcadero.com/browse/RSP-19094

TCustomMemoryStreamTMemoryStream中,大于2GB的文件存在问题。在TMemoryStream中,问题很简单,因为本地变量需要声明为NativeInt而不是LongInt,容量需要更改为NativeInt。在TCustomMemoryStream中,问题更加微妙,因为TCustomMemoryStream.Read方法都将Int64-Int64计算的结果直接赋值给LongInt。即使这个计算的结果不大于一个LongInt,也会发生溢出。

如果您想在Seattle中修复此问题,则需要进行代码挂钩、替换System.Classes单元或自己编写TMemoryStream的替代类。请注意,对于最后一种选项,您还需要替换TBytesStreamTStringStream,因为它们是从TMemoryStream派生的。

最后一种选择的另一个问题是第三方组件不会有您的“修复程序”。对于我们来说,只有少数几个地方需要处理大于2GB的文件,因此我们将这些地方切换过去。
对于TCustomMemoryStream.Read的修复(必须应用于两种方法),看起来像这样:
function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint;
{ These 2 lines are new }
var
  remaining: Int64;
begin
  if (FPosition >= 0) and (Count >= 0) then
  begin
    remaining{Result} := FSize - FPosition;
    if remaining{Result} > 0 then
    begin
      if remaining{Result} > Count then 
        Result := Count
      else
        Result := remaining;
      Move((PByte(FMemory) + FPosition)^, Buffer, Result);
      Inc(FPosition, Result);
      Exit;
    end;
  end;
  Result := 0;
end;

很高兴听到这个消息。据我所知,东京仍然存在一些漏洞,需要一些时间才能升级。我已经创建了我的流修复程序,在测试之后它非常稳定。至于第三方组件,我认为避免使用那些你不熟悉内部情况的东西总是一个好主意。 - Nasreddine Galfout
1
@NasreddineAbdelillahGalfout 请确保您还修复了TCustomMemoryStream.Read方法,因为在读取数据时,这有时会导致溢出,具体取决于您读取的数据量。 - Graymatter
是的,测试已经处理了那个。 - Nasreddine Galfout

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