Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal static partial class ZLibNative
/// <summary>
/// ZLib stream descriptor data structure
/// Do not construct instances of <see cref="ZStream" /> explicitly.
/// Always use <see cref="ZLibNative.CreateZLibStreamForDeflate" /> or <see cref="ZLibNative.CreateZLibStreamForInflate" /> instead.
/// Always use <see cref="ZLibNative.ZLibStreamHandle.CreateForDeflate" /> or <see cref="ZLibNative.ZLibStreamHandle.CreateForInflate" /> instead.
/// Those methods will wrap this structure into a <see cref="ZLibStreamHandle" /> and thus make sure that it is always disposed correctly.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
Expand Down
92 changes: 74 additions & 18 deletions src/libraries/Common/src/System/IO/Compression/ZLibNative.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Security;
Expand Down Expand Up @@ -173,6 +174,61 @@ public ZLibStreamHandle()
SetHandle(IntPtr.Zero);
}

public static ZLibStreamHandle CreateForDeflate(CompressionLevel level, int windowBits,
int memLevel, CompressionStrategy strategy)
{
ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle();
ErrorCode errC;

try
{
errC = zLibStreamHandle.DeflateInit2_(level, windowBits, memLevel, strategy);
}
catch (Exception cause) // could not load the ZLib dll
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: cause is an unusual name for an exception. We typically use e, ex, or something like that.

{
zLibStreamHandle.Dispose();
throw new ZLibException(SR.ZLibErrorDLLLoadError, cause);
}

if (errC is not ErrorCode.Ok)
{
string zlibErrorMessage = zLibStreamHandle.GetErrorMessage();
string exceptionMessage = GenerateExceptionMessage(errC);

zLibStreamHandle.Dispose();
throw new ZLibException(exceptionMessage, "deflateInit2_", (int)errC, zlibErrorMessage);
}

return zLibStreamHandle;
}

public static ZLibStreamHandle CreateForInflate(int windowBits)
{
ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle();
ErrorCode errC;

try
{
errC = zLibStreamHandle.InflateInit2_(windowBits);
}
catch (Exception cause) // could not load the ZLib dll
{
zLibStreamHandle.Dispose();
throw new ZLibException(SR.ZLibErrorDLLLoadError, cause);
}

if (errC is not ErrorCode.Ok)
{
string zlibErrorMessage = zLibStreamHandle.GetErrorMessage();
string exceptionMessage = GenerateExceptionMessage(errC);

zLibStreamHandle.Dispose();
throw new ZLibException(exceptionMessage, "inflateInit2_", (int)errC, zlibErrorMessage);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this identical to the same block in the deflate method, other than the const function name passed to the throw? Can you lift this into a shared helper? Could you dedup both methods almost entirely with a delegate that's just used to parameterize the Deflate/InflateInit2 call? You could have that delegate accept a tuple to avoid a closure allocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created the shared helper and moved the logic down into DeflateInit2_ and InflateInit2_, and this renders CreateForDeflate and CreateForInflate nearly identical.

Do you mind if I deal with the duplication in a future PR? I'm planning to adjust ZLibStreamHandle and the PAL so that it uses its handle field and we can marshal usable handles directly, and this'll change these methods anyway.


return zLibStreamHandle;
}

public override bool IsInvalid
{
get { return handle == new IntPtr(-1); }
Expand Down Expand Up @@ -228,10 +284,9 @@ private void EnsureState(State requiredState)
throw new InvalidOperationException("InitializationState != " + requiredState.ToString());
}

public unsafe ErrorCode DeflateInit2_(CompressionLevel level, int windowBits, int memLevel, CompressionStrategy strategy)
private unsafe ErrorCode DeflateInit2_(CompressionLevel level, int windowBits, int memLevel, CompressionStrategy strategy)
{
EnsureNotDisposed();
EnsureState(State.NotInitialized);
Debug.Assert(InitializationState == State.NotInitialized);

fixed (ZStream* stream = &_zStream)
{
Expand Down Expand Up @@ -267,10 +322,9 @@ public unsafe ErrorCode DeflateEnd()
}
}

public unsafe ErrorCode InflateInit2_(int windowBits)
private unsafe ErrorCode InflateInit2_(int windowBits)
{
EnsureNotDisposed();
EnsureState(State.NotInitialized);
Debug.Assert(InitializationState == State.NotInitialized);

fixed (ZStream* stream = &_zStream)
{
Expand Down Expand Up @@ -317,21 +371,23 @@ public unsafe ErrorCode InflateEnd()
}
}

// This can work even after XxflateEnd().
// This can work even after XxflateEnd(). Gets the error message from the native library.
public unsafe string GetErrorMessage() => Utf8StringMarshaller.ConvertToManaged(_zStream.msg) ?? string.Empty;
}

