Skip to content

Commit 20d31f0

Browse files
authored
Remove BinaryFormatter from StateFileBase (dotnet#6350)
This ends our reliance on BinaryFormatter within any StateFileBase. This comprises AssemblyReferenceCache, ResolveComReference, and ResGenDependencies. SystemState (part of ResolveAssemblyReference) also extends StateFileBase, but that was already converted. We may want to combine those two serialization efforts into a single location, but that's more of a refactor so lower priority. Also added tests.
1 parent 239b078 commit 20d31f0

15 files changed

Lines changed: 354 additions & 398 deletions

src/Shared/BinaryTranslator.cs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -660,12 +660,31 @@ public void TranslateDictionary<D, T>(ref D dictionary, ObjectTranslator<T> obje
660660
}
661661
}
662662

663-
/// <summary>
664-
/// Reads in the boolean which says if this object is null or not.
665-
/// </summary>
666-
/// <typeparam name="T">The type of object to test.</typeparam>
667-
/// <returns>True if the object should be read, false otherwise.</returns>
668-
public bool TranslateNullable<T>(T value)
663+
public void TranslateDictionary(ref Dictionary<string, DateTime> dictionary, StringComparer comparer)
664+
{
665+
if (!TranslateNullable(dictionary))
666+
{
667+
return;
668+
}
669+
670+
int count = _reader.ReadInt32();
671+
dictionary = new(count, comparer);
672+
string key = string.Empty;
673+
DateTime val = DateTime.MinValue;
674+
for (int i = 0; i < count; i++)
675+
{
676+
Translate(ref key);
677+
Translate(ref val);
678+
dictionary.Add(key, val);
679+
}
680+
}
681+
682+
/// <summary>
683+
/// Reads in the boolean which says if this object is null or not.
684+
/// </summary>
685+
/// <typeparam name="T">The type of object to test.</typeparam>
686+
/// <returns>True if the object should be read, false otherwise.</returns>
687+
public bool TranslateNullable<T>(T value)
669688
{
670689
bool haveRef = _reader.ReadBoolean();
671690
return haveRef;
@@ -1254,6 +1273,29 @@ public void TranslateDictionary<D, T>(ref D dictionary, ObjectTranslator<T> obje
12541273
}
12551274
}
12561275

1276+
/// <summary>
1277+
/// Translates a dictionary of { string, DateTime }.
1278+
/// </summary>
1279+
/// <param name="dictionary">The dictionary to be translated.</param>
1280+
/// <param name="comparer">Key comparer</param>
1281+
public void TranslateDictionary(ref Dictionary<string, DateTime> dictionary, StringComparer comparer)
1282+
{
1283+
if (!TranslateNullable(dictionary))
1284+
{
1285+
return;
1286+
}
1287+
1288+
int count = dictionary.Count;
1289+
_writer.Write(count);
1290+
foreach (KeyValuePair<string, DateTime> kvp in dictionary)
1291+
{
1292+
string key = kvp.Key;
1293+
DateTime val = kvp.Value;
1294+
Translate(ref key);
1295+
Translate(ref val);
1296+
}
1297+
}
1298+
12571299
/// <summary>
12581300
/// Writes out the boolean which says if this object is null or not.
12591301
/// </summary>

src/Shared/ITranslator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ void TranslateArray<T>(ref T[] array)
301301

302302
void TranslateDictionary(ref IDictionary<string, string> dictionary, NodePacketCollectionCreator<IDictionary<string, string>> collectionCreator);
303303

304+
void TranslateDictionary(ref Dictionary<string, DateTime> dictionary, StringComparer comparer);
305+
304306
void TranslateDictionary<K, V>(ref IDictionary<K, V> dictionary, ObjectTranslator<K> keyTranslator, ObjectTranslator<V> valueTranslator, NodePacketCollectionCreator<IDictionary<K, V>> dictionaryCreator);
305307

