Implements MetadataBuilder.GetOrAddDocumentName (dotnet/corefx#9716)
authorTomáš Matoušek <tmat@users.noreply.github.com>
Tue, 28 Jun 2016 19:46:09 +0000 (12:46 -0700)
committerGitHub <noreply@github.com>
Tue, 28 Jun 2016 19:46:09 +0000 (12:46 -0700)
* Implements MetadataBuilder.GetOrAddDocumentName

Commit migrated from https://github.com/dotnet/corefx/commit/b90d787285edec417659f1a04bbb3e00f46945b8

src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs
src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs
src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs
src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs

index 2b08bc5..e579605 100644 (file)
@@ -1044,8 +1044,12 @@ namespace System.Reflection.Metadata
             WriteUTF8(value, 0, value.Length, allowUnpairedSurrogates, prependSize: false);
         }
 
-        private void WriteUTF8(string str, int start, int length, bool allowUnpairedSurrogates, bool prependSize)
+        internal void WriteUTF8(string str, int start, int length, bool allowUnpairedSurrogates, bool prependSize)
         {
+            Debug.Assert(start >= 0);
+            Debug.Assert(length >= 0);
+            Debug.Assert(start + length <= str.Length);
+
             if (!IsHead)
             {
                 Throw.InvalidOperationBuilderAlreadyLinked();
@@ -1061,7 +1065,7 @@ namespace System.Reflection.Metadata
 
                 int bytesToCurrent = BlobUtilities.GetUTF8ByteCount(currentPtr, length, byteLimit, out nextPtr);
                 int charsToCurrent = (int)(nextPtr - currentPtr);
-                int charsToNext = str.Length - charsToCurrent;
+                int charsToNext = length - charsToCurrent;
                 int bytesToNext = BlobUtilities.GetUTF8ByteCount(nextPtr, charsToNext);
 
                 if (prependSize)
index 829a72a..627851a 100644 (file)
@@ -295,6 +295,81 @@ namespace System.Reflection.Metadata.Ecma335
         }
 
         /// <summary>
+        /// Encodes a debug document name and adds it to the Blob heap, if it's not there already.
+        /// </summary>
+        /// <param name="value">Document name.</param>
+        /// <returns>
+        /// Handle to the added or existing document name blob
+        /// (see https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#DocumentNameBlob).
+        /// </returns>
+        /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
+        public BlobHandle GetOrAddDocumentName(string value)
+        {
+            if (value == null)
+            {
+                Throw.ArgumentNull(nameof(value));
+            }
+
+            char separator = ChooseSeparator(value);
+
+            var resultBuilder = PooledBlobBuilder.GetInstance();
+            resultBuilder.WriteByte((byte)separator);
+
+            var partBuilder = PooledBlobBuilder.GetInstance();
+
+            int i = 0;
+            while (true)
+            {
+                int next = value.IndexOf(separator, i);
+
+                partBuilder.WriteUTF8(value, i, (next >= 0 ? next : value.Length) - i, allowUnpairedSurrogates: true, prependSize: false);
+                resultBuilder.WriteCompressedInteger(GetOrAddBlob(partBuilder).GetHeapOffset());
+
+                if (next == -1)
+                {
+                    break;
+                }
+
+                if (next == value.Length - 1)
+                {
+                    // trailing separator:
+                    resultBuilder.WriteByte(0);
+                    break;
+                }
+
+                partBuilder.Clear();
+                i = next + 1;
+            }
+
+            partBuilder.Free();
+
+            var resultHandle = GetOrAddBlob(resultBuilder);
+            resultBuilder.Free();
+            return resultHandle;
+        }
+
+        private static char ChooseSeparator(string str)
+        {
+            const char s1 = '/';
+            const char s2 = '\\';
+
+            int count1 = 0, count2 = 0;
+            foreach (var c in str)
+            {
+                if (c == s1)
+                {
+                    count1++;
+                }
+                else if (c == s2)
+                {
+                    count2++;
+                }
+            }
+
+            return (count1 >= count2) ? s1 : s2;
+        }
+
+        /// <summary>
         /// Adds specified Guid to Guid heap, if it's not there already.
         /// </summary>
         /// <param name="guid">Guid to add.</param>
index 701a421..4f9b792 100644 (file)
@@ -974,6 +974,43 @@ namespace System.Reflection.Metadata.Tests
         }
 
         [Fact]
