[CSHARP-3232] Don't create uninitialized objects during deserialization Created: 23/Oct/20  Updated: 20/Jan/23

Status: Backlog
Project: C# Driver
Component/s: Serialization
Affects Version/s: 2.11.3
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: Robert Stam Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Epic Link: Improve Serialization

 Description   

The current implementation of BsonClassMap (used by BsonClassMapSerializer) in certain scenarios can fall back to creating uninitialized objects using FormatterServices.GetUninitializedObject:

https://github.com/mongodb/mongo-csharp-driver/blob/v2.11.3/src/MongoDB.Bson/Serialization/BsonClassMap.cs#L1294

Since this bypasses calling the constructors it means any code in the constructors will not be executed, which means there is a high risk that the instantiated object is not properly initialized.

During deserialization we already try to call the constructor that takes the most arguments . If no matching constructor taking arguments is found we then call the no-argument constructor (if it exists).

There doesn't seem to really be a use case for using GetUninitializedObject. The risk of creating improperly initialized objects is just too great.

Even in the probably extremely rare case where a user has a class that can only be deserialized using GetUninitializedObject, the workaround if we stop using GetUninitializedObject is as simple as adding a no-argument constructor. If a no-argument constructor already exists we wouldn't be calling GetUninitializedObject anyway.



 Comments   
Comment by Robert Stam [ 26/Oct/20 ]

Here's an example of a class that will be deserialized incorrectly if the constructor is bypassed:

public class Person
{
    private readonly int _id;
    private readonly string _firstName;
    private readonly string _lastName;
    private readonly string _fullName;
 
    public Person(int id, string firstName, string lastName)
    {
        _id = id;
        _firstName = firstName;
        _lastName = lastName;
        _fullName = firstName + " " + lastName;
    }
 
    public int Id => _id;
    public string FirstName => _firstName;
    public string LastName => _lastName;
 
    [BsonIgnore]
    public string FullName => _fullName;
}

The problem is that if the constructor is bypassed the _fullName field is never initialized correctly.

Generated at Wed Feb 07 21:44:45 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.