306308
/// <summary>
Lines changed: 46 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
14
using System;
5+
using System.Collections.Generic;
26
using System.IO;
3-
using System.Reflection;
47
using System.Runtime.Versioning;
58
using Microsoft.Build.Shared;
69
using Microsoft.Build.Tasks;
@@ -12,10 +15,6 @@ namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests
1215
{
1316
public class ResolveAssemblyReferenceCacheSerialization : IDisposable
1417
{
15-
// Maintain this two in sync with the constant in SystemState
16-
private static readonly byte[] TranslateContractSignature = { (byte)'M', (byte)'B', (byte)'R', (byte)'S', (byte)'C' }; // Microsoft Build RAR State Cache
17-
private static readonly byte TranslateContractVersion = 0x01;
18-
1918
private readonly string _rarCacheFile;
2019
private readonly TaskLoggingHelper _taskLoggingHelper;
2120

@@ -42,178 +41,81 @@ public void RoundTripEmptyState()
4241
{
4342
SystemState systemState = new();
4443

45-
systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
44+
systemState.SerializeCache(_rarCacheFile, _taskLoggingHelper);
4645

47-
var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
46+
var deserialized = SystemState.DeserializeCache(_rarCacheFile, _taskLoggingHelper, typeof(SystemState));
4847

4948
deserialized.ShouldNotBeNull();
5049
}
5150

5251
[Fact]
53-
public void WrongFileSignature()
54-
{
55-
SystemState systemState = new();
56-
57-
for (int i = 0; i < TranslateContractSignature.Length; i++)
58-
{
59-
systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
60-
using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite))
61-
{
62-
cacheStream.Seek(i, SeekOrigin.Begin);
63-
cacheStream.WriteByte(0);
64-
cacheStream.Close();
65-
}
66-
67-
var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
68-
69-
deserialized.ShouldBeNull();
70-
}
71-
}
72-
73-
[Fact]
74-
public void WrongFileVersion()
52+
public void CorrectFileVersion()
7553
{
7654
SystemState systemState = new();
7755

78-
systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
56+
systemState.SerializeCache(_rarCacheFile, _taskLoggingHelper);
7957
using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite))
8058
{
81-
cacheStream.Seek(TranslateContractSignature.Length, SeekOrigin.Begin);
82-
cacheStream.WriteByte((byte) (TranslateContractVersion + 1));
59+
cacheStream.Seek(0, SeekOrigin.Begin);
60+
cacheStream.WriteByte(StateFileBase.CurrentSerializationVersion);
8361
cacheStream.Close();
8462
}
8563

86-
var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
87-
88-
deserialized.ShouldBeNull();
89-
}
90-
91-
[Fact]
92-
public void CorrectFileSignature()
93-
{
94-
SystemState systemState = new();
95-
96-
for (int i = 0; i < TranslateContractSignature.Length; i++)
97-
{
98-
systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
99-
using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite))
100-
{
101-
cacheStream.Seek(i, SeekOrigin.Begin);
102-
cacheStream.WriteByte(TranslateContractSignature[i]);
103-
cacheStream.Close();
104-
}
105-
106-
var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
64+
var deserialized = SystemState.DeserializeCache(_rarCacheFile, _taskLoggingHelper, typeof(SystemState));
10765

108-
deserialized.ShouldNotBeNull();
109-
}
66+
deserialized.ShouldNotBeNull();
11067
}
11168

11269
[Fact]
113-
public void CorrectFileVersion()
70+
public void WrongFileVersion()
11471
{
11572
SystemState systemState = new();
11673

117-
systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
74+
systemState.SerializeCache(_rarCacheFile, _taskLoggingHelper);
11875
using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite))
11976
{
120-
cacheStream.Seek(TranslateContractSignature.Length, SeekOrigin.Begin);
121-
cacheStream.WriteByte(TranslateContractVersion);
77+
cacheStream.Seek(0, SeekOrigin.Begin);
78+
cacheStream.WriteByte(StateFileBase.CurrentSerializationVersion - 1);
12279
cacheStream.Close();
12380
}
12481

125-
var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
82+
var deserialized = SystemState.DeserializeCache(_rarCacheFile, _taskLoggingHelper, typeof(SystemState));
12683

127-
deserialized.ShouldNotBeNull();
84+
deserialized.ShouldBeNull();
12885
}
12986

