Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ValueType.GetHashCode not calling overriden method on nested field #98754

Merged
merged 11 commits into from
Feb 26, 2024

Conversation

huoyaoyuan
Copy link
Member

Follow up for #97590 (comment)

This is indeed a bug, since GetHashCode can return different result when Equals is returning true.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Feb 21, 2024
@huoyaoyuan huoyaoyuan marked this pull request as ready for review February 23, 2024 10:31
@@ -164,7 +164,8 @@ private unsafe int RegularGetValueTypeHashCode(ref byte data, int numFields)
var fieldValue = (ValueType)RuntimeImports.RhBox(fieldType, ref fieldData);
if (fieldValue != null)
{
hashCode = fieldValue.GetHashCodeImpl();
// call virtual method to handle overriden case
hashCode = fieldValue.GetHashCode();
Copy link
Member

Choose a reason for hiding this comment

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

I think GetHashCodeImpl can now be inlined into GetHashCode.

The only reason we had GetHashCodeImpl was because it was emulating CoreCLR behavior that did not call the GetHashCode on the field type (even if it had one) but instead used the fallback algorithm.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is it feasible to use System.HashCode for NativeAOT here? Will it bring unintended dependency or complexity?

Copy link
Member

Choose a reason for hiding this comment

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

I think it would be fine to use System.HashCode. This was mirroring the CoreCLR-JIT logic but now it diverged because that's how forks usually end up.

If we really wanted to, we could probably unify this to the point of sharing a part of this file, maybe with an ifdef or two. From a quick look GetHashCodeStrategy could be implemented using NativeAOT's __GetFieldHelper.

@@ -95,21 +95,17 @@ public override unsafe bool Equals([NotNullWhen(true)] object? obj)

public override unsafe int GetHashCode()
{
int hashCode = (int)this.GetMethodTable()->HashCode;
HashCode hashCode = default;
hashCode.Add(this.GetMethodTable()->HashCode);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
hashCode.Add(this.GetMethodTable()->HashCode);
hashCode.Add((IntPtr)this.GetMethodTable());

to match CoreCLR

@@ -150,7 +137,9 @@ private unsafe int RegularGetValueTypeHashCode(ref byte data, int numFields)
}
else if (fieldType->IsPrimitive)
{
hashCode = FastGetValueTypeHashCodeHelper(fieldType, ref fieldData);
HashCode hash = default;
Copy link
Member

Choose a reason for hiding this comment

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

The method should take ref HashCode argument instead of creating a new instance here.

Copy link
Member

Choose a reason for hiding this comment

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

I meant change RegularGetValueTypeHashCode to take ref HashCode argument and delete HashCode hash = default; that this comment is attached to.

@huoyaoyuan
Copy link
Member Author

Noticed that the test case doesn't actually cover this case. It should not match the default comparison/hashcode strategy of nested field.

Using manual version of int Abs to avoid introducing any dependency.

@jkotas
Copy link
Member

jkotas commented Feb 25, 2024

Noticed that the test case doesn't actually cover this case. It should not match the default comparison/hashcode strategy of nested field

What is the test case that you are talking about?

Using manual version of int Abs to avoid introducing any dependency.

Nit: Dependencies like this are not a problem in the tests.

}

private static unsafe int FastGetValueTypeHashCodeHelper(MethodTable* type, ref byte data)
private static unsafe void AddHashCodeForField(ref HashCode hashCode, MethodTable* type, ref byte data)
Copy link
Member

Choose a reason for hiding this comment

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

This method name is a bit misleading. It cannot be used to add hashcode for any field.

I like the Span returning method that you had here before better.

@huoyaoyuan
Copy link
Member Author

What is the test case that you are talking about?

Describing the failing case: GetHashCode of top-level struct fails to call GetHashCode on top.field, but running the fallback strategy on top.field instead, which end up calls default strategy on top.field.nested.
If top.field.GetHashCode behaves the same as default strategy of top.field.nested, the test won't fail.
So, it's better to implement top.field.GetHashCode different than top.field.nested.GetHashCode.

Nit: Dependencies like this are not a problem in the tests.

The original reproduction uses StringComparer.OrdinalIgnoreCase. I think using numbers only is more clear here.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

Thanks

@jkotas jkotas merged commit a71cc52 into dotnet:main Feb 26, 2024
151 of 153 checks passed
@huoyaoyuan huoyaoyuan deleted the valuetype-hashcode-fix branch February 26, 2024 19:25
@github-actions github-actions bot locked and limited conversation to collaborators Mar 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-VM-coreclr community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants