rsrx
rsrx

Reputation: 1473

ZDecompressStream() causes memory leak

I've been using ZLib functions to compress/uncompress streams in memory. In case when I try to uncompress invalid stream, it leaks memory. The following code would leak memory:

uses
  Winapi.Windows, System.Classes, System.ZLib;

function DecompressStream(const AStream: TMemoryStream): Boolean;
var
  ostream: TMemoryStream;
begin
  ostream := TMemoryStream.Create;
  try
    AStream.Position := 0;

    // ISSUE: Memory leak happening here
    try
      ZDecompressStream(AStream, ostream);
    except
      Exit(FALSE);
    end;

    AStream.Clear;
    ostream.Position := 0;
    AStream.CopyFrom(ostream, ostream.Size);
    result := TRUE;
  finally
    ostream.Free;
  end;
end;

var
  s: TMemoryStream;

begin
  ReportMemoryLeaksOnShutdown := TRUE;

  s := TMemoryStream.Create;
  try
    DecompressStream(s);
  finally
    s.Free;
  end;
end.

I try to decompress empty TMemoryStream here and at the end of execution it shows that memory leak happened. Testing on Delphi XE2.

Any ideas how to prevent this leak to happen, because in real world there would be a chance for my application to try to decompress invalid stream and leak the memory there.

QC: http://qc.embarcadero.com/wc/qcmain.aspx?d=120329 - claimed fixed starting with XE6

Upvotes: 4

Views: 1531

Answers (1)

David Heffernan
David Heffernan

Reputation: 613582

It's a bug in the Delphi RTL code. The implementation of ZDecompressStream raises exceptions and then fails to perform tidy up. Let's look at the code:

procedure ZDecompressStream(inStream, outStream: TStream);
const
  bufferSize = 32768;
var
  zstream: TZStreamRec;
  zresult: Integer;
  inBuffer: TBytes;
  outBuffer: TBytes;
  inSize: Integer;
  outSize: Integer;
begin
  SetLength(inBuffer, BufferSize);
  SetLength(outBuffer, BufferSize);
  FillChar(zstream, SizeOf(TZStreamRec), 0);

  ZCompressCheck(InflateInit(zstream));   <--- performs heap allocation

  inSize := inStream.Read(inBuffer, bufferSize);

  while inSize > 0 do
  begin
    zstream.next_in := @inBuffer[0];
    zstream.avail_in := inSize;

    repeat
      zstream.next_out := @outBuffer[0];
      zstream.avail_out := bufferSize;

      ZCompressCheck(inflate(zstream, Z_NO_FLUSH));

      // outSize := zstream.next_out - outBuffer;
      outSize := bufferSize - zstream.avail_out;

      outStream.Write(outBuffer, outSize);
    until (zstream.avail_in = 0) and (zstream.avail_out > 0);

    inSize := inStream.Read(inBuffer, bufferSize);
  end;

  repeat
    zstream.next_out := @outBuffer[0];
    zstream.avail_out := bufferSize;

    zresult := ZCompressCheck(inflate(zstream, Z_FINISH));

    // outSize := zstream.next_out - outBuffer;
    outSize := bufferSize - zstream.avail_out;

    outStream.Write(outBuffer, outSize);
  until (zresult = Z_STREAM_END) and (zstream.avail_out > 0);

  ZCompressCheck(inflateEnd(zstream));   <--- tidy up, frees heap allocation
end;

I've taken this from my XE3, but I believe that it is essentially the same in all versions. I've highlighted the problem. The call to inflateInit allocates memory off the heap. It needs to be paired with a call to inflateEnd. Because ZCompressCheck raises exceptions in the face of errors, the call to inflateEnd never happens. And hence the code leaks.

The other calls to inflateInit and inflateEnd in that unit are correctly protected with try/finally. It just appears to be the use in this function that is erroneous.

My recommendation is that you replace the Zlib unit with a version that is implemented correctly.

Upvotes: 3

Related Questions