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

Fix BinaryFormatter to properly deserialize objects with different ve… - #20552

Merged
danmoseley merged 3 commits into
dotnet:masterfrom
krwq:BinaryFormatterFix
Jun 1, 2017
Merged

Fix BinaryFormatter to properly deserialize objects with different ve…#20552
danmoseley merged 3 commits into
dotnet:masterfrom
krwq:BinaryFormatterFix

Conversation

@krwq

@krwq krwq commented Jun 1, 2017

Copy link
Copy Markdown
Member

Fix BinaryFormatter to properly deserialize objects with different version when AssemblyFormat is set to Simple

{
Assembly assm = null;
try
if (_isSimpleAssembly)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@stephentoub , we tried to make this logic match the semantics of https://referencesource.microsoft.com/#mscorlib/system/runtime/serialization/formatters/binary/binaryobjectreader.cs,84b78d568158e011 , but we didn't know the code well. Does this look right to you?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What prevents us from just using the desktop code as-is? Are we still missing some Assembly-related methods it depends on?

@danmoseley danmoseley Jun 1, 2017

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Desktop calls LoadWithPartialNameInternal which does not exist in Core. After that point Core diverges.

LoadWithPartialNameInternal calls nLoad(string...). This change does Assembly.Load(string) instead. In Desktop, Assembly.Load(string) eventually calls to nLoad with what seems to essentially be just the assembly name: so hopefully that is an equivalent substitution.

@danmoseley
danmoseley requested a review from a user June 1, 2017 15:54
}
catch { }

if (assm == null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Per desktop this should also check assemblyName != null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

{
assm = Assembly.Load(new AssemblyName(assemblyName));
}
catch { }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

According to Desktop, the GetSimplyNamedTypeFromAssembly and FormatterServices.GetTypeFromAssembly calls should be inside the if/else here, not after the if/else.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it's the same behavior. First the assembly gets loaded, then it checks if is correctly loaded (!= null) and then it executes either GetSimplyNamedTypeFromAssembly or FormatterServices.GetTypeFromAssembly. This is just code cleanup.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm missing something. In Desktop, if bSimpleAssembly==true, and the load succeeds, it calls ObjectReader.GetSimplyNamedTypeFromAssembly. if bSimpleAssembly==false and the load succeeds, it calls FormatterServices.GetTypeFromAssembly. If either load fails it calls neither.

In this code, whatever the value of bSimplAssembly, if either load succeeds it calls GetSimplyNamedTypeFromAssembly otherwise it calls FormatterServices.GetTypeFromAssembly.

What am I missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In this code, whatever the value of bSimplAssembly, if either load succeeds it calls GetSimplyNamedTypeFromAssembly otherwise it calls FormatterServices.GetTypeFromAssembly.

No. If load succeeds and _isSimpleAssembly == true then it calls GetSimplyNamedTypeFromAssembly. If load succeeds and _isSimpleAssembly == false it calls FormatterServices.GetTypeFromAssembly. If load fails it calls nothing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

aagh -- the relevant if was collapsed in the diff, and the code looked completely valid collapsed.. thx!

if (_isSimpleAssembly)
{
assm = Assembly.Load(new AssemblyName(assemblyName));
try

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is just an inlined copy of ResolveSimpleAssemblyName(). I would replace this with a call to that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right - we tried to take minimum changes first but then realized that wasn't fully fixing a problem and had to in the end add the changes we wanted out of this PR - now we have all the pieces, I'll update this to match desktop

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@krwq I'm already addressing the PR feedback

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@atsushikan it's not the same. ResolveSimpleAssemblyName first tries to load the assembly with an AssemblyName type and if that fails it tries to load it with the name of the assembly as a string.

At this point we are first trying to resolve it by assemblyname (string) and if that fails by AssemblyName type. So vice versa.

@ViktorHofer ViktorHofer Jun 1, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At this point we are first trying to resolve it by assemblyname (string) and if that fails by AssemblyName type. So vice versa.

That wasn't 100% correct. First it tries to load it with the given assemblyname (string) and if that fails it creates an AssemblyName type and tries to load it with the name property of it. I don't think this is the same as ResolveSimpleAssemblyName.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No, it's the same logic - the only difference is that the first call is to Assembly.Load(String) rather than Load(AssemblyName). You could use the common function just by creating an AssemblyName out of the string "assemblyName" first (that's just what Assembly.Load(string) does internally so you're not adding overhead by doing this.)

@ViktorHofer ViktorHofer Jun 1, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ok then I'm fine changing it. Is it safe to create the AssemblyName outside of a try catch or should I also wrap one around it? I checked the sources and can't say for sure as it is calling an external method.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If the AssemblyName constructor throws, it's because the assembly name itself is badly formed. That's an exception that really shouldn't be swallowed, in my opinion.

I realize, though, that may be a "breaking change" so I won't complain too hard if you put it inside the try.

(On the similar note, catching every exception indiscriminately out of Assembly.Load() is also bound to be controversial - one more reason why I'd like to have just copy of that logic.)

}

internal sealed class TopLevelAssemblyTypeResolver
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I assume this came from the desktop given its retro style - I think a closure at the Type.GetType() callsite would be clearer these days.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes it's a desktop copy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

At least, the "m_" should be changed to "_".

catch (FileLoadException) { }
catch (BadImageFormatException) { }

if((object)type == null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Casting type to object before comparing to null isn't necessary. The == operator for MemberInfo ensures that null checks occur without invoking any members on potentially 3-rd party types (assuming such a thing could even make it to this point.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks, we saw Desktop code had this and though this might be changing logic around overriden equals - if this is equivalent let's remove it. cc: @morganbr

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fine by me.

if (assembly == null)
assembly = m_topLevelAssembly;

return assembly.GetType(simpleTypeName, false, ignoreCase);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I prefer "throwOnError: false, ignoreCase: ignoreCase".

@ghost

ghost commented Jun 1, 2017

Copy link
Copy Markdown

Other than these notes, lgtm.

catch (BadImageFormatException) { }

if((object)type == null)
if(type == null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit:spacing after if

@ViktorHofer

Copy link
Copy Markdown
Member

PTAL

@ViktorHofer

Copy link
Copy Markdown
Member

@atsushikan Ah I see you just approved the changes. Thanks!

@ViktorHofer

ViktorHofer commented Jun 1, 2017

Copy link
Copy Markdown
Member

@dotnet-bot test Innerloop netfx Windows_NT Release x64 Build and Test

System.Transactions.Tests.CloneTxTests.Run(cloneType: RollbackDependent, isoLevel: Chaos, forcePromote: False, completeClone: False, expectedStatus: Aborted) [FAIL]

https://ci.dot.net/job/dotnet_corefx/job/master/job/netfx_windows_nt_release_prtest/965/

@danmoseley
danmoseley merged commit 19e4b90 into dotnet:master Jun 1, 2017
@karelz karelz modified the milestone: 2.1.0 Jun 2, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants