Skip to content

Commit

Permalink
InlineArray's Equals and GetHashCode throw (dotnet#103612)
Browse files Browse the repository at this point in the history
* InlineArray's Equals and GetHashCode throw

The default Equals() and GetHashCode() throw
for types marked with InlineArrayAttribute.
  • Loading branch information
AaronRobinsonMSFT authored and rzikm committed Jun 24, 2024
1 parent ceca9de commit 9bbd7d4
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,10 @@ public static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException();
}

public static void ThrowNotSupportedInlineArrayEqualsGetHashCode()
{
throw new NotSupportedException(SR.NotSupported_InlineArrayEqualsGetHashCode);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ public static bool CanCompareValueTypeBits(MetadataType type, MethodDesc objectE
if (type.ContainsGCPointers)
return false;

if (type.IsInlineArray)
return false;

if (type.IsGenericDefinition)
return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ public override MethodIL EmitIL()

ILEmitter emitter = new ILEmitter();

if (_owningType is MetadataType mdType)
{
// Types marked as InlineArray aren't supported by
// the built-in Equals() or GetHashCode().
if (mdType.IsInlineArray)
{
var stream = emitter.NewCodeStream();
MethodDesc thrower = Context.GetHelperEntryPoint("ThrowHelpers", "ThrowNotSupportedInlineArrayEqualsGetHashCode");
stream.EmitCallThrowHelper(emitter, thrower);
return emitter.Link(this);
}
}

TypeDesc methodTableType = Context.SystemModule.GetKnownType("Internal.Runtime", "MethodTable");
MethodDesc methodTableOfMethod = methodTableType.GetKnownMethod("Of", null);

Expand Down
8 changes: 6 additions & 2 deletions src/coreclr/vm/comutilnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1624,7 +1624,8 @@ BOOL CanCompareBitsOrUseFastGetHashCode(MethodTable* mt)
}

if (mt->ContainsPointers()
|| mt->IsNotTightlyPacked())
|| mt->IsNotTightlyPacked()
|| mt->GetClass()->IsInlineArray())
{
mt->SetHasCheckedCanCompareBitsOrUseFastGetHashCode();
return FALSE;
Expand Down Expand Up @@ -1677,14 +1678,17 @@ BOOL CanCompareBitsOrUseFastGetHashCode(MethodTable* mt)
return canCompareBitsOrUseFastGetHashCode;
}

extern "C" BOOL QCALLTYPE MethodTable_CanCompareBitsOrUseFastGetHashCode(MethodTable * mt)
extern "C" BOOL QCALLTYPE MethodTable_CanCompareBitsOrUseFastGetHashCode(MethodTable* mt)
{
QCALL_CONTRACT;

BOOL ret = FALSE;

BEGIN_QCALL;

if (mt->GetClass()->IsInlineArray())
COMPlusThrow(kNotSupportedException, W("NotSupported_InlineArrayEqualsGetHashCode"));

ret = CanCompareBitsOrUseFastGetHashCode(mt);

END_QCALL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3029,6 +3029,9 @@
<data name="NotSupported_IllegalOneByteBranch" xml:space="preserve">
<value>Illegal one-byte branch at position: {0}. Requested branch was: {1}.</value>
</data>
<data name="NotSupported_InlineArrayEqualsGetHashCode" xml:space="preserve">
<value>Calling built-in Equals() or GetHashCode() on type marked as InlineArray is invalid.</value>
</data>
<data name="NotSupported_KeyCollectionSet" xml:space="preserve">
<value>Mutating a key collection derived from a dictionary is not allowed.</value>
</data>
Expand Down
10 changes: 10 additions & 0 deletions src/mono/mono/metadata/icall.c
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,11 @@ ves_icall_System_ValueType_InternalGetHashCode (MonoObjectHandle this_obj, MonoA

klass = mono_handle_class (this_obj);

if (m_class_is_inlinearray (klass)) {
mono_error_set_not_supported (error, "Calling built-in GetHashCode() on type marked as InlineArray is invalid.");
return FALSE;
}

if (mono_class_num_fields (klass) == 0)
return result;

Expand Down Expand Up @@ -1327,6 +1332,11 @@ ves_icall_System_ValueType_Equals (MonoObjectHandle this_obj, MonoObjectHandle t

klass = mono_handle_class (this_obj);

if (m_class_is_inlinearray (klass)) {
mono_error_set_not_supported (error, "Calling built-in Equals() on type marked as InlineArray is invalid.");
return FALSE;
}

if (m_class_is_enumtype (klass) && mono_class_enum_basetype_internal (klass) && mono_class_enum_basetype_internal (klass)->type == MONO_TYPE_I4)
return *(gint32*)mono_handle_get_data_unsafe (this_obj) == *(gint32*)mono_handle_get_data_unsafe (that);

Expand Down
60 changes: 58 additions & 2 deletions src/tests/Loader/classloader/InlineArray/InlineArrayValid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public unsafe class Validate
{
// ====================== SizeOf ==============================================================
[InlineArray(42)]
struct FourtyTwoBytes
struct FortyTwoBytes
{
byte b;
}
Expand All @@ -50,11 +50,20 @@ struct FourtyTwoBytes
public static void Sizeof()
{
Console.WriteLine($"{nameof(Sizeof)}...");
Assert.Equal(42, sizeof(FourtyTwoBytes));
Assert.Equal(42, sizeof(FortyTwoBytes));
Assert.Equal(84, sizeof(MyArray<char>));
}

// ====================== OneElement ==========================================================
// These types are interesting since their layouts are
// identical with or without the InlineArrayAttribute.

[InlineArray(1)]
struct OneInt
{
public int i;
}

[InlineArray(1)]
struct OneObj
{
Expand All @@ -65,6 +74,7 @@ struct OneObj
public static void OneElement()
{
Console.WriteLine($"{nameof(OneElement)}...");
Assert.Equal(sizeof(int), sizeof(OneInt));
Assert.Equal(sizeof(nint), sizeof(OneObj));
}

Expand Down Expand Up @@ -393,4 +403,50 @@ public static void MonoGCDescOpt()
Assert.Equal(i + 1, holder.arr[i].s);
}
}

struct StructHasFortyTwoBytesField
{
FortyTwoBytes _field;
}

struct StructHasOneIntField
{
OneInt _field;
}

[Fact]
public static void InlineArrayEqualsGetHashCode_Fails()
{
Console.WriteLine($"{nameof(InlineArrayEqualsGetHashCode_Fails)}...");

Assert.Throws<NotSupportedException>(() =>
{
new OneInt().Equals(new OneInt());
});

Assert.Throws<NotSupportedException>(() =>
{
new StructHasOneIntField().Equals(new StructHasOneIntField());
});

Assert.Throws<NotSupportedException>(() =>
{
new FortyTwoBytes().Equals(new FortyTwoBytes());
});

Assert.Throws<NotSupportedException>(() =>
{
new StructHasFortyTwoBytesField().Equals(new StructHasFortyTwoBytesField());
});

Assert.Throws<NotSupportedException>(() =>
{
new OneInt().GetHashCode();
});

Assert.Throws<NotSupportedException>(() =>
{
new FortyTwoBytes().GetHashCode();
});
}
}

0 comments on commit 9bbd7d4

Please sign in to comment.