+        public void WriteUTF8_Substring()
+        {
+            var writer = new BlobBuilder(4);
+            writer.WriteUTF8("abc", 0, 0, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new byte[0], writer.ToArray());
+            writer.Clear();
+
+            writer.WriteUTF8("abc", 0, 1, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new[] { (byte)'a' }, writer.ToArray());
+            writer.Clear();
+
+            writer.WriteUTF8("abc", 0, 2, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new[] { (byte)'a', (byte)'b' }, writer.ToArray());
+            writer.Clear();
+
+            writer.WriteUTF8("abc", 0, 3, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new[] { (byte)'a', (byte)'b', (byte)'c' }, writer.ToArray());
+            writer.Clear();
+
+            writer.WriteUTF8("abc", 1, 0, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new byte[0], writer.ToArray());
+            writer.Clear();
+
+            writer.WriteUTF8("abc", 1, 1, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new[] { (byte)'b' }, writer.ToArray());
+            writer.Clear();
+
+            writer.WriteUTF8("abc", 1, 2, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new[] { (byte)'b', (byte)'c' }, writer.ToArray());
+            writer.Clear();
+
+            writer.WriteUTF8("abc", 2, 1, allowUnpairedSurrogates: true, prependSize: false);
+            AssertEx.Equal(new[] { (byte)'c' }, writer.ToArray());
+            writer.Clear();
+        }
+
+        [Fact]
         public void EmptyWrites()
         {
             var writer = new BlobWriter(16);
index 9fd1e8f..8b7bc7f 100644 (file)
@@ -286,6 +286,7 @@ namespace System.Reflection.Metadata.Ecma335.Tests
             Assert.Throws<ArgumentNullException>("value", () => mdBuilder.GetOrAddBlob(default(ImmutableArray<byte>)));
             Assert.Throws<ArgumentNullException>("value", () => mdBuilder.GetOrAddBlobUTF8(null));
             Assert.Throws<ArgumentNullException>("value", () => mdBuilder.GetOrAddBlobUTF16(null));
+            Assert.Throws<ArgumentNullException>("value", () => mdBuilder.GetOrAddDocumentName(null));
             Assert.Throws<ArgumentNullException>("value", () => mdBuilder.GetOrAddString(null));
         }
 
@@ -372,6 +373,130 @@ namespace System.Reflection.Metadata.Ecma335.Tests
         }
 
         [Fact]
