Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET 7 regression in ZipArchive #68734

Closed
jonathanpeppers opened this issue Apr 30, 2022 · 15 comments · Fixed by #69524
Closed

.NET 7 regression in ZipArchive #68734

jonathanpeppers opened this issue Apr 30, 2022 · 15 comments · Fixed by #69524

Comments

@jonathanpeppers
Copy link
Member

Description

I ran into this trying to get MAUI workloads running on .NET 7. Our CI caught some test failures here.

Append to an existing ZipEntry with code such as:

using var fileStream = File.Open(path, FileMode.Open, FileAccess.ReadWrite);
using var zip = new ZipArchive(fileStream, ZipArchiveMode.Update);

var oldEntry = zip.GetEntry("foo.txt");
ArgumentNullException.ThrowIfNull(oldEntry);
var newEntry = zip.CreateEntry("foo.txt");

using var newStream = newEntry.Open();

using (var oldStream = oldEntry.Open())
{
    oldStream.CopyTo(newStream);
}

using var writer = new StreamWriter(newStream);
writer.Write("foo");

oldEntry.Delete();

In .NET 6 the code works, in .NET 7 it throws:

Message: 
System.InvalidOperationException : An entry named 'foo.txt' already exists in the archive.

Stack Trace: 
ZipArchive.DoCreateEntry(String entryName, Nullable`1 compressionLevel)
ZipArchive.CreateEntry(String entryName)
ZipArchiveTest.AppendToZip(String path) line 42
ZipArchiveTest.Test1() line 17
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
RuntimeMethodInfo.InvokeNonEmitUnsafe(Object obj, IntPtr* arguments, Span`1 argsForTemporaryMonoSupport, BindingFlags invokeAttr)

Reproduction Steps

Here is an xunit project targeting both .NET 6 and .NET 7: zippy.zip

If you just unzip and run dotnet test you'll see it passes on 6 and fails in 7.

Expected behavior

ZipEntry.CreateEntry() doesn't throw in this example.

Actual behavior

ZipEntry.CreateEntry() throws an exception.

Regression?

Yes, works in .NET 6.

Known Workarounds

There may be a different way to write the above code, but I ported this example from existing code.

Configuration

> dotnet --version
7.0.100-preview.5.22229.2

I'm running on Windows 11, but this also happens on macOS where I saw this on CI.

Other information

I think this may have been introduced in: #60973

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.IO.Compression untriaged New issue has not been triaged by the area owner labels Apr 30, 2022
@ghost
Copy link

ghost commented Apr 30, 2022

Tagging subscribers to this area: @dotnet/area-system-io-compression
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

I ran into this trying to get MAUI workloads running on .NET 7. Our CI caught some test failures here.

Append to an existing ZipEntry with code such as:

using var fileStream = File.Open(path, FileMode.Open, FileAccess.ReadWrite);
using var zip = new ZipArchive(fileStream, ZipArchiveMode.Update);

var oldEntry = zip.GetEntry("foo.txt");
ArgumentNullException.ThrowIfNull(oldEntry);
var newEntry = zip.CreateEntry("foo.txt");

using var newStream = newEntry.Open();

using (var oldStream = oldEntry.Open())
{
    oldStream.CopyTo(newStream);
}

using var writer = new StreamWriter(newStream);
writer.Write("foo");

oldEntry.Delete();

In .NET 6 the code works, in .NET 7 it throws:

Message: 
System.InvalidOperationException : An entry named 'foo.txt' already exists in the archive.

Stack Trace: 
ZipArchive.DoCreateEntry(String entryName, Nullable`1 compressionLevel)
ZipArchive.CreateEntry(String entryName)
ZipArchiveTest.AppendToZip(String path) line 42
ZipArchiveTest.Test1() line 17
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
RuntimeMethodInfo.InvokeNonEmitUnsafe(Object obj, IntPtr* arguments, Span`1 argsForTemporaryMonoSupport, BindingFlags invokeAttr)

Reproduction Steps

Here is an xunit project targeting both .NET 6 and .NET 7: zippy.zip

