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.
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.
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.
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.
Nope, just needed a value.
src/Tasks/TaskTranslatorHelpers.cs
Outdated
| } | ||
| } | ||
|
|
||
| public static void TranslateDictionary(this ITranslator translator, ref Dictionary<string, DateTime> dict, StringComparer comparer) |
There was a problem hiding this comment.
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.
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.
What's the motivation for this change?
There was a problem hiding this comment.
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.
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.
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.
Can we make Dependencies implement ITranslatable and override it in derived classes?
There was a problem hiding this comment.
If it was the first instinct for both of us it deserves a comment please
There was a problem hiding this comment.
@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
| // 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.
I don't feel like this is clearer than switching the cast to an as.
There was a problem hiding this comment.
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
| } | ||
|
|
||
| /// <summary> | ||
| /// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors. |
There was a problem hiding this comment.
| /// 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.
Can you use _reader.ReadInt32() here like other implementations?
| } | ||
|
|
||
| [Fact] | ||
| public void WrongFileVersion() |
There was a problem hiding this comment.
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.
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.