+        public void GetOrAddDocumentName1()
+        {
+            var mdBuilder = new MetadataBuilder();
+            mdBuilder.GetOrAddDocumentName("");
+            mdBuilder.GetOrAddDocumentName("/a/b/c");
+            mdBuilder.GetOrAddDocumentName(@"\a\b\cc");
+            mdBuilder.GetOrAddDocumentName(@"/a/b\c");
+            mdBuilder.GetOrAddDocumentName(@"/\a/\b\\//c");
+            mdBuilder.GetOrAddDocumentName(@"a/");
+            mdBuilder.GetOrAddDocumentName(@"/");
+            mdBuilder.GetOrAddDocumentName(@"\\");
+            mdBuilder.GetOrAddDocumentName("\ud800"); // unpaired surrogate
+            mdBuilder.GetOrAddDocumentName("\0");
+
+            var serialized = mdBuilder.GetSerializedMetadata(MetadataRootBuilder.EmptyRowCounts, 12, isStandaloneDebugMetadata: false);
+
+            var heaps = new BlobBuilder();
+            mdBuilder.WriteHeapsTo(heaps, serialized.StringHeap);
+
+            AssertEx.Equal(new byte[]
+            {
+                // #String
+                0x00, 0x00, 0x00, 0x00,
+                // #US
+                0x00, 0x00, 0x00, 0x00,
+                
+                0x00,              // 0x00
+
+                // ""
+                0x02, (byte)'/', 0x00,
+
+                0x01, (byte)'a',   // 0x04
+                0x01, (byte)'b',   // 0x06
+                0x01, (byte)'c',   // 0x08
+
+                // "/a/b/c"
+                0x05, (byte)'/', 0x00, 0x04, 0x06, 0x08,
+
+                // 0x10
+                0x02, (byte)'c', (byte)'c', 
+
+                // @"\a\b\cc"
+                0x05, (byte)'\\', 0x00, 0x04, 0x06, 0x10,
+
+                // 0x19
+                0x03, (byte)'b', (byte)'\\', (byte)'c',
+
+                // @"/a/b\c"
+                0x04, (byte)'/', 0x00, 0x04, 0x19,
+
+                // 0x22
+                0x02, (byte)'\\', (byte)'a',
+
+                // 0x25
+                0x04, (byte)'\\', (byte)'b', (byte)'\\', (byte)'\\',
+
+                // @"/\a/\b\\//c"
+                0x06, (byte)'/', 0x00, 0x22, 0x25, 0x00, 0x08,
+                
+                // @"a/"
+                0x03, (byte)'/', 0x04, 0x00,
+
+                // @"/"
+                0x03, (byte)'/', 0x00, 0x00,
+
+                // @"\\"
+                0x04, (byte)'\\', 0x00, 0x00, 0x00,
+
+                // 0x3E
+                0x03, 0xED, 0xA0, 0x80,
+
+                // "\ud800"
+                0x02, (byte)'/', 0x3E,
+
+                // 0x45
+                0x01, 0x00,
+
+                // "\0"
+                0x02, (byte)'/', 0x45,
+
+                // heap padding
+                0x00, 0x00
+            }, heaps.ToArray());
+        }
+
+        [Fact]
+        public void GetOrAddDocumentName2()
+        {
+            var mdBuilder = new MetadataBuilder();
+            mdBuilder.AddModule(0, default(StringHandle), default(GuidHandle), default(GuidHandle), default(GuidHandle));
+
+            var n1 = mdBuilder.GetOrAddDocumentName("");
+            var n2 = mdBuilder.GetOrAddDocumentName("/a/b/c");
+            var n3 = mdBuilder.GetOrAddDocumentName(@"\a\b\cc");
+            var n4 = mdBuilder.GetOrAddDocumentName(@"/a/b\c");
+            var n5 = mdBuilder.GetOrAddDocumentName(@"/\a/\b\\//c");
+            var n6 = mdBuilder.GetOrAddDocumentName(@"a/");
+            var n7 = mdBuilder.GetOrAddDocumentName(@"/");
+            var n8 = mdBuilder.GetOrAddDocumentName(@"\\");
+            var n9 = mdBuilder.GetOrAddDocumentName("\ud800"); // unpaired surrogate
+            var n10 = mdBuilder.GetOrAddDocumentName("\0");
+
+            var root = new MetadataRootBuilder(mdBuilder);
+            var rootBuilder = new BlobBuilder();
+            root.Serialize(rootBuilder, 0, 0);
+            var mdImage = rootBuilder.ToImmutableArray();
+
+            using (var provider = MetadataReaderProvider.FromMetadataImage(mdImage))
+            {
+                var mdReader = provider.GetMetadataReader();
+                Assert.Equal("", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n1))));
+                Assert.Equal("/a/b/c", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n2))));
+                Assert.Equal(@"\a\b\cc", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n3))));
+                Assert.Equal(@"/a/b\c", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n4))));
+                Assert.Equal(@"/\a/\b\\//c", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n5))));
+                Assert.Equal(@"a/", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n6))));
+                Assert.Equal(@"/", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n7))));
+                Assert.Equal(@"\\", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n8))));
+                Assert.Equal("\uFFFd\uFFFd", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n9))));
+                Assert.Equal("\0", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n10))));
+            }
+        }
+
+        [Fact]
         public void Heaps_StartOffsets()
         {
             var mdBuilder = new MetadataBuilder(