[CSHARP-4470] Missing null check in BsonClassMapSerializer Created: 01/Jan/23  Updated: 27/Oct/23  Resolved: 18/Jan/23

Status: Closed
Project: C# Driver
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: R B Assignee: Robert Stam
Resolution: Gone away Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Summary

As I reviewed the source code to fill some gaps in the documentation, it appeared to me that a null check may be missing on this line to turn the code into the below (see the null check in the if statement)

// process any left over values that weren't passed to the creator
            foreach (var keyValuePair in values)
            {
                var elementName = keyValuePair.Key;
                var value = keyValuePair.Value;
 
                var memberMap = _classMap.GetMemberMapForElement(elementName);
                if (memberMap != null && !memberMap.IsReadOnly)
                {
                    memberMap.Setter.Invoke(document, value);
                }
            }

For there may be situations in which there are leftover values for properties not having any memberMap defined. In these situations, with the current implementation the user would receive a NullReferenceException at runtime since GetMemberMapForElement will return null. If this situation is not allowed, a better approach would be throw a more specific exception but it seems to me that the ideal behavior would be to silently ignore the properties not having any memberMap.

Please provide the version of the driver. If applicable, please provide the MongoDB server version and topology (standalone, replica set, or sharded cluster).

Affects version 2.18 (latest release) and likely previous versions as well.

How to Reproduce

  1. Create a class without default constructor and having for example 5 properties while only 2 of them are set from the constructor, while the remaining three are set through methods.
  2. All properties have public getters and private setters
  3. Register the constructor as a creator with the driver
  4. Create a member map for 1 of the properties not being set in the constructor but none for the remaining 2 (and do not use automap)
  5. Create an instance of the class, set all properties and persist it in MongoDB using the driver
  6. Retrieve the record from the database to trigger deserialization.

Additional Background

Please note that the ability to have leftover values for instances created using creators is extremely useful (and even essential in our case) so I am by no mean asking for this feature to be removed.



 Comments   
Comment by PM Bot [ 18/Jan/23 ]

There hasn't been any recent activity on this ticket, so we're resolving it. Thanks for reaching out! Please feel free to comment on this if you're able to provide more information.

Comment by Robert Stam [ 03/Jan/23 ]

You can see the full code I used to attempt to reproduce here:

https://github.com/rstam/mongo-csharp-driver/commits/csharp4470

Comment by Robert Stam [ 03/Jan/23 ]

Thanks for this report.

I am unable to reproduce the reported issue.

I used this class:

public class C
{
    public C(string s)
    {
        S = s;
    }
 
    public string S { get; set; } // mapped
    public string T { get; set; } // not mapped
}

 And configured the class map (in code) using:

static CSharp4470Tests()
{
    var cm = new BsonClassMap<C>();
    cm.MapConstructor(typeof(C).GetConstructor(new[] { typeof(string) }), "S");
    cm.MapProperty(x => x.S);
    cm.SetIgnoreExtraElements(true); // if set to false still doesn't throw a NullReferenceException
    BsonClassMap.RegisterClassMap(cm);
}

 And wrote this test:

[Fact]
public void Deerialize_with_unmapped_element_should_return_expected_result()
{
    var document = BsonDocument.Parse("{ S : 's', T : 't' }"); // note that T will be ignored because IgnoreExtraElements was set to true
    var result = BsonSerializer.Deserialize<C>(document);
    result.S.Should().Be("s");
    result.T.Should().BeNull();
}

Even if I configure `IgnoreExtraElements` to `false` I still don't get a `NullReferenceException`. Instead I get a `FormatException` with an error message stating that `T` is an unmapped element.

System.FormatException : Element 'T' does not match any field or property of class MongoDB.Bson.Tests.Jira.CSharp4470Tests+C.

Please let me know if I should be doing something different to reproduce what you are reporting.

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