13087
[Fact]
131-
public void VerifySampleStateDeserialization()
88+
public void ValidateSerializationAndDeserialization()
13289
{
133-
// This test might also fail when binary format is modified.
134-
// Any change in SystemState and child class ITranslatable implementation will most probably make this fail.
135-
// To fix it, file referred by 'sampleName' needs to be recaptured and constant bellow modified to reflect
136-
// the content of that cache.
137-
// This sample was captured by compiling https://github.com/dotnet/roslyn/commit/f8107de2a94a01e96ac3d7c1f225acbb61e18830
138-
const string sampleName = "Microsoft.VisualStudio.LanguageServices.Implementation.csprojAssemblyReference.cache";
139-
const string expectedAssemblyPath = @"C:\Users\rokon\.nuget\packages\microsoft.visualstudio.codeanalysis.sdk.ui\15.8.27812-alpha\lib\net46\Microsoft.VisualStudio.CodeAnalysis.Sdk.UI.dll";
140-
const long expectedAssemblyLastWriteTimeTicks = 636644382480000000;
141-
const string expectedAssemblyName = "Microsoft.VisualStudio.CodeAnalysis.Sdk.UI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
142-
const string expectedFrameworkName = ".NETFramework,Version=v4.5";
143-
var expectedDependencies = new[]
90+
Dictionary<string, SystemState.FileState> cache = new() {
91+
{ "path1", new SystemState.FileState(DateTime.Now) },
92+
{ "path2", new SystemState.FileState(DateTime.Now) { Assembly = new AssemblyNameExtension("hi") } },
93+
{ "dllName", new SystemState.FileState(DateTime.Now.AddSeconds(-10)) {
94+
Assembly = null,
95+
RuntimeVersion = "v4.0.30319",
96+
FrameworkNameAttribute = new FrameworkName(".NETFramework", Version.Parse("4.7.2"), "Profile"),
97+
scatterFiles = new string[] { "first", "second" } } } };
98+
SystemState sysState = new();
99+
sysState.instanceLocalFileStateCache = cache;
100+
SystemState sysState2 = null;
101+
using (TestEnvironment env = TestEnvironment.Create())
144102
{
145-
"mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
146-
"System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
147-
"System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
148-
"Microsoft.VisualStudio.CodeAnalysis, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
149-
"Microsoft.VisualStudio.DeveloperTools, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
150-
"System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
151-
"Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
152-
"EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
153-
"Microsoft.VisualStudio.CodeAnalysis.Sdk, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
154-
"Microsoft.Build.Framework, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
155-
"Microsoft.VisualStudio.Text.Logic, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
156-
"Microsoft.VisualStudio.Text.UI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
157-
"Microsoft.VisualStudio.Text.Data, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
158-
"Microsoft.VisualStudio.Text.UI.Wpf, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
159-
"Microsoft.VisualStudio.ComponentModelHost, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
160-
"Microsoft.VisualStudio.VSHelp, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
161-
"Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
162-
"Microsoft.VisualStudio.VCProjectEngine, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
163-
"Microsoft.VisualStudio.Shell.15.0, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
164-
"Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
165-
"System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
166-
"Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
167-
"EnvDTE80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
168-
"System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
169-
"Microsoft.VisualStudio.VirtualTreeGrid, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
170-
"Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
171-
"Microsoft.VisualStudio.Editor, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
172-
};
173-
174-
175-
CopyResourceSampleFileIntoRarCacheFile($@"AssemblyDependency\CacheFileSamples\{sampleName}");
176-
177-
var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper);
178-
deserializedByTranslator.ShouldNotBeNull();
179-
180-
deserializedByTranslator.SetGetLastWriteTime(path =>
181-
{
182-
if (path != expectedAssemblyPath)
183-
throw new InvalidOperationException("Unexpected file name for this test case");
184-
185-
return new DateTime(expectedAssemblyLastWriteTimeTicks, DateTimeKind.Utc);
186-
});
187-
188-
GetAssemblyName getAssemblyName = deserializedByTranslator.CacheDelegate((GetAssemblyName)null);
189-
GetAssemblyMetadata getAssemblyMetadata = deserializedByTranslator.CacheDelegate((GetAssemblyMetadata)null);
190-
191-
var assemblyName = getAssemblyName(expectedAssemblyPath);
192-
getAssemblyMetadata(expectedAssemblyPath, null,
193-
out AssemblyNameExtension[] dependencies,
194-
out string[] scatterFiles,
195-
out FrameworkName frameworkNameAttribute);
196-
197-
198-
assemblyName.ShouldNotBeNull();
199-
assemblyName.ShouldBe(new AssemblyNameExtension(expectedAssemblyName, false));
200-
scatterFiles.ShouldBeEmpty();
201-
frameworkNameAttribute.ShouldBe(new FrameworkName(expectedFrameworkName));
202-
dependencies.ShouldNotBeNull();
203-
expectedDependencies.ShouldBe(expectedDependencies, ignoreOrder: true);
204-
}
205-
206-
private void CopyResourceSampleFileIntoRarCacheFile(string name)
207-
{
208-
Assembly asm = this.GetType().Assembly;
209-
var resource = string.Format($"{asm.GetName().Name}.{name.Replace("\\", ".")}");
210-
using Stream resourceStream = asm.GetManifestResourceStream(resource);
211-
if (resourceStream == null)
212-
throw new InvalidOperationException($"Resource '{resource}' has not been found.");
213-
214-
using FileStream rarCacheFile = new FileStream(_rarCacheFile, FileMode.CreateNew);
103+
TransientTestFile file = env.CreateFile();
104+
sysState.SerializeCache(file.Path, null);
105+
sysState2 = SystemState.DeserializeCache(file.Path, null, typeof(SystemState)) as SystemState;
106+
}
215107

