Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public static Checksum Create(IEnumerable<string?> values)
}

public static Checksum Create(ImmutableArray<string> values)
=> Create(ImmutableCollectionsMarshal.AsArray(values).AsSpan());

public static Checksum Create(ReadOnlySpan<string> values)
{
using var pooledHash = s_incrementalHashPool.GetPooledObject();

Expand Down Expand Up @@ -159,11 +162,12 @@ public static Checksum Create(ImmutableArray<Checksum> checksums)
}

public static Checksum Create(ImmutableArray<byte> bytes)
{
var source = ImmutableCollectionsMarshal.AsArray(bytes).AsSpan();
=> Create(ImmutableCollectionsMarshal.AsArray(bytes).AsSpan());

public static Checksum Create(ReadOnlySpan<byte> bytes)
{
Span<byte> destination = stackalloc byte[XXHash128SizeBytes];
XxHash128.Hash(source, destination);
XxHash128.Hash(bytes, destination);
return From(destination);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Security.Cryptography;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.CodeAnalysis.Diagnostics;
Expand Down Expand Up @@ -41,31 +42,30 @@ public static SourceGeneratedDocumentIdentity Generate(ProjectId projectId, stri
// ensure we don't have collisions.
var generatorIdentity = SourceGeneratorIdentity.Create(generator, analyzerReference);

// Combine the strings together; we'll use Encoding.Unicode since that'll match the underlying format; this can be made much
// faster once we're on .NET Core since we could directly treat the strings as ReadOnlySpan<char>.
var projectIdBytes = projectId.Id.ToByteArray();

// The assembly path should exist in any normal scenario; the hashing of the name only would apply if the user loaded a
// dynamic assembly they produced at runtime and passed us that via a custom AnalyzerReference.
var assemblyNameToHash = generatorIdentity.AssemblyPath ?? generatorIdentity.AssemblyName;

using var _ = ArrayBuilder<byte>.GetInstance(capacity: (assemblyNameToHash.Length + 1 + generatorIdentity.TypeName.Length + 1 + hintName.Length) * 2 + projectIdBytes.Length, out var hashInput);
hashInput.AddRange(projectIdBytes);

// Add a null to separate the generator name and hint name; since this is effectively a joining of UTF-16 bytes
// we'll use a UTF-16 null just to make sure there's absolutely no risk of collision.
hashInput.AddRange(Encoding.Unicode.GetBytes(assemblyNameToHash));
hashInput.AddRange(0, 0);
hashInput.AddRange(Encoding.Unicode.GetBytes(generatorIdentity.TypeName));
hashInput.AddRange(0, 0);
hashInput.AddRange(Encoding.Unicode.GetBytes(hintName));

// The particular choice of crypto algorithm here is arbitrary and can be always changed as necessary. The only requirement
// is it must be collision resistant, and provide enough bits to fill a GUID.
using var crytpoAlgorithm = System.Security.Cryptography.SHA256.Create();
var hash = crytpoAlgorithm.ComputeHash(hashInput.ToArray());
Array.Resize(ref hash, 16);
var guid = new Guid(hash);
#if NET
Span<byte> bytesToChecksum = stackalloc byte[16];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a constant we can use for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is anything in the Guid class accessible to us. A regexp search for "guid.*16" in Roslyn has several hits, but nothing is sticking out as a good candidate to use.

projectId.Id.TryWriteBytes(bytesToChecksum);
#else
var bytesToChecksum = projectId.Id.ToByteArray().AsSpan();
#endif

ReadOnlySpan<string> stringsToChecksum = [assemblyNameToHash, generatorIdentity.TypeName, hintName];
var stringChecksum = Checksum.Create(stringsToChecksum);
var byteChecksum = Checksum.Create(bytesToChecksum);
var compositeChecksum = Checksum.Create(stringChecksum, byteChecksum);

Span<byte> checksumAsBytes = stackalloc byte[16];
compositeChecksum.WriteTo(checksumAsBytes);

#if NET
var guid = new Guid(checksumAsBytes);
#else
var guid = new Guid(checksumAsBytes.ToArray());
#endif

var documentId = DocumentId.CreateFromSerialized(projectId, guid, isSourceGenerated: true, hintName);

Expand Down
10 changes: 5 additions & 5 deletions src/Workspaces/CoreTest/ChecksumTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ public void ValidateChecksumFromSpanSameAsChecksumFromBytes10()
[Fact]
public void StringArraysProduceDifferentResultsThanConcatenation()
{
var checksum1 = Checksum.Create(["goo", "bar"]);
var checksum2 = Checksum.Create(["go", "obar"]);
var checksum1 = Checksum.Create(ImmutableArray.Create("goo", "bar"));
var checksum2 = Checksum.Create(ImmutableArray.Create("go", "obar"));
var checksum3 = Checksum.Create("goobar");
Assert.NotEqual(checksum1, checksum2);
Assert.NotEqual(checksum2, checksum3);
Expand All @@ -175,9 +175,9 @@ public void DoNotProduceNullChecksum()
Assert.NotEqual(Checksum.Null, Checksum.Create(ImmutableArray<Checksum>.Empty));
Assert.NotEqual(Checksum.Null, Checksum.Create(ImmutableArray<byte>.Empty));

Assert.NotEqual(Checksum.Null, Checksum.Create([""]));
Assert.NotEqual(Checksum.Null, Checksum.Create(["\0"]));
Assert.NotEqual(Checksum.Null, Checksum.Create(new string?[] { null }));
Assert.NotEqual(Checksum.Null, Checksum.Create(ImmutableArray.Create("")));
Assert.NotEqual(Checksum.Null, Checksum.Create(ImmutableArray.Create("\0")));
Assert.NotEqual(Checksum.Null, Checksum.Create(new string?[] { null }.AsEnumerable()));
Assert.NotEqual(Checksum.Null, Checksum.Create(new MemoryStream()));
Assert.NotEqual(Checksum.Null, Checksum.Create(stackalloc Checksum[] { Checksum.Null }));
Assert.NotEqual(Checksum.Null, Checksum.Create(ImmutableArray.Create(Checksum.Null)));
Expand Down
Loading