RecordConverter: Do not throw ClassNotFoundException when "class" attribute is absent #351
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR fixes a crash in RecordConverter during deserialization.
For some reason the RecordConverter implementation assumes that either the "class" attribute is present or the XML tag name is the record classname. This is not true in at least one case (see unit test added by this PR) and causes a ClassNotFoundException when RecordConverter tries to resolve the member field name as a class.
I'm not familiar with the XStream design but after stepping through the code it seems that the "class" attribute is not being omitted by bug/accident but intentionally (most likely to save memory/disk space) when the actual type of a value stored in a field matches the field's type... so to me it looks like the crash is caused by a bug in RecordConverter and not outside code.
I've fixed the issue by using UnmarshallingContext#getRequiredType() instead of trying to use the "class" attribute and falling back to HierarchicalStreamReader#getNodeName() ... not sure whether this is the right approach though or why the original RecordConverter contribution did not use that method in the first place.
Because I was unsure about using UnmarshallingContext#getRequiredType() I've added a couple additional test cases (Object field type, Object field containing an object array ) to my unit test but it still passes so at least my fix is not trivially wrong.
For the sake of completeness, this is the exception being thrown when running my test case without my RecordConverter change:
com.thoughtworks.xstream.converters.ConversionException:
Class not found.
---- Debugging information ----
message : Class not found.
cause-exception : java.lang.ClassNotFoundException
cause-message : field1
class : com.thoughtworks.xstream.converters.extended.RecordConverterTest$SerializableRecord
required-type : com.thoughtworks.xstream.converters.extended.RecordConverterTest$SerializableRecord
converter-type : com.thoughtworks.xstream.converters.extended.RecordConverter
path : /com.thoughtworks.xstream.converters.extended.RecordConverterTest$MyObj/field1
line number : 1
class[1] : com.thoughtworks.xstream.converters.extended.RecordConverterTest$MyObj
required-type[1] : com.thoughtworks.xstream.converters.extended.RecordConverterTest$MyObj
converter-type[1] : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
version : not available
Caused by: java.lang.ClassNotFoundException: field1
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:375)
at com.thoughtworks.xstream.converters.extended.RecordConverter.classForName(RecordConverter.java:222)
... 40 more