Reputation: 1473
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
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