}
/// <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>
}
[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);
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));
}
}
[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(