Fix BinaryFormatter to properly deserialize objects with different ve… - #20552
Conversation
…rsion when AssemblyFormat is set to Simple
| { | ||
| Assembly assm = null; | ||
| try | ||
| if (_isSimpleAssembly) |
There was a problem hiding this comment.
@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?
There was a problem hiding this comment.
What prevents us from just using the desktop code as-is? Are we still missing some Assembly-related methods it depends on?
There was a problem hiding this comment.
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.
| } | ||
| catch { } | ||
|
|
||
| if (assm == null) |
There was a problem hiding this comment.
Per desktop this should also check assemblyName != null
There was a problem hiding this comment.
is already checking here: https://github.com/dotnet/corefx/pull/20552/files#diff-b4b2fd9840862f03eae907efcd988a68R1011
| { | ||
| assm = Assembly.Load(new AssemblyName(assemblyName)); | ||
| } | ||
| catch { } |
There was a problem hiding this comment.
According to Desktop, the GetSimplyNamedTypeFromAssembly and FormatterServices.GetTypeFromAssembly calls should be inside the if/else here, not after the if/else.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
This is just an inlined copy of ResolveSimpleAssemblyName(). I would replace this with a call to that.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | ||
| { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
At least, the "m_" should be changed to "_".
| catch (FileLoadException) { } | ||
| catch (BadImageFormatException) { } | ||
|
|
||
| if((object)type == null) |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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
| if (assembly == null) | ||
| assembly = m_topLevelAssembly; | ||
|
|
||
| return assembly.GetType(simpleTypeName, false, ignoreCase); |
There was a problem hiding this comment.
I prefer "throwOnError: false, ignoreCase: ignoreCase".
|
Other than these notes, lgtm. |
| catch (BadImageFormatException) { } | ||
|
|
||
| if((object)type == null) | ||
| if(type == null) |
|
PTAL |
|
@atsushikan Ah I see you just approved the changes. Thanks! |
|
@dotnet-bot test Innerloop netfx Windows_NT Release x64 Build and Test
https://ci.dot.net/job/dotnet_corefx/job/master/job/netfx_windows_nt_release_prtest/965/ |
Fix BinaryFormatter to properly deserialize objects with different version when AssemblyFormat is set to Simple