public static ErrorCode CreateZLibStreamForDeflate(out ZLibStreamHandle zLibStreamHandle, CompressionLevel level,
int windowBits, int memLevel, CompressionStrategy strategy)
{
zLibStreamHandle = new ZLibStreamHandle();
return zLibStreamHandle.DeflateInit2_(level, windowBits, memLevel, strategy);
}
private static string GenerateExceptionMessage(ErrorCode nativeErrorCode)
=> nativeErrorCode switch
{
// Not enough memory
ErrorCode.MemError => SR.ZLibErrorNotEnoughMemory,

public static ErrorCode CreateZLibStreamForInflate(out ZLibStreamHandle zLibStreamHandle, int windowBits)
{
zLibStreamHandle = new ZLibStreamHandle();
return zLibStreamHandle.InflateInit2_(windowBits);
}
// zlib library is incompatible with the version assumed
ErrorCode.VersionError => SR.ZLibErrorVersionMismatch,

// Parameters are invalid
ErrorCode.StreamError => SR.ZLibErrorIncorrectInitParameters,

_ => SR.Format(SR.ZLibErrorUnexpected, (int)nativeErrorCode)
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
<value>The version of the underlying compression routine does not match expected version.</value>
</data>
<data name="ZLibErrorUnexpected" xml:space="preserve">
<value>The underlying compression routine returned an unexpected error code.</value>
<value>The underlying compression routine returned an unexpected error code {0}.</value>
</data>
<data name="CDCorrupt" xml:space="preserve">
<value>Central Directory corrupt.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ internal DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen, int
if (!stream.CanRead)
throw new ArgumentException(SR.NotSupported_UnreadableStream, nameof(stream));

_inflater = new Inflater(windowBits, uncompressedSize);
_inflater = Inflater.CreateInflater(windowBits, uncompressedSize);
_stream = stream;
_mode = CompressionMode.Decompress;
_leaveOpen = leaveOpen;
Expand Down Expand Up @@ -114,7 +114,7 @@ internal void InitializeDeflater(Stream stream, ZLibNative.CompressionLevel comp
if (!stream.CanWrite)
throw new ArgumentException(SR.NotSupported_UnwritableStream, nameof(stream));

_deflater = new Deflater(compressionLevel, strategy, windowBits, GetMemLevel(compressionLevel));
_deflater = Deflater.CreateDeflater(compressionLevel, strategy, windowBits, GetMemLevel(compressionLevel));

_stream = stream;
_mode = CompressionMode.Compress;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Buffers;
using System.Diagnostics;
using System.Security;

using ZErrorCode = System.IO.Compression.ZLibNative.ErrorCode;
using ZFlushCode = System.IO.Compression.ZLibNative.FlushCode;
Expand All @@ -28,37 +27,9 @@ internal sealed class Deflater : IDisposable
// on the stream explicitly.
private object SyncLock => this;

internal Deflater(ZLibNative.CompressionLevel compressionLevel, ZLibNative.CompressionStrategy strategy, int windowBits, int memLevel)
private Deflater(ZLibNative.ZLibStreamHandle zlibStream)
{
Debug.Assert(windowBits >= minWindowBits && windowBits <= maxWindowBits);

ZErrorCode errC;
try
{
errC = ZLibNative.CreateZLibStreamForDeflate(out _zlibStream, compressionLevel, windowBits, memLevel, strategy);
}
catch (Exception cause)
{
throw new ZLibException(SR.ZLibErrorDLLLoadError, cause);
}

switch (errC)
{
case ZErrorCode.Ok:
return;

case ZErrorCode.MemError:
throw new ZLibException(SR.ZLibErrorNotEnoughMemory, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());

case ZErrorCode.VersionError:
throw new ZLibException(SR.ZLibErrorVersionMismatch, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());

case ZErrorCode.StreamError:
throw new ZLibException(SR.ZLibErrorIncorrectInitParameters, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());

default:
throw new ZLibException(SR.ZLibErrorUnexpected, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());
}
_zlibStream = zlibStream;
}

~Deflater()
Expand All @@ -77,9 +48,12 @@ private void Dispose(bool disposing)
if (!_isDisposed)
{
if (disposing)
{
_zlibStream.Dispose();
}

DeallocateInputBufferHandle();
// Unpin the input buffer, but avoid modifying the ZLibStreamHandle (which may have been disposed of).
DeallocateInputBufferHandle(resetStreamHandle: false);
_isDisposed = true;
}
}
Expand Down Expand Up @@ -129,7 +103,7 @@ internal int GetDeflateOutput(byte[] outputBuffer)
// Before returning, make sure to release input buffer if necessary:
if (0 == _zlibStream.AvailIn)
{
DeallocateInputBufferHandle();
DeallocateInputBufferHandle(resetStreamHandle: true);
}
}
}
Expand Down Expand Up @@ -179,12 +153,16 @@ internal bool Flush(byte[] outputBuffer, out int bytesRead)
return ReadDeflateOutput(outputBuffer, ZFlushCode.SyncFlush, out bytesRead) == ZErrorCode.Ok;
}

private void DeallocateInputBufferHandle()
private void DeallocateInputBufferHandle(bool resetStreamHandle)
{
lock (SyncLock)
{
_zlibStream.AvailIn = 0;
_zlibStream.NextIn = ZLibNative.ZNullPtr;
if (resetStreamHandle)
{
_zlibStream.AvailIn = 0;
_zlibStream.NextIn = ZLibNative.ZNullPtr;
}

_inputBufferHandle.Dispose();
}
}
Expand Down Expand Up @@ -217,5 +195,14 @@ private ZErrorCode Deflate(ZFlushCode flushCode)
throw new ZLibException(SR.ZLibErrorUnexpected, "deflate", (int)errC, _zlibStream.GetErrorMessage());
}
}

public static Deflater CreateDeflater(ZLibNative.CompressionLevel compressionLevel, ZLibNative.CompressionStrategy strategy, int windowBits, int memLevel)
{
Debug.Assert(windowBits >= minWindowBits && windowBits <= maxWindowBits);

ZLibNative.ZLibStreamHandle zlibStream = ZLibNative.ZLibStreamHandle.CreateForDeflate(compressionLevel, windowBits, memLevel, strategy);

return new Deflater(zlibStream);
}
}
}
Loading
Loading