From d2a5f5916de5ef043f4b5b7ef70a5b0f831363f9 Mon Sep 17 00:00:00 2001 From: rstam Date: Mon, 18 Apr 2022 09:32:32 -0700 Subject: [PATCH] CSHARP-4118: Limit propagation of known serializers. --- .../KnownSerializers/KnownSerializerFinder.cs | 2 +- .../KnownSerializers/KnownSerializersNode.cs | 27 ++++++++- .../KnownSerializersRegistry.cs | 14 ++++- .../Jira/CSharp4118Tests.cs | 60 +++++++++++++++++++ 4 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp4118Tests.cs diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializerFinder.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializerFinder.cs index 89fc6dde85b..8a56785145b 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializerFinder.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializerFinder.cs @@ -58,7 +58,7 @@ public override Expression Visit(Expression node) return null; } - _currentKnownSerializersNode = new KnownSerializersNode(_currentKnownSerializersNode); + _currentKnownSerializersNode = new KnownSerializersNode(node, _currentKnownSerializersNode); if (node == _root) { diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersNode.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersNode.cs index c1ae814c291..01eda9b215f 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersNode.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersNode.cs @@ -16,25 +16,28 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using MongoDB.Bson.Serialization; using MongoDB.Driver.Linq.Linq3Implementation.Misc; -using MongoDB.Driver.Support; namespace MongoDB.Driver.Linq.Linq3Implementation.Serializers.KnownSerializers { internal class KnownSerializersNode { // private fields + private readonly Expression _expression; private readonly Dictionary> _knownSerializers = new Dictionary>(); private readonly KnownSerializersNode _parent; // constructors - public KnownSerializersNode(KnownSerializersNode parent) + public KnownSerializersNode(Expression expression, KnownSerializersNode parent) { + _expression = expression; _parent = parent; // will be null for the root node } // public properties + public Expression Expression => _expression; public Dictionary> KnownSerializers => _knownSerializers; public KnownSerializersNode Parent => _parent; @@ -49,7 +52,10 @@ public void AddKnownSerializer(Type type, IBsonSerializer serializer) set.Add(serializer); - _parent?.AddKnownSerializer(type, serializer); + if (ShouldPropagateKnownSerializerToParent()) + { + _parent.AddKnownSerializer(type, serializer); + } } public HashSet GetPossibleSerializers(Type type) @@ -109,5 +115,20 @@ private HashSet GetPossibleSerializersAtThisLevel(Type type) return possibleSerializers; } + + private bool ShouldPropagateKnownSerializerToParent() + { + if (_parent == null) + { + return false; + } + + return _parent.Expression.NodeType switch + { + ExpressionType.MemberInit => false, + ExpressionType.New => false, + _ => true + }; + } } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersRegistry.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersRegistry.cs index cb4ecf64583..e39ecaa0742 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersRegistry.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializersRegistry.cs @@ -29,7 +29,15 @@ internal class KnownSerializersRegistry // public methods public void Add(Expression expression, KnownSerializersNode knownSerializers) { - if (_registry.ContainsKey(expression)) return; + if (knownSerializers.Expression != expression) + { + throw new ArgumentException($"Expression {expression} does not match knownSerializers.Expression {knownSerializers.Expression}."); + } + + if (_registry.ContainsKey(expression)) + { + return; + } _registry.Add(expression, knownSerializers); } @@ -41,8 +49,8 @@ public IBsonSerializer GetSerializer(Expression expression, IBsonSerializer defa return possibleSerializers.Count switch { 0 => defaultSerializer ?? BsonSerializer.LookupSerializer(expressionType), // sometimes there is no known serializer from the context (e.g. CSHARP-4062) - > 1 => throw new InvalidOperationException($"More than one possible serializer found for {expression}."), - _ => possibleSerializers.First() + 1 => possibleSerializers.First(), + _ => throw new InvalidOperationException($"More than one possible serializer found for {expression}.") }; } } diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp4118Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp4118Tests.cs new file mode 100644 index 00000000000..ab9ccbc8a3c --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp4118Tests.cs @@ -0,0 +1,60 @@ +/* Copyright 2010-present MongoDB Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System.Linq; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira +{ + public class CSharp4118Tests : Linq3IntegrationTest + { + [Fact] + public void Known_serializers_should_not_propagate_past_anonymous_class() + { + var collection = GetCollection(); + var queryable = collection.AsQueryable() + .Select(x => new { S = "abc", HasId = x.Id != "000000000000000000000000" }); + + var stages = Translate(collection, queryable); + + AssertStages(stages, "{ $project : { S : 'abc', HasId : { $ne : ['$_id', ObjectId('000000000000000000000000')] }, _id : 0 } }"); + } + + [Fact] + public void Known_serializers_should_not_propagate_past_class_with_member_initializers() + { + var collection = GetCollection(); + var queryable = collection.AsQueryable() + .Select(x => new R { S = "abc", HasId = x.Id != "000000000000000000000000" }); + + var stages = Translate(collection, queryable); + + AssertStages(stages, "{ $project : { S : 'abc', HasId : { $ne : ['$_id', ObjectId('000000000000000000000000')] }, _id : 0 } }"); + } + + private class C + { + [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } + } + + private class R + { + public string S { get; set; } + public bool HasId { get; set; } + } + } +}