-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Remove BinaryFormatter from StateFileBase #6350
Conversation
src/Tasks/StateFileBase.cs
Outdated
if (retVal == null && deserializedObject != null) | ||
// If retVal is still null or the version is wrong, log a message not a warning. This could be a valid cache with the wrong version preventing correct deserialization. | ||
// For the latter case, internals may be unexpectedly null. | ||
if (retVal == null || retVal._serializedVersion != CurrentSerializationVersion) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since _serializedVersion is never serialized, it will always have same value as CurrentSerializationVersion and it will never detect outdated cache.
In SystemState.DeserializeCacheByTranslator I have implemented some kind of file signature check along with basic versioning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, added.
src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs
Show resolved
Hide resolved
src/Tasks/TaskTranslatorHelpers.cs
Outdated
dict = new Dictionary<string, DateTime>(comparer); | ||
translator.Translate(ref count); | ||
string key = string.Empty; | ||
DateTime val = DateTime.Now; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be harmful to default this to min or max? Those are constants and would never require a clock read.
DateTime val = DateTime.Now; | |
DateTime val = DateTime.MinValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, just needed a value.
src/Tasks/TaskTranslatorHelpers.cs
Outdated
@@ -34,5 +35,35 @@ public static void Translate(this ITranslator translator, ref FrameworkName fram | |||
frameworkName = new FrameworkName(identifier, version, profile); | |||
} | |||
} | |||
|
|||
public static void TranslateDictionary(this ITranslator translator, ref Dictionary<string, DateTime> dict, StringComparer comparer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this overload not in BinaryTranslator.cs
with the rest? It doesn't use a Tasks-specific type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To do this, I would have to add a method signature in ITranslator.cs and a separate implementation for read and write. Those implementations would be almost identical and easily merged. Looking at other examples in that class, most just reimplemented the same logic twice with a tiny tweak. I don't approve of wasting code like that and would propose moving all methods beyond the basics (int, string, etc.) to an extensions class where they can reuse code properly. I'm fine with moving this to Shared, though.
/// <summary> | ||
/// Cached delegate. | ||
/// </summary> | ||
private FileExists fileExists; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the motivation for this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It isn't used, and I came across it. Small cleanup.
src/Tasks/Dependencies.cs
Outdated
{ | ||
translator.TranslateDictionary(ref dependencies, (ITranslator translator, ref DependencyFile dependency) => | ||
{ | ||
if (t == typeof(ResGenDependencies.ResXFile)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why pass and check the type explicitly? Could you instead do a pattern match on the runtime type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great question—that's actually how I first implemented it, and it took me a bit to figure out why it was failing. For serializing to disk, yes, that works. For deserializing, dependency comes in as null, so it can be cast to either a ResXFile or a PortableLibraryFile. Only one of those is valid, however, so I need to pick the right one. Explicitly passing the type (or some equivalent information) is the only way I can get the requisite information for deserializing the right type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make Dependencies implement ITranslatable and override it in derived classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it was the first instinct for both of us it deserves a comment please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rokonec The type is just Dictionary<string, DependencyFile> and it has to work for either Dictionary<string, ResXFile> or Dictionary<string, PortableLibraryFile>. The Dependencies class doesn't know which will be required, so the Translator can't either. To be honest, this whole class structure could have been dramatically simplified by just having ResGenDependencies have a Dictionary<string, ResXFile> and a Dictionary<string, PortableLibraryFile>. That would make this a lot cleaner but be a little outside the scope of this PR. That said, if you want me to do that, I'm happy deleting a lot of code 😉
src/Tasks/ResGenDependencies.cs
Outdated
internal ResXFile GetResXFileInfo(string resxFile, bool useMSBuildResXReader) | ||
{ | ||
// First, try to retrieve the resx information from our hashtable. | ||
var retVal = (ResXFile)resXFiles.GetDependencyFile(resxFile); | ||
if (retVal == null) | ||
if (resXFiles.GetDependencyFile(resxFile) is not ResXFile retVal || retVal == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel like this is clearer than switching the cast to an as
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's probably better, thanks. This wasn't about clarity but correctness—the cast was failing and throwing an exception because it wasn't a ResXFile. That shouldn't happen in practice, and it's now fixed (see previous comment), but did cause problems for a bit.
src/Shared/BinaryTranslator.cs
Outdated
@@ -1254,6 +1269,24 @@ public void TranslateDictionary<T>(ref Dictionary<string, T> dictionary, IEquali | |||
} | |||
} | |||
|
|||
/// <summary> | |||
/// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors. | |
/// Translates a dictionary of { string, DateTime }. |
src/Shared/BinaryTranslator.cs
Outdated
{ | ||
int count = 0; | ||
dictionary = new(comparer); | ||
Translate(ref count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use _reader.ReadInt32()
here like other implementations?
} | ||
|
||
[Fact] | ||
public void WrongFileVersion() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is there now no equivalent to this test? What about the "cache was written with v.last and is read with v.current" scenario?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can reinstate it. To be honest, in retrospect, I'm not sure why I deleted it in the first place.
This reverts commit 20d31f0.
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.