Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

InvalidCastException When Using [StructLayout] to Reinterpret Bytes
TobiasvdVen
#1 Posted : Wednesday, July 24, 2024 1:14:56 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 2/18/2022(UTC)
Posts: 7
Location: Denmark

Thanks: 2 times
Was thanked: 2 time(s) in 2 post(s)
I found an odd case in the codebase at my work after upgrading to 5.9.0.1 today. I can't say for certain if this was present in previous versions of NCrunch, as it has been a while since I was able to run NCrunch with our solution.

What I do know is that this test passes in the VS test runner, but not in NCrunch:

Quote:
[StructLayout(LayoutKind.Explicit)]
private struct BytesToUints
{
[FieldOffset(0)] public byte[] Bytes;
[FieldOffset(0)] public uint[] Uints;
}

[Fact]
public void InvalidCastException_Repro()
{
byte[] bytes = new byte[] { 1, 1, 0, 0 };
uint[] uints = new BytesToUints { Bytes = bytes }.Uints;

uint result = uints[0];

Assert.Equal(257u, result);
}


This is an unconventional way of achieving the desired reinterpretation in C#, I imagine it was written long ago by someone more familiar with C++. I'm not entirely sure it's fully reliable, given the nature of managed arrays, but it does seem to work in practice.

That being said, I wonder why this fails in NCrunch specifically. The exception is the following:

Quote:
System.InvalidCastException: Unable to cast object of type 'System.Byte[]' to type 'System.UInt32[]'.


Any ideas?

Edit: It may be based on this blog post, referenced from the wiki for MurmurHash: http://landman-code.blog...h-and-murmurhash2.html.

As mentioned there, this is a bit of a hack, but "works". As long as you don't rely on Uints.Length (or perhaps other properties of either array), because it will actually equal the length of the byte array. I wonder what NCrunch is doing that causes a cast to be attempted of the uint[] to the "actual" type of byte[]. I thought it might be RDI related, but disabling that does not remove the exception.
Remco
#2 Posted : Wednesday, July 24, 2024 11:13:51 PM(UTC)
Rank: NCrunch Developer

Groups: Administrators
Joined: 4/16/2011(UTC)
Posts: 7,074

Thanks: 946 times
Was thanked: 1274 time(s) in 1183 post(s)
Hi, thanks for sharing this issue.

It looks like RDI doesn't like this. Having two fields sharing the same place in memory must be causing some crosswiring at IL level or in the evaluation stack. I seemed to have a different experience with this problem to you. Although I can reproduce it consistently, disabling RDI via suppression comment did fix it for me, as such:

Code:

//ncrunch: rdi off
[Test]
public void Test1()
{
byte[] bytes = new byte[] { 1, 1, 0, 0 };
uint[] uints = new BytesToUints { Bytes = bytes }.Uints;

uint result = uints[0];

Assert.AreEqual(257u, result);
}
//ncrunch: rdi default


Could you give this a try and let me know if it lets you work around the problem?

Edit: I've done some quick analysis of the behaviour of the runtime around this odd piece of code. It looks like the problem is actually coming from the platform itself, with NCrunch's instrumentation responsible for surfacing it. The following code will cause the same exception to appear:

Code:

[StructLayout(LayoutKind.Explicit)]
public struct BytesToUints
{
[FieldOffset(0)] public byte[] Bytes;
[FieldOffset(0)] public uint[] Uints;
}

public class Tests
{
[Test]
public void Test1()
{
byte[] bytes = new byte[] { 1, 1, 0, 0 };
uint[] uints = (uint[])MyMethod(new BytesToUints { Bytes = bytes }.Uints);

uint result = uints[0];

Assert.AreEqual(257u, result);
}

public object MyMethod(object value)
{
return value;
}
}


There's no reasonable fix that can be made to NCrunch that will allow us to support this code. It looks like it's relying on quirks of the runtime in order to function. I don't think MS intend for this to be possible, or if they do, they haven't done much QA around it. The RDI suppression comments should allow NCrunch to roll with the code, but I recommend finding an alternative approach if possible. Perhaps having a single field of type object and casting where appropriate might allow a similar but more stable result.
1 user thanked Remco for this useful post.
TobiasvdVen on 7/25/2024(UTC)
TobiasvdVen
#3 Posted : Thursday, July 25, 2024 8:14:08 AM(UTC)
Rank: Newbie

Groups: Registered
Joined: 2/18/2022(UTC)
Posts: 7
Location: Denmark

Thanks: 2 times
Was thanked: 2 time(s) in 2 post(s)
That seems consistent with my findings. It seems to indexing operator "works" by accident, but the underlying type at runtime is still a byte[] and attempting to cast explicitly reveals that fact.

Quote:
//ncrunch: rdi off


This also fixes it for me, so I guess it was RDI after all. I thought I had disabled it solution-wide.

I'm looking into alternative solutions, such as MemoryMarshal.Cast<byte, uint>, which behave reliably.
Funnily though, this hack is noticeably faster in some quick benchmarks:

Quote:

| Method | Job | Runtime | Size | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
| StructLayout | .NET 8.0 | .NET 8.0 | 65536 | 392.8 us | 3.25 us | 3.04 us | 1.00 | 0.00 | - | NA |
| MemoryMarshalCast | .NET 8.0 | .NET 8.0 | 65536 | 939.9 us | 8.30 us | 7.77 us | 2.39 | 0.02 | - | NA |
| Unsafe | .NET 8.0 | .NET 8.0 | 65536 | 943.6 us | 9.33 us | 8.72 us | 2.40 | 0.03 | - | NA |


I say "noticeably", but this is measuring 1000000 iterations at a time to make benchmark dotnet be able to differentiate the timing from an empty method. Either approach is unlikely to be a significant performance bottleneck, though this is part of a hashing implementation.

Anyway, thanks for commenting. This is clearly not something for NCrunch to address :)
Users browsing this topic
Guest
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.

YAF | YAF © 2003-2011, Yet Another Forum.NET
This page was generated in 0.041 seconds.
Trial NCrunch
Take NCrunch for a spin
Do your fingers a favour and supercharge your testing workflow
Free Download