< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 245
Coverable lines: 245
Total lines: 457
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 58
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/NamespaceCache.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Collections.Immutable;
 6using System.Diagnostics;
 7using System.Runtime.CompilerServices;
 8
 9namespace System.Reflection.Metadata.Ecma335
 10{
 11    internal sealed class NamespaceCache
 12    {
 13        private readonly MetadataReader _metadataReader;
 014        private readonly object _namespaceTableAndListLock = new object();
 15        private Dictionary<NamespaceDefinitionHandle, NamespaceData>? _namespaceTable;
 16        private NamespaceData? _rootNamespace;
 17        private uint _virtualNamespaceCounter;
 18
 019        internal NamespaceCache(MetadataReader reader)
 020        {
 021            Debug.Assert(reader != null);
 022            _metadataReader = reader;
 023        }
 24
 25        /// <summary>
 26        /// Returns whether the namespaceTable has been created. If it hasn't, calling a GetXXX method
 27        /// on this will probably have a very high amount of overhead.
 28        /// </summary>
 29        internal bool CacheIsRealized
 30        {
 031            get { return _namespaceTable != null; }
 32        }
 33
 34        internal string GetFullName(NamespaceDefinitionHandle handle)
 035        {
 036            Debug.Assert(!handle.HasFullName); // we should not hit the cache in this case.
 037            NamespaceData data = GetNamespaceData(handle);
 038            return data.FullName;
 039        }
 40
 41        internal NamespaceData GetRootNamespace()
 042        {
 043            EnsureNamespaceTableIsPopulated();
 044            Debug.Assert(_rootNamespace != null);
 045            return _rootNamespace;
 046        }
 47
 48        internal NamespaceData GetNamespaceData(NamespaceDefinitionHandle handle)
 049        {
 050            EnsureNamespaceTableIsPopulated();
 51            NamespaceData? result;
 052            if (!_namespaceTable!.TryGetValue(handle, out result))
 053            {
 054                Throw.InvalidHandle();
 55            }
 56
 057            return result;
 058        }
 59
 60        /// <summary>
 61        /// This will return a StringHandle for the simple name of a namespace name at the given segment index.
 62        /// If no segment index is passed explicitly or the "segment" index is greater than or equal to the number
 63        /// of segments, then the last segment is used. "Segment" in this context refers to part of a namespace
 64        /// name between dots.
 65        ///
 66        /// Example: Given a NamespaceDefinitionHandle to "System.Collections.Generic.Test" called 'handle':
 67        ///
 68        ///   reader.GetString(GetSimpleName(handle)) == "Test"
 69        ///   reader.GetString(GetSimpleName(handle, 0)) == "System"
 70        ///   reader.GetString(GetSimpleName(handle, 1)) == "Collections"
 71        ///   reader.GetString(GetSimpleName(handle, 2)) == "Generic"
 72        ///   reader.GetString(GetSimpleName(handle, 3)) == "Test"
 73        ///   reader.GetString(GetSimpleName(handle, 1000)) == "Test"
 74        /// </summary>
 75        private StringHandle GetSimpleName(NamespaceDefinitionHandle fullNamespaceHandle, int segmentIndex = int.MaxValu
 076        {
 077            StringHandle handleContainingSegment = fullNamespaceHandle.GetFullName();
 078            Debug.Assert(!handleContainingSegment.IsVirtual);
 79
 080            int lastFoundIndex = fullNamespaceHandle.GetHeapOffset() - 1;
 081            int currentSegment = 0;
 082            while (currentSegment < segmentIndex)
 083            {
 084                int currentIndex = _metadataReader.StringHeap.IndexOfRaw(lastFoundIndex + 1, '.');
 085                if (currentIndex == -1)
 086                {
 087                    break;
 88                }
 089                lastFoundIndex = currentIndex;
 090                ++currentSegment;
 091            }
 92
 093            Debug.Assert(lastFoundIndex >= 0 || currentSegment == 0);
 94
 95            // + 1 because lastFoundIndex will either "point" to a '.', or will be -1. Either way,
 96            // we want the next char.
 097            int resultIndex = lastFoundIndex + 1;
 098            return StringHandle.FromOffset(resultIndex).WithDotTermination();
 099        }
 100
 101        /// <summary>
 102        /// Two distinct namespace handles represent the same namespace if their full names are the same. This
 103        /// method merges builders corresponding to such namespace handles.
 104        /// </summary>
 105        private void PopulateNamespaceTable()
 0106        {
 0107            lock (_namespaceTableAndListLock)
 0108            {
 0109                if (_namespaceTable != null)
 0110                {
 0111                    return;
 112                }
 113
 0114                var namespaceBuilderTable = new Dictionary<NamespaceDefinitionHandle, NamespaceDataBuilder>();
 115
 116                // Make sure to add entry for root namespace. The root namespace is special in that even
 117                // though it might not have types of its own it always has an equivalent representation
 118                // as a nil handle and we don't want to handle it below as dot-terminated virtual namespace.
 119                // We use NamespaceDefinitionHandle.FromIndexOfFullName(0) instead of default(NamespaceDefinitionHandle)
 120                // that we never hand back a handle to the user that doesn't have a typeid as that prevents
 121                // round-trip conversion to Handle and back. (We may discover other handle aliases for the
 122                // root namespace (any nil/empty string will do), but we need this one to always be there.
 0123                NamespaceDefinitionHandle rootNamespace = NamespaceDefinitionHandle.FromFullNameOffset(0);
 0124                namespaceBuilderTable.Add(
 0125                    rootNamespace,
 0126                    new NamespaceDataBuilder(
 0127                        rootNamespace,
 0128                        rootNamespace.GetFullName(),
 0129                        string.Empty));
 130
 0131                PopulateTableWithTypeDefinitions(namespaceBuilderTable);
 0132                PopulateTableWithExportedTypes(namespaceBuilderTable);
 133
 134                Dictionary<string, NamespaceDataBuilder> stringTable;
 0135                MergeDuplicateNamespaces(namespaceBuilderTable, out stringTable);
 136
 137                List<NamespaceDataBuilder>? virtualNamespaces;
 0138                ResolveParentChildRelationships(stringTable, out virtualNamespaces);
 139
 0140                var namespaceTable = new Dictionary<NamespaceDefinitionHandle, NamespaceData>();
 141
 0142                foreach (var group in namespaceBuilderTable)
 0143                {
 144                    // Freeze() caches the result, so any many-to-one relationships
 145                    // between keys and values will be preserved and efficiently handled.
 0146                    namespaceTable.Add(group.Key, group.Value.Freeze());
 0147                }
 148
 0149                if (virtualNamespaces != null)
 0150                {
 0151                    foreach (var virtualNamespace in virtualNamespaces)
 0152                    {
 0153                        namespaceTable.Add(virtualNamespace.Handle, virtualNamespace.Freeze());
 0154                    }
 0155                }
 156
 0157                _rootNamespace = namespaceTable[rootNamespace];
 0158                _namespaceTable = namespaceTable;
 0159            }
 0160        }
 161
 162        /// <summary>
 163        /// This will take 'table' and merge all of the NamespaceData instances that point to the same
 164        /// namespace. It has to create 'stringTable' as an intermediate dictionary, so it will hand it
 165        /// back to the caller should the caller want to use it.
 166        /// </summary>
 167        private static void MergeDuplicateNamespaces(Dictionary<NamespaceDefinitionHandle, NamespaceDataBuilder> table, 
 0168        {
 0169            var namespaces = new Dictionary<string, NamespaceDataBuilder>();
 0170            List<KeyValuePair<NamespaceDefinitionHandle, NamespaceDataBuilder>>? remaps = null;
 0171            foreach (var group in table)
 0172            {
 0173                NamespaceDataBuilder data = group.Value;
 174                NamespaceDataBuilder? existingRecord;
 0175                if (namespaces.TryGetValue(data.FullName, out existingRecord))
 0176                {
 177                    // Children should not exist until the next step.
 0178                    Debug.Assert(data.Namespaces!.Count == 0);
 0179                    data.MergeInto(existingRecord);
 180
 0181                    remaps ??= new List<KeyValuePair<NamespaceDefinitionHandle, NamespaceDataBuilder>>();
 0182                    remaps.Add(new KeyValuePair<NamespaceDefinitionHandle, NamespaceDataBuilder>(group.Key, existingReco
 0183                }
 184                else
 0185                {
 0186                    namespaces.Add(data.FullName, data);
 0187                }
 0188            }
 189
 190            // Needs to be done outside of foreach (var group in table) to avoid modifying the dictionary while foreach'
 0191            if (remaps != null)
 0192            {
 0193                foreach (var tuple in remaps)
 0194                {
 0195                    table[tuple.Key] = tuple.Value;
 0196                }
 0197            }
 198
 0199            stringTable = namespaces;
 0200        }
 201
 202        /// <summary>
 203        /// Creates a NamespaceDataBuilder instance that contains a synthesized NamespaceDefinitionHandle,
 204        /// as well as the name provided.
 205        /// </summary>
 206        private NamespaceDataBuilder SynthesizeNamespaceData(string fullName, NamespaceDefinitionHandle realChild)
 0207        {
 0208            Debug.Assert(realChild.HasFullName);
 209
 0210            int numberOfSegments = fullName.AsSpan().Count('.');
 211
 0212            StringHandle simpleName = GetSimpleName(realChild, numberOfSegments);
 0213            var namespaceHandle = NamespaceDefinitionHandle.FromVirtualIndex(++_virtualNamespaceCounter);
 0214            return new NamespaceDataBuilder(namespaceHandle, simpleName, fullName);
 0215        }
 216
 217        /// <summary>
 218        /// Quick convenience method that handles linking together child + parent
 219        /// </summary>
 220        private static void LinkChildDataToParentData(NamespaceDataBuilder child, NamespaceDataBuilder parent)
 0221        {
 0222            Debug.Assert(child != null && parent != null);
 0223            Debug.Assert(!child.Handle.IsNil);
 0224            child.Parent = parent.Handle;
 0225            parent.Namespaces!.Add(child.Handle);
 0226        }
 227
 228        /// <summary>
 229        /// Links a child to its parent namespace. If the parent namespace doesn't exist, this will create a
 230        /// virtual one. This will automatically link any virtual namespaces it creates up to its parents.
 231        /// </summary>
 232        private void LinkChildToParentNamespace(Dictionary<string, NamespaceDataBuilder> existingNamespaces,
 233            NamespaceDataBuilder realChild,
 234            ref List<NamespaceDataBuilder>? virtualNamespaces)
 0235        {
 0236            Debug.Assert(realChild.Handle.HasFullName);
 0237            string childName = realChild.FullName;
 0238            var child = realChild;
 239
 240            // The condition for this loop is very complex -- essentially, we keep going
 241            // until we:
 242            //   A. Encounter the root namespace as 'child'
 243            //   B. Find a preexisting namespace as 'parent'
 0244            while (true)
 0245            {
 0246                int lastIndex = childName.LastIndexOf('.');
 247                string parentName;
 0248                if (lastIndex == -1)
 0249                {
 0250                    if (childName.Length == 0)
 0251                    {
 0252                        return;
 253                    }
 254                    else
 0255                    {
 0256                        parentName = string.Empty;
 0257                    }
 0258                }
 259                else
 0260                {
 0261                    parentName = childName.Substring(0, lastIndex);
 0262                }
 263
 264                NamespaceDataBuilder? parentData;
 0265                if (existingNamespaces.TryGetValue(parentName, out parentData))
 0266                {
 0267                    LinkChildDataToParentData(child, parentData);
 0268                    return;
 269                }
 270
 0271                if (virtualNamespaces != null)
 0272                {
 0273                    foreach (var data in virtualNamespaces)
 0274                    {
 0275                        if (data.FullName == parentName)
 0276                        {
 0277                            LinkChildDataToParentData(child, data);
 0278                            return;
 279                        }
 0280                    }
 0281                }
 282                else
 0283                {
 0284                    virtualNamespaces = new List<NamespaceDataBuilder>();
 0285                }
 286
 0287                var virtualParent = SynthesizeNamespaceData(parentName, realChild.Handle);
 0288                LinkChildDataToParentData(child, virtualParent);
 0289                virtualNamespaces.Add(virtualParent);
 0290                childName = virtualParent.FullName;
 0291                child = virtualParent;
 0292            }
 0293        }
 294
 295        /// <summary>
 296        /// This will link all parents/children in the given namespaces dictionary up to each other.
 297        ///
 298        /// In some cases, we need to synthesize namespaces that do not have any type definitions or forwarders
 299        /// of their own, but do have child namespaces. These are returned via the virtualNamespaces out
 300        /// parameter.
 301        /// </summary>
 302        private void ResolveParentChildRelationships(Dictionary<string, NamespaceDataBuilder> namespaces, out List<Names
 0303        {
 0304            virtualNamespaces = null;
 0305            foreach (var namespaceData in namespaces)
 0306            {
 0307                LinkChildToParentNamespace(namespaces, namespaceData.Value, ref virtualNamespaces);
 0308            }
 0309        }
 310
 311        /// <summary>
 312        /// Loops through all type definitions in metadata, adding them to the given table
 313        /// </summary>
 314        private void PopulateTableWithTypeDefinitions(Dictionary<NamespaceDefinitionHandle, NamespaceDataBuilder> table)
 0315        {
 0316            Debug.Assert(table != null);
 317
 0318            foreach (var typeHandle in _metadataReader.TypeDefinitions)
 0319            {
 0320                TypeDefinition type = _metadataReader.GetTypeDefinition(typeHandle);
 0321                if (type.Attributes.IsNested())
 0322                {
 0323                    continue;
 324                }
 325
 0326                NamespaceDefinitionHandle namespaceHandle = _metadataReader.TypeDefTable.GetNamespaceDefinition(typeHand
 327                NamespaceDataBuilder? builder;
 0328                if (table.TryGetValue(namespaceHandle, out builder))
 0329                {
 0330                    builder.TypeDefinitions!.Add(typeHandle);
 0331                }
 332                else
 0333                {
 0334                    StringHandle name = GetSimpleName(namespaceHandle);
 0335                    string fullName = _metadataReader.GetString(namespaceHandle);
 0336                    var newData = new NamespaceDataBuilder(namespaceHandle, name, fullName);
 0337                    newData.TypeDefinitions!.Add(typeHandle);
 0338                    table.Add(namespaceHandle, newData);
 0339                }
 0340            }
 0341        }
 342
 343        /// <summary>
 344        /// Loops through all type forwarders in metadata, adding them to the given table
 345        /// </summary>
 346        private void PopulateTableWithExportedTypes(Dictionary<NamespaceDefinitionHandle, NamespaceDataBuilder> table)
 0347        {
 0348            Debug.Assert(table != null);
 349
 0350            foreach (var exportedTypeHandle in _metadataReader.ExportedTypes)
 0351            {
 0352                ExportedType exportedType = _metadataReader.GetExportedType(exportedTypeHandle);
 0353                if (exportedType.Implementation.Kind == HandleKind.ExportedType)
 0354                {
 0355                    continue; // skip nested exported types.
 356                }
 357
 0358                NamespaceDefinitionHandle namespaceHandle = exportedType.NamespaceDefinition;
 359                NamespaceDataBuilder? builder;
 0360                if (table.TryGetValue(namespaceHandle, out builder))
 0361                {
 0362                    builder.ExportedTypes!.Add(exportedTypeHandle);
 0363                }
 364                else
 0365                {
 0366                    Debug.Assert(namespaceHandle.HasFullName);
 0367                    StringHandle simpleName = GetSimpleName(namespaceHandle);
 0368                    string fullName = _metadataReader.GetString(namespaceHandle);
 0369                    var newData = new NamespaceDataBuilder(namespaceHandle, simpleName, fullName);
 0370                    newData.ExportedTypes!.Add(exportedTypeHandle);
 0371                    table.Add(namespaceHandle, newData);
 0372                }
 0373            }
 0374        }
 375
 376        /// <summary>
 377        /// If the namespace table doesn't exist, populates it!
 378        /// </summary>
 379        private void EnsureNamespaceTableIsPopulated()
 0380        {
 381            // PERF: Branch will rarely be taken; do work in PopulateNamespaceList() so this can be inlined easily.
 0382            if (_namespaceTable == null)
 0383            {
 0384                PopulateNamespaceTable();
 0385            }
 0386            Debug.Assert(_namespaceTable != null);
 0387        }
 388
 389        /// <summary>
 390        /// An intermediate class used to build NamespaceData instances. This was created because we wanted to
 391        /// use ImmutableArrays in NamespaceData, but having ArrayBuilders and ImmutableArrays that served the
 392        /// same purpose in NamespaceData got ugly. With the current design of how we create our Namespace
 393        /// dictionary, this needs to be a class because we have a many-to-one mapping between NamespaceHandles
 394        /// and NamespaceData. So, the pointer semantics must be preserved.
 395        ///
 396        /// This class assumes that the builders will not be modified in any way after the first call to
 397        /// Freeze().
 398        /// </summary>
 399        private sealed class NamespaceDataBuilder
 400        {
 401            public readonly NamespaceDefinitionHandle Handle;
 402            public readonly StringHandle Name;
 403            public readonly string FullName;
 404            public NamespaceDefinitionHandle Parent;
 405            public ImmutableArray<NamespaceDefinitionHandle>.Builder? Namespaces;
 406            public ImmutableArray<TypeDefinitionHandle>.Builder? TypeDefinitions;
 407            public ImmutableArray<ExportedTypeHandle>.Builder? ExportedTypes;
 408
 409            private NamespaceData? _frozen;
 410
 0411            public NamespaceDataBuilder(NamespaceDefinitionHandle handle, StringHandle name, string fullName)
 0412            {
 0413                Handle = handle;
 0414                Name = name;
 0415                FullName = fullName;
 0416                Namespaces = ImmutableArray.CreateBuilder<NamespaceDefinitionHandle>();
 0417                TypeDefinitions = ImmutableArray.CreateBuilder<TypeDefinitionHandle>();
 0418                ExportedTypes = ImmutableArray.CreateBuilder<ExportedTypeHandle>();
 0419            }
 420
 421            /// <summary>
 422            /// Returns a NamespaceData that represents this NamespaceDataBuilder instance. After calling
 423            /// this method, it is an error to use any methods or fields except Freeze() on the target
 424            /// NamespaceDataBuilder.
 425            /// </summary>
 426            public NamespaceData Freeze()
 0427            {
 428                // It is not an error to call this function multiple times. We cache the result
 429                // because it's immutable.
 0430                if (_frozen == null)
 0431                {
 0432                    var namespaces = Namespaces!.ToImmutable();
 0433                    Namespaces = null;
 434
 0435                    var typeDefinitions = TypeDefinitions!.ToImmutable();
 0436                    TypeDefinitions = null;
 437
 0438                    var exportedTypes = ExportedTypes!.ToImmutable();
 0439                    ExportedTypes = null;
 440
 0441                    _frozen = new NamespaceData(Name, FullName, Parent, namespaces, typeDefinitions, exportedTypes);
 0442                }
 443
 0444                return _frozen;
 0445            }
 446
 447            public void MergeInto(NamespaceDataBuilder other)
 0448            {
 0449                Parent = default(NamespaceDefinitionHandle);
 0450                other.Namespaces!.AddRange(this.Namespaces!);
 0451                other.TypeDefinitions!.AddRange(this.TypeDefinitions!);
 0452                other.ExportedTypes!.AddRange(this.ExportedTypes!);
 0453            }
 454        }
 455    }
 456}
 457

Methods/Properties

.ctor(System.Reflection.Metadata.MetadataReader)
CacheIsRealized()
GetFullName(System.Reflection.Metadata.NamespaceDefinitionHandle)
GetRootNamespace()
GetNamespaceData(System.Reflection.Metadata.NamespaceDefinitionHandle)
GetSimpleName(System.Reflection.Metadata.NamespaceDefinitionHandle,System.Int32)
PopulateNamespaceTable()
MergeDuplicateNamespaces(System.Collections.Generic.Dictionary`2<System.Reflection.Metadata.NamespaceDefinitionHandle,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>,System.Collections.Generic.Dictionary`2<System.String,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>&)
SynthesizeNamespaceData(System.String,System.Reflection.Metadata.NamespaceDefinitionHandle)
LinkChildDataToParentData(System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder)
LinkChildToParentNamespace(System.Collections.Generic.Dictionary`2<System.String,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder,System.Collections.Generic.List`1<System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>&)
ResolveParentChildRelationships(System.Collections.Generic.Dictionary`2<System.String,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>,System.Collections.Generic.List`1<System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>&)
PopulateTableWithTypeDefinitions(System.Collections.Generic.Dictionary`2<System.Reflection.Metadata.NamespaceDefinitionHandle,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>)
PopulateTableWithExportedTypes(System.Collections.Generic.Dictionary`2<System.Reflection.Metadata.NamespaceDefinitionHandle,System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder>)
EnsureNamespaceTableIsPopulated()
.ctor(System.Reflection.Metadata.NamespaceDefinitionHandle,System.Reflection.Metadata.StringHandle,System.String)
Freeze()
MergeInto(System.Reflection.Metadata.Ecma335.NamespaceCache/NamespaceDataBuilder)