If you just unzip and run dotnet test you'll see it passes on 6 and fails in 7.

Expected behavior

ZipEntry.CreateEntry() doesn't throw in this example.

Actual behavior

ZipEntry.CreateEntry() throws an exception.

Regression?

Yes, works in .NET 6.

Known Workarounds

There may be a different way to write the above code, but I ported this example from existing code.

Configuration

> dotnet --version
7.0.100-preview.5.22229.2

I'm running on Windows 11, but this also happens on macOS where I saw this on CI.

Other information

I think this may have been introduced in: #60973

Author: jonathanpeppers
Assignees: -
Labels:

area-System.IO.Compression, untriaged

Milestone: -

@danmoseley
Copy link
Member

@jonathanpeppers Can you work around this or are you actively blocked?

@jonathanpeppers
Copy link
Member Author

Let me check on Monday the actual number of test failures this causes. If it’s a small number we might be able to disable some tests for now.

@danmoseley
Copy link
Member

The docs do say " If an entry with the specified path and name already exists in the archive, a second entry is created with the same path and name." That should be considered (if it wasn't already) when we evaluate the breaking change. Although, it seems that the previous behavior was buggy, which is why it was disabled.

I would imagine that the code above could be changed to write the old data into a MemoryStream (or even file) so that you could remove the old entry before writing the new one.

@jonathanpeppers
Copy link
Member Author

So these tests are tricky, they are integration tests that fail in an MSBuild task inside a NuGet package. We'd have to patch the package and push it somewhere.

It looked like I can just add a category on 4 tests to skip them for now. This should be ok, because we will continue running these tests for Xamarin.Android projects alongside .NET 7 Android tests.

@danmoseley
Copy link
Member

Cool. My inclination would be to leave the code as is, pending further feedback on previews. (And document the breaking change and fix the docs) cc @dotnet/area-system-io-compression

@jozkee
Copy link
Member

jozkee commented May 3, 2022

I also believe the breaking change should stay cc @carlossanlop.

@jonathanpeppers can you use a different implementation to append to a zip file (avoiding having a duplicated entry)? e.g:

using var fileStream = File.Open(path, FileMode.Open, FileAccess.ReadWrite);
using var zip = new ZipArchive(fileStream, ZipArchiveMode.Update);

var entry = zip.GetEntry("foo.txt");
ArgumentNullException.ThrowIfNull(entry);
//var newEntry = zip.CreateEntry("foo.txt");

using var stream = entry.Open();
stream.Seek(0, SeekOrigin.End);
//using (var oldStream = oldEntry.Open())
//{
//    oldStream.CopyTo(newStream);
//}

using var writer = new StreamWriter(stream);
writer.Write("foo");

//oldEntry.Delete();

@jozkee jozkee added this to the 7.0.0 milestone May 3, 2022
@jozkee jozkee removed the untriaged New issue has not been triaged by the area owner label May 3, 2022
jonathanpeppers added a commit to jonathanpeppers/XamarinComponents that referenced this issue May 3, 2022
Context: dotnet/runtime#68734

A breaking chang in in .NET 7 has uncovered an issue when using
Xamarin.Build.Download:

    Renaming: AndroidManifest.xml to AndroidManifest.xml
    ...
    (_XamarinBuildDownloadCore target) ->
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-basement/17.6.0/play-services-basement-17.6.0.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicesbasement-17.6.0.aar.
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-tasks/17.2.1/play-services-tasks-17.2.1.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicestasks-17.2.1.aar.
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-base/17.6.0/play-services-base-17.6.0.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicesbase-17.6.0.aar.
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-maps/17.0.1/play-services-maps-17.0.1.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicesmaps-17.0.1.aar.
    1 Warning(s)
    4 Error(s)

The exception is somewhat swallowed here, the underlying error is
something like:

    System.InvalidOperationException : An entry named 'AndroidManifest.xml' already exists in the archive.

This class had various "fixups" to workaround issues in
Xamarin.Android. Many of these issues have long since been fixed.

1. Removal of `aapt/AndroidManifest.xml`. Xamarin.Android has been
   handling this since ~Sept 2018:

dotnet/android@f6c3288

2. Removal of `internal_impl-*.jar`. Xamarin.Android has been handling
   this since ~Feb 2018:

dotnet/android@6a8ea2b

3. Replacement of `android:name=".SomeService"` as shorthand for
   `androidx.foo.SomeService` in the `androidx.foo` package. The use
   of `manifest-merger` solves this issue completely. This setting is
   the default for .NET 6, and has been the default for AndroidX since
   ~July 2020:

dotnet/android-libraries@c6c0e50
jonathanpeppers added a commit to xamarin/XamarinComponents that referenced this issue May 5, 2022
Context: dotnet/runtime#68734

A breaking chang in in .NET 7 has uncovered an issue when using
Xamarin.Build.Download:

    Renaming: AndroidManifest.xml to AndroidManifest.xml
    ...
    (_XamarinBuildDownloadCore target) ->
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-basement/17.6.0/play-services-basement-17.6.0.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicesbasement-17.6.0.aar.
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-tasks/17.2.1/play-services-tasks-17.2.1.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicestasks-17.2.1.aar.
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-base/17.6.0/play-services-base-17.6.0.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicesbase-17.6.0.aar.
        /Users/runner/.nuget/packages/xamarin.build.download/0.11.0/buildTransitive/Xamarin.Build.Download.targets(52,3): error XBD001: Download failed. Please download https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-maps/17.0.1/play-services-maps-17.0.1.aar to a file called /Users/runner/Library/Caches/XamarinBuildDownload/playservicesmaps-17.0.1.aar.
    1 Warning(s)
    4 Error(s)

The exception is somewhat swallowed here, the underlying error is
something like:

    System.InvalidOperationException : An entry named 'AndroidManifest.xml' already exists in the archive.

This class had various "fixups" to workaround issues in
Xamarin.Android. Many of these issues have long since been fixed.

1. Removal of `aapt/AndroidManifest.xml`. Xamarin.Android has been
   handling this since ~Sept 2018:

dotnet/android@f6c3288

2. Removal of `internal_impl-*.jar`. Xamarin.Android has been handling
   this since ~Feb 2018:

dotnet/android@6a8ea2b

3. Replacement of `android:name=".SomeService"` as shorthand for
   `androidx.foo.SomeService` in the `androidx.foo` package. The use
   of `manifest-merger` solves this issue completely. This setting is
   the default for .NET 6, and has been the default for AndroidX since
   ~July 2020:

dotnet/android-libraries@c6c0e50
jonpryor pushed a commit to dotnet/android that referenced this issue May 5, 2022
Context: dotnet/runtime#56989
Context: dotnet/runtime#68734
Context: dotnet/runtime#68914
Context: dotnet/runtime#68701

Changes: dotnet/installer@04e40fa...c7afae6

	% git diff --shortstat 04e40fa9...c7afae69
	 98 files changed, 1788 insertions(+), 1191 deletions(-)

Changes: dotnet/runtime@a21b9a2...c5d40c9

	% git diff --shortstat a21b9a2d...c5d40c9e
	 28347 files changed, 1609359 insertions(+), 1066473 deletions(-)

Changes: dotnet/linker@01c4f59...04c49c9

	% git diff --shortstat 01c4f590...04c49c9d
	 577 files changed, 28039 insertions(+), 10835 deletions(-)

Updates to build with the .NET 7 SDK and use the runtime specified by
the SDK.  We no longer use different SDK & runtime versions.

This produces a 7.0.100 Android workload.

After this is merged we should be able to enable Maestro to consume
future .NET 7 builds from dotnet/installer/main.

~~ Known Issues ~~

AOT+LLVM crashes on startup:

  * dotnet/runtime#68914

Xamarin.Build.Download hits a breaking change with `ZipEntry`:

  * dotnet/runtime#68734
  * xamarin/XamarinComponents#1368

illink outputs different MVIDs per architecture:

  * dotnet/linker#2203
  * dotnet/runtime#67660

Size of `libmonosgen-2.0.so` regressed:

  * dotnet/runtime#68330
  * dotnet/runtime#68354

Newer .NET 7 builds crash on startup:

  * dotnet/runtime#68701
  * This is worked around by *disabling* lazy loading of AOT'd
    assemblies 6dc426f.
  * TODO: re-enable once we get a fixed .NET 7 runtime.

TODO: We can't yet push to the `dotnet7` feed. Working on this.

Co-authored-by: Jonathan Peppers <[email protected]>
Co-authored-by: Peter Collins <[email protected]>
jonathanpeppers added a commit to jonathanpeppers/xamarin-android that referenced this issue May 6, 2022
Context: dotnet/runtime#68734
Context: xamarin/XamarinComponents#1368

We have a newer Xamarin.Build.Download package that shouldn't suffer
from the duplicate `ZipArchive.CreateEntry()` issue were are hitting
in .NET 7.

Let's use it, and enable previously failing tests.
dellis1972 pushed a commit to dotnet/android that referenced this issue May 6, 2022
Context: dotnet/runtime#68734
Context: xamarin/XamarinComponents#1368

We have a newer Xamarin.Build.Download package that shouldn't suffer
from the duplicate `ZipArchive.CreateEntry()` issue were are hitting
in .NET 7.

Let's use it, and enable previously failing tests.
@jonathanpeppers
Copy link
Member Author

We ended up deleting the problematic code and updated the NuGet package.

We are unblocked here, so now it just depends on how many customers will hit this, and if it is an acceptable breaking change. Completely up to you, thanks!

@jozkee
Copy link
Member

jozkee commented May 9, 2022

Closing for now, we can reopen if someone brings a convincing scenario (unable to workaround, probably).

@jozkee jozkee closed this as completed May 9, 2022
@akoeplinger
Copy link
Member

@jozkee would you mind providing a bit more detail about why this breaking change is acceptable? E.g. does it violate the ZIP specs etc. and how widespread we think it is.

Should we apply the breaking-change label to track getting this added to https://docs.microsoft.com/en-us/dotnet/core/compatibility/7.0 ?

@carlossanlop
Copy link
Member

@jozkee can you create the breaking change issue in dotnet/docs?https://github.com/dotnet/docs/issues/new?assignees=gewarren&labels=breaking-change%2CPri1%2Cdoc-idea&template=breaking-change.yml&title=%5BBreaking+change%5D%3A+

Here's the PR that introduced this breaking change: #60973

@carlossanlop carlossanlop reopened this May 18, 2022
@jozkee
Copy link
Member

jozkee commented May 18, 2022

@akoeplinger the problem was that .NET's zip impl. allows to create multiple entries with the same name, but if you attempt multiple operations on archives in such state, some operations won't work e.g: if you attempt to extract the archive with ZipFile.ExtractToDirectory("archive.zip", "archive"); it will throw The file 'duplicate-entry.txt' already exists. archive.GetEntry(duplicateEntryName); also is unable to retrieve duplicate entries, because it is backed by a Dictionary<string, ZipArchiveEntry>.

However duplicate entries are also OK in the spec. I have seen many libraries/tools disallowing this scenario, e.g. 7zip.

The only way to see duplicate entries is on the actual entry list, (archive.Entries), and that still works. The changed only broke creating them.

Again, I think we can undo the change if there's an scenario for duplicate entries. From a quick search, I've seen mixed opinions on both sides.

@stephentoub
Copy link
Member

However duplicate entries are also OK in the spec

If it's allowed by the spec, I don't think we should be enforcing our own policy to the contrary. If we allow it, a higher level tool could choose its own policy here using our APIs, but if we prohibit it, there's no wiggle room.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label May 18, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jul 11, 2022
@TonyValenti
Copy link

Glad to see this fixed!

The change prohibiting duplicate entries broke interesting code used in steganographic contexts.

@jozkee
Copy link
Member

jozkee commented Jul 12, 2022

Thanks to @jonathanpeppers for consuming our previews and reporting the issue.

@ghost ghost locked as resolved and limited conversation to collaborators Aug 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants