| File: System\Text\Json\Document\JsonDocument.PropertyNameSet.cs | Web Access |
| Project: src\runtime\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace System.Text.Json { public sealed partial class JsonDocument { // Ensure this stays on the stack by making it a ref struct. private ref struct PropertyNameSet : IDisposable { // Data structure to track property names in an object while deserializing // into a JsonDocument and validate that there are no duplicates. A small // array is used when the number of properties is small and no properties // are escaped. Otherwise a hash set is used. private HashSet<ReadOnlyMemory<byte>>? _hashSet; private const int ArraySetThreshold = 16; private int _arraySetCount; private bool _useArraySet = true; #if NET [InlineArray(ArraySetThreshold)] private struct InlineRangeArray16 { private (int Start, int Length) _element0; } private InlineRangeArray16 _arraySet; #else private readonly (int Start, int Length)[] _arraySet = new (int Start, int Length)[ArraySetThreshold]; #endif public PropertyNameSet() { } internal void SetCapacity(int capacity) { if (capacity <= ArraySetThreshold) { _useArraySet = true; } else { _useArraySet = false; if (_hashSet is null) { _hashSet = new HashSet<ReadOnlyMemory<byte>>( #if NET capacity, #endif PropertyNameComparer.Instance); } else { #if NET _hashSet.EnsureCapacity(capacity); #endif } } } internal void AddPropertyName(JsonProperty property, JsonDocument document) { DbRow dbRow = document._parsedData.Get(property.Value.MetadataDbIndex - DbRow.Size); Debug.Assert(dbRow.TokenType is JsonTokenType.PropertyName); ReadOnlyMemory<byte> utf8Json = document._utf8Json; ReadOnlyMemory<byte> propertyName = utf8Json.Slice(dbRow.Location, dbRow.SizeOrLength); if (dbRow.HasComplexChildren) { SwitchToHashSet(utf8Json); propertyName = JsonReaderHelper.GetUnescaped(propertyName.Span); } if (_useArraySet) { for (int i = 0; i < _arraySetCount; i++) { (int Start, int Length) range = _arraySet[i]; ReadOnlySpan<byte> previousPropertyName = utf8Json.Span.Slice(range.Start, range.Length); if (previousPropertyName.SequenceEqual(propertyName.Span)) { ThrowHelper.ThrowJsonException_DuplicatePropertyNotAllowed(propertyName.Span); } } _arraySet[_arraySetCount] = (dbRow.Location, dbRow.SizeOrLength); _arraySetCount++; } else { Debug.Assert(_hashSet is not null); if (!_hashSet.Add(propertyName)) { ThrowHelper.ThrowJsonException_DuplicatePropertyNotAllowed(propertyName.Span); } } } private void SwitchToHashSet(ReadOnlyMemory<byte> utf8Json) { if (_useArraySet) { _hashSet ??= new HashSet<ReadOnlyMemory<byte>>( #if NET ArraySetThreshold, #endif PropertyNameComparer.Instance); for (int i = 0; i < _arraySetCount; i++) { (int Start, int Length) range = _arraySet[i]; ReadOnlyMemory<byte> propertyName = utf8Json.Slice(range.Start, range.Length); bool success = _hashSet.Add(propertyName); Debug.Assert(success, $"Property name {propertyName} should not already exist in the set."); } _useArraySet = false; _arraySetCount = 0; } } internal void Reset() { _hashSet?.Clear(); _arraySetCount = 0; } public readonly void Dispose() { } private sealed class PropertyNameComparer : IEqualityComparer<ReadOnlyMemory<byte>> { internal static readonly PropertyNameComparer Instance = new(); public bool Equals(ReadOnlyMemory<byte> left, ReadOnlyMemory<byte> right) => left.Length == right.Length && left.Span.SequenceEqual(right.Span); public int GetHashCode(ReadOnlyMemory<byte> name) { // Marvin is the currently used hash algorithm for string comparisons. // The seed is unique to this process so an item's hash code can't easily be // discovered by an adversary trying to perform a denial of service attack. return Marvin.ComputeHash32(name.Span, Marvin.DefaultSeed); } } } } }