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

Remove BinaryFormatter from StateFileBase #6350

Merged
merged 8 commits into from May 24, 2021
Merged

Conversation

Forgind
Copy link
Member

@Forgind Forgind commented Apr 15, 2021

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.

src/Tasks/ResGenDependencies.cs Outdated Show resolved Hide resolved
src/Tasks/TaskTranslatorHelpers.cs Outdated Show resolved Hide resolved
@marcpopMSFT marcpopMSFT linked an issue Apr 15, 2021 that may be closed by this pull request
27 tasks
src/Tasks/TaskTranslatorHelpers.cs Outdated Show resolved Hide resolved
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)
Copy link
Contributor

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.

Copy link
Member Author

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/TaskTranslatorHelpers.cs Outdated Show resolved Hide resolved
dict = new Dictionary<string, DateTime>(comparer);
translator.Translate(ref count);
string key = string.Empty;
DateTime val = DateTime.Now;
Copy link
Member

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.

Suggested change
DateTime val = DateTime.Now;
DateTime val = DateTime.MinValue;

Copy link
Member Author

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.

@@ -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)
Copy link
Member

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.

Copy link
Member Author

@Forgind Forgind Apr 20, 2021

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.

src/Tasks/SystemState.cs Show resolved Hide resolved
/// <summary>
/// Cached delegate.
/// </summary>
private FileExists fileExists;
Copy link
Member

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?

Copy link
Member Author

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.

{
translator.TranslateDictionary(ref dependencies, (ITranslator translator, ref DependencyFile dependency) =>
{
if (t == typeof(ResGenDependencies.ResXFile))
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Contributor

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?

Copy link
Member

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

Copy link
Member Author

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 😉

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)
Copy link
Member

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.

Copy link
Member Author

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.

@@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors.
/// Translates a dictionary of { string, DateTime }.

{
int count = 0;
dictionary = new(comparer);
Translate(ref count);
Copy link
Member

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()
Copy link
Member

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?

Copy link
Member Author

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.

@Forgind Forgind mentioned this pull request Apr 30, 2021
@Forgind Forgind added the merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. label Apr 30, 2021
@Forgind Forgind merged commit 20d31f0 into dotnet:main May 24, 2021
@Forgind Forgind deleted the next-bf-removal branch May 24, 2021 21:50
Forgind added a commit to Forgind/msbuild that referenced this pull request May 26, 2021
@baronfel baronfel mentioned this pull request Jun 1, 2023
27 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BinaryFormatter Deprecation
4 participants