216-
resourceStream.CopyTo(rarCacheFile);
108+
Dictionary<string, SystemState.FileState> cache2 = sysState2.instanceLocalFileStateCache;
109+
cache2.Count.ShouldBe(cache.Count);
110+
cache2["path2"].Assembly.Name.ShouldBe(cache["path2"].Assembly.Name);
111+
SystemState.FileState dll = cache["dllName"];
112+
SystemState.FileState dll2 = cache2["dllName"];
113+
dll2.Assembly.ShouldBe(dll.Assembly);
114+
dll2.FrameworkNameAttribute.FullName.ShouldBe(dll.FrameworkNameAttribute.FullName);
115+
dll2.LastModified.ShouldBe(dll.LastModified);
116+
dll2.RuntimeVersion.ShouldBe(dll.RuntimeVersion);
117+
dll2.scatterFiles.Length.ShouldBe(dll.scatterFiles.Length);
118+
dll2.scatterFiles[1].ShouldBe(dll.scatterFiles[1]);
217119
}
218120
}
219121
}

src/Tasks.UnitTests/AssemblyRegistrationCache_Tests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using Microsoft.Build.Tasks;
5+
using Shouldly;
56
using Xunit;
67

78
namespace Microsoft.Build.UnitTests
@@ -26,5 +27,24 @@ public void ExerciseCache()
2627
Assert.Equal("foo", assembly);
2728
Assert.Equal("bar", tlb);
2829
}
30+
31+
[Fact]
32+
public void ExerciseCacheSerialization()
33+
{
34+
AssemblyRegistrationCache arc = new();
35+
arc.AddEntry("foo", "bar");
36+
AssemblyRegistrationCache arc2 = null;
37+
using (TestEnvironment env = TestEnvironment.Create())
38+
{
39+
TransientTestFile file = env.CreateFile();
40+
arc.SerializeCache(file.Path, null);
41+
arc2 = StateFileBase.DeserializeCache(file.Path, null, typeof(AssemblyRegistrationCache)) as AssemblyRegistrationCache;
42+
}
43+
44+
arc2._assemblies.Count.ShouldBe(arc._assemblies.Count);
45+
arc2._assemblies[0].ShouldBe(arc._assemblies[0]);
46+
arc2._typeLibraries.Count.ShouldBe(arc._typeLibraries.Count);
47+
arc2._typeLibraries[0].ShouldBe(arc._typeLibraries[0]);
48+
}
2949
}
3050
}

src/Tasks.UnitTests/ResolveComReference_Tests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
using Microsoft.Build.Tasks;
1515
using Xunit;
1616
using Microsoft.Build.Shared;
17+
using System.IO;
18+
using Microsoft.Build.BackEnd;
19+
using Shouldly;
1720

1821
namespace Microsoft.Build.UnitTests
1922
{
@@ -57,6 +60,29 @@ public void GetResolvedASsemblyReferenceSpecNotNull()
5760
Assert.NotNull(task.GetResolvedAssemblyReferenceItemSpecs());
5861
}
5962

63+
[Fact]
64+
public void TestSerializationAndDeserialization()
65+
{
66+
ResolveComReferenceCache cache = new("path1", "path2");
67+
cache.componentTimestamps = new()
68+
{
69+
{ "first", DateTime.Now },
70+
{ "second", DateTime.FromBinary(10000) },
71+
};
72+
ResolveComReferenceCache cache2 = null;
73+
using (TestEnvironment env = TestEnvironment.Create())
74+
{
75+
TransientTestFile file = env.CreateFile();
76+
cache.SerializeCache(file.Path, null);
77+
cache2 = StateFileBase.DeserializeCache(file.Path, null, typeof(ResolveComReferenceCache)) as ResolveComReferenceCache;
78+
}
79+
80+
cache2.tlbImpLocation.ShouldBe(cache.tlbImpLocation);
81+
cache2.axImpLocation.ShouldBe(cache.axImpLocation);
82+
cache2.componentTimestamps.Count.ShouldBe(cache.componentTimestamps.Count);
83+
cache2.componentTimestamps["second"].ShouldBe(cache.componentTimestamps["second"]);
84+
}
85+
6086
/*
6187
* Method: CheckComReferenceAttributeVerificationForNameItems
6288
*

0 commit comments

Comments
 (0)