Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 452c40d

Browse files
authored
Fix resource stream for collectible assemblies (#22925)
* Fix resource stream for collectible assemblies The GetManifestResourceStream was returning a stream that didn't keep the underlying assembly alive. For collectible assemblies, that means that an assembly could be collected and the stream still kept, potentially reading garbage. The fix is to create a new stream type that stores a reference to the underlying assembly, thus ensuring the proper lifetime management. * Make the new stream class sealed
2 parents a26aabe + 48de3c0 commit 452c40d

File tree

6 files changed

+319
-1
lines changed

6 files changed

+319
-1
lines changed

src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ internal class RuntimeAssembly : Assembly
2929

3030
#endregion
3131

32+
private sealed class ManifestResourceStream : UnmanagedMemoryStream
33+
{
34+
private RuntimeAssembly _manifestAssembly;
35+
36+
internal unsafe ManifestResourceStream(RuntimeAssembly manifestAssembly, byte* pointer, long length, long capacity, FileAccess access) : base(pointer, length, capacity, access)
37+
{
38+
_manifestAssembly = manifestAssembly;
39+
}
40+
}
41+
3242
internal object SyncRoot
3343
{
3444
get
@@ -227,7 +237,7 @@ public unsafe override Stream GetManifestResourceStream(string name)
227237

228238
if (pbInMemoryResource != null)
229239
{
230-
return new UnmanagedMemoryStream(pbInMemoryResource, length, length, FileAccess.Read);
240+
return new ManifestResourceStream(this, pbInMemoryResource, length, length, FileAccess.Read);
231241
}
232242

233243
return null;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
using System;
5+
using System.IO;
6+
using System.Reflection;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.Loader;
9+
10+
class TestAssemblyLoadContext : AssemblyLoadContext
11+
{
12+
public TestAssemblyLoadContext() : base(isCollectible: true)
13+
{
14+
}
15+
16+
protected override Assembly Load(AssemblyName name)
17+
{
18+
return null;
19+
}
20+
}
21+
public class Test22888
22+
{
23+
[MethodImpl(MethodImplOptions.NoInlining)]
24+
static Stream LoadGetResourceStreamAndUnload(string assemblyPath, out WeakReference alcWeakRef)
25+
{
26+
var alc = new TestAssemblyLoadContext();
27+
alcWeakRef = new WeakReference(alc);
28+
29+
Assembly a = alc.LoadFromAssemblyPath(assemblyPath);
30+
Stream resourceStream = a.GetManifestResourceStream("test22888.resources");
31+
alc.Unload();
32+
33+
return resourceStream;
34+
}
35+
36+
[MethodImpl(MethodImplOptions.NoInlining)]
37+
static bool LoadAndUnload(string assemblyPath, out WeakReference alcWeakRef)
38+
{
39+
Stream s = LoadGetResourceStreamAndUnload(assemblyPath, out alcWeakRef);
40+
41+
bool success = (s != null);
42+
43+
if (success)
44+
{
45+
for (int i = 0; alcWeakRef.IsAlive && (i < 10); i++)
46+
{
47+
GC.Collect();
48+
GC.WaitForPendingFinalizers();
49+
}
50+
51+
// Verify that the ALC is still alive - it should be kept alive by the Stream.
52+
success = alcWeakRef.IsAlive;
53+
if (!success)
54+
{
55+
Console.WriteLine("Failed to keep AssemblyLoadContext alive by the resource stream");
56+
}
57+
GC.KeepAlive(s);
58+
}
59+
else
60+
{
61+
Console.WriteLine("Failed to get resource stream from the test assembly");
62+
}
63+
64+
return success;
65+
}
66+
67+
public static int Main()
68+
{
69+
string currentAssemblyDirectory = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath);
70+
string testAssemblyFullPath = Path.Combine(currentAssemblyDirectory, "test22888resources.exe");
71+
72+
WeakReference alcWeakRef;
73+
bool success = LoadAndUnload(testAssemblyFullPath, out alcWeakRef);
74+
75+
if (success)
76+
{
77+
for (int i = 0; alcWeakRef.IsAlive && (i < 10); i++)
78+
{
79+
GC.Collect();
80+
GC.WaitForPendingFinalizers();
81+
}
82+
83+
// Now the ALC should not be alive anymore as the resource stream is gone
84+
success = !alcWeakRef.IsAlive;
85+
if (!success)
86+
{
87+
Console.WriteLine("Failed to unload the test assembly");
88+
}
89+
}
90+
91+
return success ? 100 : 101;
92+
}
93+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<SchemaVersion>2.0</SchemaVersion>
8+
<ProjectGuid>{9D87696F-8C3A-49B8-86AD-A26D850B5A14}</ProjectGuid>
9+
<OutputType>Exe</OutputType>
10+
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11+
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
12+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
13+
<CLRTestKind>BuildAndRun</CLRTestKind>
14+
<CLRTestPriority>0</CLRTestPriority>
15+
</PropertyGroup>
16+
<!-- Default configurations to help VS understand the configurations -->
17+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
18+
</PropertyGroup>
19+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
20+
</PropertyGroup>
21+
<ItemGroup>
22+
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
23+
<Visible>False</Visible>
24+
</CodeAnalysisDependentAssemblyPaths>
25+
</ItemGroup>
26+
<ItemGroup>
27+
<!-- Add Compile Object Here -->
28+
<Compile Include="test22888.cs" />
29+
</ItemGroup>
30+
<ItemGroup>
31+
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
32+
</ItemGroup>
33+
<ItemGroup>
34+
<ProjectReference Include="../../../Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
35+
<ProjectReference Include="test22888resources.csproj" />
36+
</ItemGroup>
37+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
38+
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
39+
</PropertyGroup>
40+
</Project>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<!--
4+
Microsoft ResX Schema
5+
6+
Version 2.0
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
11+
associated with the data types.
12+
13+
Example:
14+
15+
... ado.net/XML headers & schema ...
16+
<resheader name="resmimetype">text/microsoft-resx</resheader>
17+
<resheader name="version">2.0</resheader>
18+
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19+
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20+
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21+
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22+
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23+
<value>[base64 mime encoded serialized .NET Framework object]</value>
24+
</data>
25+
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26+
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27+
<comment>This is a comment</comment>
28+
</data>
29+
30+
There are any number of "resheader" rows that contain simple
31+
name/value pairs.
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
37+
mimetype set.
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
41+
extensible. For a given mimetype the value must be set accordingly:
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
45+
read any of the formats listed below.
46+
47+
mimetype: application/x-microsoft.net.object.binary.base64
48+
value : The object must be serialized with
49+
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50+
: and then encoded with base64 encoding.
51+
52+
mimetype: application/x-microsoft.net.object.soap.base64
53+
value : The object must be serialized with
54+
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55+
: and then encoded with base64 encoding.
56+
57+
mimetype: application/x-microsoft.net.object.bytearray.base64
58+
value : The object must be serialized into a byte array
59+
: using a System.ComponentModel.TypeConverter
60+
: and then encoded with base64 encoding.
61+
-->
62+
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63+
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64+
<xsd:element name="root" msdata:IsDataSet="true">
65+
<xsd:complexType>
66+
<xsd:choice maxOccurs="unbounded">
67+
<xsd:element name="metadata">
68+
<xsd:complexType>
69+
<xsd:sequence>
70+
<xsd:element name="value" type="xsd:string" minOccurs="0" />
71+
</xsd:sequence>
72+
<xsd:attribute name="name" use="required" type="xsd:string" />
73+
<xsd:attribute name="type" type="xsd:string" />
74+
<xsd:attribute name="mimetype" type="xsd:string" />
75+
<xsd:attribute ref="xml:space" />
76+
</xsd:complexType>
77+
</xsd:element>
78+
<xsd:element name="assembly">
79+
<xsd:complexType>
80+
<xsd:attribute name="alias" type="xsd:string" />
81+
<xsd:attribute name="name" type="xsd:string" />
82+
</xsd:complexType>
83+
</xsd:element>
84+
<xsd:element name="data">
85+
<xsd:complexType>
86+
<xsd:sequence>
87+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88+
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89+
</xsd:sequence>
90+
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91+
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92+
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93+
<xsd:attribute ref="xml:space" />
94+
</xsd:complexType>
95+
</xsd:element>
96+
<xsd:element name="resheader">
97+
<xsd:complexType>
98+
<xsd:sequence>
99+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100+
</xsd:sequence>
101+
<xsd:attribute name="name" type="xsd:string" use="required" />
102+
</xsd:complexType>
103+
</xsd:element>
104+
</xsd:choice>
105+
</xsd:complexType>
106+
</xsd:element>
107+
</xsd:schema>
108+
<resheader name="resmimetype">
109+
<value>text/microsoft-resx</value>
110+
</resheader>
111+
<resheader name="version">
112+
<value>2.0</value>
113+
</resheader>
114+
<resheader name="reader">
115+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116+
</resheader>
117+
<resheader name="writer">
118+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119+
</resheader>
120+
</root>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
using System;
5+
6+
class Program
7+
{
8+
static void Main(string[] args)
9+
{
10+
}
11+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<SchemaVersion>2.0</SchemaVersion>
8+
<ProjectGuid>{558D34B5-6EC1-4F30-8A15-B94F7EC74DA9}</ProjectGuid>
9+
<OutputType>Exe</OutputType>
10+
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11+
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
12+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
13+
<CLRTestKind>BuildOnly</CLRTestKind>
14+
<CLRTestPriority>0</CLRTestPriority>
15+
</PropertyGroup>
16+
<!-- Default configurations to help VS understand the configurations -->
17+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
18+
</PropertyGroup>
19+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
20+
</PropertyGroup>
21+
<ItemGroup>
22+
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
23+
<Visible>False</Visible>
24+
</CodeAnalysisDependentAssemblyPaths>
25+
</ItemGroup>
26+
<ItemGroup>
27+
<!-- Add Compile Object Here -->
28+
<Compile Include="test22888resources.cs" />
29+
</ItemGroup>
30+
<ItemGroup>
31+
<EmbeddedResource Include="test22888.resx">
32+
<Generator>ResXFileCodeGenerator</Generator>
33+
</EmbeddedResource>
34+
</ItemGroup>
35+
<ItemGroup>
36+
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
37+
</ItemGroup>
38+
<ItemGroup>
39+
<ProjectReference Include="../../../Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
40+
</ItemGroup>
41+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
42+
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
43+
</PropertyGroup>
44+
</Project>

0 commit comments

Comments
 (0)