diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index 90ae9fd13c7..749c4283cf0 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -3677,6 +3677,41 @@ public void PropertyStringConstructorConsumingItemMetadata(string metadatumName, result.ShouldBe(metadatumValue); } + [Fact] + public void PropertyFunctionHashCodeSameOnlyIfStringSame() + { + PropertyDictionary pg = new PropertyDictionary(); + Expander expander = new Expander(pg, FileSystems.Default); + string[] stringsToHash = { + "cat1s", + "cat1z", + "bat1s", + "cut1s", + "cat1so", + "cats1", + "acat1s", + "cat12s", + "cat1s" + }; + int[] hashes = stringsToHash.Select(toHash => + (int)expander.ExpandPropertiesLeaveTypedAndEscaped($"$([MSBuild]::StableStringHash('{toHash}'))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance) + ).ToArray(); + for (int a = 0; a < hashes.Length; a++) + { + for (int b = a; b < hashes.Length; b++) + { + if (stringsToHash[a].Equals(stringsToHash[b])) + { + hashes[a].ShouldBe(hashes[b], "Identical strings should hash to the same value."); + } + else + { + hashes[a].ShouldNotBe(hashes[b], "Different strings should not hash to the same value."); + } + } + } + } + /// /// A whole bunch error check tests /// diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 75c65fd389c..1df70c8fd09 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -4009,6 +4009,14 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst return true; } } + else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.StableStringHash), StringComparison.OrdinalIgnoreCase)) + { + if (TryGetArg(args, out string arg0)) + { + returnVal = IntrinsicFunctions.StableStringHash(arg0); + return true; + } + } else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.AreFeaturesEnabled), StringComparison.OrdinalIgnoreCase)) { if (TryGetArg(args, out string arg0)) diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index b4f26fcc40e..7bdedf62916 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -347,6 +347,14 @@ internal static string ValueOrDefault(string conditionValue, string defaultValue } } + /// + /// Hash the string independent of bitness and target framework. + /// + internal static int StableStringHash(string toHash) + { + return CommunicationsUtilities.GetHashCode(toHash); + } + /// /// Returns true if a task host exists that can service the requested runtime and architecture /// values, and false otherwise. diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index 51ee4cfa488..3b0a98ea917 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -83,7 +83,7 @@ internal Handshake(HandshakeOptions nodeType) CommunicationsUtilities.Trace("Handshake salt is " + handshakeSalt); string toolsDirectory = (nodeType & HandshakeOptions.X64) == HandshakeOptions.X64 ? BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64 : BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32; CommunicationsUtilities.Trace("Tools directory is " + toolsDirectory); - salt = CommunicationsUtilities.GetHandshakeHashCode(handshakeSalt + toolsDirectory); + salt = CommunicationsUtilities.GetHashCode(handshakeSalt + toolsDirectory); Version fileVersion = new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); fileVersionMajor = fileVersion.Major; fileVersionMinor = fileVersion.Minor; @@ -622,7 +622,7 @@ internal static void Trace(int nodeId, string format, params object[] args) /// but stripped out architecture specific defines /// that causes the hashcode to be different and this causes problem in cross-architecture handshaking /// - internal static int GetHandshakeHashCode(string fileVersion) + internal static int GetHashCode(string fileVersion) { unsafe {