{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/corefx/issues/27098","repository_url":"https://api.github.com/repos/dotnet/corefx","labels_url":"https://api.github.com/repos/dotnet/corefx/issues/27098/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/corefx/issues/27098/comments","events_url":"https://api.github.com/repos/dotnet/corefx/issues/27098/events","html_url":"https://github.com/dotnet/corefx/issues/27098","id":296855444,"number":27098,"title":".NET 4.7 -  '\\0' char in String causes GetHashCode to ignore remainder -- but on x64 only","user":{"login":"jcouv","id":12466233,"avatar_url":"https://avatars2.githubusercontent.com/u/12466233?v=4","gravatar_id":"","url":"https://api.github.com/users/jcouv","html_url":"https://github.com/jcouv","followers_url":"https://api.github.com/users/jcouv/followers","following_url":"https://api.github.com/users/jcouv/following{/other_user}","gists_url":"https://api.github.com/users/jcouv/gists{/gist_id}","starred_url":"https://api.github.com/users/jcouv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jcouv/subscriptions","organizations_url":"https://api.github.com/users/jcouv/orgs","repos_url":"https://api.github.com/users/jcouv/repos","events_url":"https://api.github.com/users/jcouv/events{/privacy}","received_events_url":"https://api.github.com/users/jcouv/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2018-02-13T19:22:44Z","updated_at":"2018-02-13T20:22:42Z","closed_at":null,"author_association":"MEMBER","body":"@vsfeedback commented on [Mon Feb 12 2018](https://github.com/dotnet/roslyn/issues/24780)\n\nThe `GetHashCode(...)` function for the **ordinal** `String` comparisons In **.NET 4.7** has a grossly different behavior between running on **x86** versus **x64**. Note: ***I am not talking about the different hash code values, per-platform***, since these are intended as opaque -- rather, the issue here is a ***semantic difference*** in runtime behavior between the two platforms.\r\n\r\nOn **x86**, the full length of a `String` is always hashed, regardless of any `\\0` characters.\r\n\r\nOn **x64**, the hashing terminates at the first `\\0` character.\r\n\r\nThis is trivial to demonstrate:\r\n\r\n\tvar h1 = \"\\0AAAAAAAAA\".GetHashCode();\r\n\tvar h2 = \"\\0BBBBBBBBBBBB\".GetHashCode();\r\n\tvar h3 = \"\\0CCCCCCCCCCCCCCC\".GetHashCode();\r\n\r\n\r\n\t// x64 results:\r\n\t//\t\t0x162a16fe\r\n\t//\t\t0x162a16fe\r\n\t//\t\t0x162a16fe\r\n\r\n\r\n\t// x86 results:\r\n\t//\t\t0x07b801bc\r\n\t//\t\t0x048e22a3\r\n\t//\t\t0x21e2037d\r\n\r\nAs shown above, this affects the `GetHashCode` instance virtual method (overriden by the `String` type), as well as two other **.NET**-provided techniques for obtaining an ordinal-based `String` hash code: `StringComparer.Ordinal.GetHashCode` and also `CultureInfo.CurrentCulture.CompareInfo.GetHashCode` (when using `CompareOptions.Ordinal`). These results are summarized below:\r\n\r\n<pre>\r\n-------------------------- x86 --------------------------\r\n&quot;\\0AAAAAAAAA&quot;\r\ns.GetHashCode\t\t\t\t\t0x07b801bc\r\nStringComparer.Ordinal.GetHashCode\t\t0x07b801bc\r\nCompareInfo.GetHashCode/CompareOptions.Ordinal\t0x07b801bc\r\n\r\n\r\n&quot;\\0BBBBBBBBBBBB&quot;\r\ns.GetHashCode\t\t\t\t\t0x00001505\r\nStringComparer.Ordinal.GetHashCode\t\t0x00001505\r\nCompareInfo.GetHashCode/CompareOptions.Ordinal\t0x00001505\r\n\r\n\r\n&quot;\\0CCCCCCCCCCCCCCC&quot;\r\ns.GetHashCode\t\t\t\t\t0x21e2037d\r\nStringComparer.Ordinal.GetHashCode\t\t0x21e2037d\r\nCompareInfo.GetHashCode/CompareOptions.Ordinal\t0x21e2037d\r\n\r\n\r\n\r\n\r\n-------------------------- x64 --------------------------\r\n&quot;\\0AAAAAAAAA&quot;\r\ns.GetHashCode\t\t\t\t\t0x162a16fe\r\nStringComparer.Ordinal.GetHashCode\t\t0x162a16fe\r\nCompareInfo.GetHashCode/CompareOptions.Ordinal\t0x162a16fe\r\n\r\n\r\n&quot;\\0BBBBBBBBBBBB&quot;\r\ns.GetHashCode\t\t\t\t\t0x162a16fe\r\nStringComparer.Ordinal.GetHashCode\t\t0x162a16fe\r\nCompareInfo.GetHashCode/CompareOptions.Ordinal\t0x162a16fe\r\n\r\n\r\n&quot;\\0CCCCCCCCCCCCCCC&quot;\r\ns.GetHashCode\t\t\t\t\t0x162a16fe\r\nStringComparer.Ordinal.GetHashCode\t\t0x162a16fe\r\nCompareInfo.GetHashCode/CompareOptions.Ordinal\t0x162a16fe\r\n</pre>\r\n\r\nWhile I do realize that theoretical practice deems hash codes purely advisory, in practice applications will be designed to expect that hash code collisions are rare. Being able to trivially produce an infinite number of identical hashes, at will -- such as one can do via &quot;\\0A&quot;, &quot;\\0B&quot;, &quot;\\0C&quot;, &quot;\\012345...&quot;  (on **x64** all of these give the same hash code as above, `0x162a16fe`)-- is neither in the spirit of hashing nor accepted/expected common practice.\r\n\r\nWorse, this only happens on the  x86 platform and not x64, which could cause considerable loss of design and development effort, if such proceeded based on a false impression of consistent behavior.\r\n\r\n_This issue has been moved from https://developercommunity.visualstudio.com/content/problem/196675/net-47-0-char-in-string-causes-gethashcode-to-igno-1.html\r\nVSTS ticketId: 565715_\r\n_These are the original issue comments:_\r\n\r\nGlenn Slayden on 2/9/2018, 06:39 PM (2 days ago): <p>I should also mention that the **x64** version of `String.GetHashCode(...)` in fact makes no reference to the string's length whatsoever, neither to incorporate that value into the hash, nor to determine how many characters to compute the hash code for. Instead, the x64 code ***just scans the string forward until it hits the first '\\0' char***. If there are no embedded nulls, then it will scan to the end, where the **CLR** ensures there will always be an extra `\\0` char (that is not included in the `Length`).</p><p>Thus, by ignoring the stored length of the string, the x64 code *takes a critical dependency* on that zero-terminating char being present. If that `\\0` gets corrupted somehow, the failure mode is severe, since that loop can immediately scan off the end of the string into unrelated memory areas. </p><p>Since the **x86** version refers to the stored string length to determine how many chars to hash--it might seem like that code doesn't have the same problem, and it's true that it's not as fatal, as follows:</p><p>The x86 code does actually take a ***different kind of dependency*** on the zero terminator, namely, that loop is written such that the 16-bit terminator value itself is `XORed` into the returned hash value whenever the two lowest bits of the input string length are equal to `0b_00000011 == 0x03`. For that operation to have no effect, the terminator must be zero (since `xor` with zero is a `nop`.)  So if the terminator somehow becomes corrupted, the returned hash code value will be affected, but there won't likely be an immediate `AccessViolationException` (as with the x64 case).</p>\r\n\r\n_These are the original issue solutions:_\r\n(no solutions)\n\n---\n\n@Joe4evr commented on [Mon Feb 12 2018](https://github.com/dotnet/roslyn/issues/24780#issuecomment-365098562)\n\nCurious, does this also repro for the `OrdinalIgnoreCase` variants?\n\n---\n\n@jcouv commented on [Mon Feb 12 2018](https://github.com/dotnet/roslyn/issues/24780#issuecomment-365161086)\n\n@AlexGhiondea Could you help route? It feels like either a runtime or a BCL issue on desktop.\n\n---\n\n@glenn-slayden commented on [Tue Feb 13 2018](https://github.com/dotnet/roslyn/issues/24780#issuecomment-365363366)\n\nYes, it should be BCL. The problem is with MSIL code that exists in the `String.GetHashCode` function in mscorlib.dll. The difference between **x64** and **x86** is due to there being different mscorlib.dll builds for each.\r\n\n\n---\n\n@glenn-slayden commented on [Tue Feb 13 2018](https://github.com/dotnet/roslyn/issues/24780#issuecomment-365370873)\n\n@jinujoseph  The 'Area-IDE' tag doesn't sound right; as I mentioned the problem is with runtime code in the Base Class Library.\n\n---\n\n@jcouv commented on [Tue Feb 13 2018](https://github.com/dotnet/roslyn/issues/24780#issuecomment-365374965)\n\nI'll move the issue over to corefx\n\n"},"comment":{"url":"https://api.github.com/repos/dotnet/corefx/issues/comments/365392261","html_url":"https://github.com/dotnet/corefx/issues/27098#issuecomment-365392261","issue_url":"https://api.github.com/repos/dotnet/corefx/issues/27098","id":365392261,"user":{"login":"danmosemsft","id":6385855,"avatar_url":"https://avatars3.githubusercontent.com/u/6385855?v=4","gravatar_id":"","url":"https://api.github.com/users/danmosemsft","html_url":"https://github.com/danmosemsft","followers_url":"https://api.github.com/users/danmosemsft/followers","following_url":"https://api.github.com/users/danmosemsft/following{/other_user}","gists_url":"https://api.github.com/users/danmosemsft/gists{/gist_id}","starred_url":"https://api.github.com/users/danmosemsft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danmosemsft/subscriptions","organizations_url":"https://api.github.com/users/danmosemsft/orgs","repos_url":"https://api.github.com/users/danmosemsft/repos","events_url":"https://api.github.com/users/danmosemsft/events{/privacy}","received_events_url":"https://api.github.com/users/danmosemsft/received_events","type":"User","site_admin":false},"created_at":"2018-02-13T20:22:42Z","updated_at":"2018-02-13T20:22:42Z","author_association":"MEMBER","body":"@glenn-slayden - oops, I think that was a mistake on their part. @AlexGhiondea how can we best get this into a place where action can happen? Reactivate the internal bug linked to Connect?"}}
{"actor":{"display_login":"danmosemsft"}}