Edit: I\'m well aware of that this works very well with value types, my specific question is about using this for reference types.
Edit2:
I can't see how the explicit-layout version can be verifiable without the runtime injecting extra checks anyway, since it allows you to see a non-null reference to something that isn't of the declared type.
This would be safer:
struct Overlaid { // could also be a class for reference-type semantics
private object asObject;
public object AsObject {get {return asObject;} set {asObject = value;} }
public Foo AsFoo { get {return asObject as Foo;} set {asObject = value;} }
public Bar AsBar { get {return asObject as Bar;} set {asObject = value;} }
}
No risk of torn references etc, and still only a single field. It doesn't involve any risky code, etc. In particular, it doesn't risk something silly like:
[FieldOffset(0)]
public object AsObject;
[FieldOffset(0)]
public Foo AsFoo;
[FieldOffset(1)]
public Bar AsBar; // kaboom!!!!
Another issue is that you can only support a single field this way unless you can guarantee the CPU mode; offset 0 is easy, but it gets trickier if you need multiple fields and need to support x86 and x64.
I'm not aware of any issues with it. Furthermore, I doubt that Microsoft would permit this usage if it was dangerous in a non-obvious way.
If you align the type in an unsafe way, the runtime will throw a TypeLoadException
on load even when compiling with /unsafe
. So I think you are safe.
I'm guessing--since you can use StructLayout
and compile your code without /unsafe
flags-- that this is a feature of the CLR. You need the StructLayout attribute simply because C# has no direct means of declaring types this way.
Take a look at this page which details some of the way C# structs translates into IL, you'll notice that there are many memory layouts support built-in to the IL/CLR itself.
Well, you found a loop hole, the CLR permits it since all overlapped fields are objects. Anything that would allow you to mess with an object reference directly gets rejected with a TypeLoadException:
[StructLayout(LayoutKind.Explicit)]
struct Overlaid {
[FieldOffset(0)]
public object AsObject;
[FieldOffset(0)]
public IntPtr AsPointer;
}
But you can exploit it by giving the classes fields. Nothing really bad happens as long as you are just reading the field values, you can get the value of the tracking handle that way for example.
Writing those fields however leads to an ExecutionEngineException. I think however that it is an exploit if you can guess the value of a tracking handle correctly. Practical use is sufficiently close to zero though.
Since the garbage collector is untyped and only distinguishes between object references and plain bits, overlapping references won't confuse it. However, while one object reference can completely overlap another, this is unverifiable, aka unsafe (ECMA-335 standard, page 180, II.10.7 Controlling instance layout). It's easy to construct a program that exploits this unverifiability to crash horrendously:
using System.Runtime.InteropServices;
class Bar
{
public virtual void func() { }
}
[StructLayout(LayoutKind.Explicit)]
struct Overlaid
{
[FieldOffset(0)]
public object foo;
[FieldOffset(0)]
public Bar bar;
}
class Program
{
static void Main(string[] args)
{
var overlaid = new Overlaid();
overlaid.foo = new object();
overlaid.bar.func();
}
}
Here the func call loads a function pointer from one past the last element of object class's virtual table. According to this article following the vtbl there's a handle table. Treating it as a function pointer leads to a System.AccessViolationException.