diff --git a/src/TypeSystem/src/Common/FieldLayout.cs b/src/TypeSystem/src/Common/FieldLayout.cs index a2653d2e0c6..36c33926531 100644 --- a/src/TypeSystem/src/Common/FieldLayout.cs +++ b/src/TypeSystem/src/Common/FieldLayout.cs @@ -546,10 +546,13 @@ internal void ComputeInstanceFieldLayout() _instanceFieldAlignment = computedLayout.FieldAlignment; _instanceByteCount = computedLayout.ByteCount; - foreach (var fieldAndOffset in computedLayout.Offsets) + if (computedLayout.Offsets != null) { - Debug.Assert(fieldAndOffset.Field.OwningType == this); - fieldAndOffset.Field.InitializeOffset(fieldAndOffset.Offset); + foreach (var fieldAndOffset in computedLayout.Offsets) + { + Debug.Assert(fieldAndOffset.Field.OwningType == this); + fieldAndOffset.Field.InitializeOffset(fieldAndOffset.Offset); + } } _fieldLayoutFlags.AddFlags(FieldLayoutFlags.HasInstanceFieldLayout); diff --git a/src/TypeSystem/tests/CoreTestAssembly/CoreTestAssembly.csproj b/src/TypeSystem/tests/CoreTestAssembly/CoreTestAssembly.csproj new file mode 100644 index 00000000000..2bda7169d8d --- /dev/null +++ b/src/TypeSystem/tests/CoreTestAssembly/CoreTestAssembly.csproj @@ -0,0 +1,38 @@ + + + + + Debug + AnyCPU + Library + CoreTestAssembly + + false + true + + + Portable + .NETPortable + v4.5 + Profile7 + .NET Portable Subset + false + + + + + + + + + + + + + + + + diff --git a/src/TypeSystem/tests/CoreTestAssembly/InstanceFieldLayout.cs b/src/TypeSystem/tests/CoreTestAssembly/InstanceFieldLayout.cs new file mode 100644 index 00000000000..62e1a9a4acc --- /dev/null +++ b/src/TypeSystem/tests/CoreTestAssembly/InstanceFieldLayout.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +#pragma warning disable 169 + +namespace ContainsPointers +{ + struct NoPointers + { + int int1; + byte byte1; + char char1; + } + + struct StillNoPointers + { + NoPointers noPointers1; + bool bool1; + } + + class ClassNoPointers + { + char char1; + } + + struct HasPointers + { + string string1; + } + + struct FieldHasPointers + { + HasPointers hasPointers1; + } + + class ClassHasPointers + { + ClassHasPointers classHasPointers1; + } + + class BaseClassHasPointers : ClassHasPointers + { + } + + public class ClassHasIntArray + { + int[] intArrayField; + } + + public class ClassHasArrayOfClassType + { + ClassNoPointers[] classTypeArray; + } +} + +namespace Explicit +{ + [StructLayout(LayoutKind.Explicit)] + class Class1 + { + static int Stat; + [FieldOffset(4)] + bool Bar; + [FieldOffset(10)] + char Baz; + } + + [StructLayout(LayoutKind.Explicit)] + class Class2 : Class1 + { + [FieldOffset(0)] + int Lol; + [FieldOffset(20)] + byte Omg; + } + + [StructLayout(LayoutKind.Explicit, Size = 40)] + class ExplicitSize : Class1 + { + [FieldOffset(0)] + int Lol; + [FieldOffset(20)] + byte Omg; + } + + [StructLayout(LayoutKind.Explicit)] + public class ExplicitEmptyClass + { + } + + [StructLayout(LayoutKind.Explicit)] + public struct ExplicitEmptyStruct + { + } +} + +namespace Sequential +{ + class Class1 + { + int MyInt; + bool MyBool; + char MyChar; + string MyString; + byte[] MyByteArray; + Class1 MyClass1SelfRef; + } + + class Class2 : Class1 + { + int MyInt2; + } + + struct Struct0 + { + bool b1; + bool b2; + bool b3; + int i1; + string s1; + } + + struct Struct1 + { + Struct0 MyStruct0; + bool MyBool; + } +} + diff --git a/src/TypeSystem/tests/CoreTestAssembly/Platform.cs b/src/TypeSystem/tests/CoreTestAssembly/Platform.cs new file mode 100644 index 00000000000..e22eb6ed725 --- /dev/null +++ b/src/TypeSystem/tests/CoreTestAssembly/Platform.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable 649 + +namespace System +{ + // Dummy core types to allow us compiling this assembly as a core library so that the type + // system tests don't have a dependency on a real core library. + + // We might need to bring in some extra things (Interface lists? Virtual methods on Object?), + // but let's postpone that until actually needed. + + public class Object + { + internal IntPtr m_pEEType; + } + + public struct Void { } + public struct Boolean { } + public struct Char { } + public struct SByte { } + public struct Byte { } + public struct Int16 { } + public struct UInt16 { } + public struct Int32 { } + public struct UInt32 { } + public struct Int64 { } + public struct UInt64 { } + public struct IntPtr { } + public struct UIntPtr { } + public struct Single { } + public struct Double { } + public abstract class ValueType { } + public abstract class Enum : ValueType { } + public struct Nullable where T : struct { } + + public sealed class String { } + public abstract class Array { } + public abstract class Delegate { } + public abstract class MulticastDelegate : Delegate { } + + public struct RuntimeTypeHandle { } + public struct RuntimeMethodHandle { } + public struct RuntimeFieldHandle { } + + public class Attribute { } +} + +namespace System.Runtime.InteropServices +{ + public enum LayoutKind + { + Sequential = 0, // 0x00000008, + Explicit = 2, // 0x00000010, + Auto = 3, // 0x00000000, + } + + public sealed class StructLayoutAttribute : Attribute + { + internal LayoutKind _val; + + public StructLayoutAttribute(LayoutKind layoutKind) + { + _val = layoutKind; + } + + public LayoutKind Value { get { return _val; } } + public int Pack; + public int Size; + } + + public sealed class FieldOffsetAttribute : Attribute + { + private int _val; + public FieldOffsetAttribute(int offset) + { + _val = offset; + } + public int Value { get { return _val; } } + } +} + diff --git a/src/TypeSystem/tests/InstanceFieldLayoutTests.cs b/src/TypeSystem/tests/InstanceFieldLayoutTests.cs new file mode 100644 index 00000000000..b3485b6f59c --- /dev/null +++ b/src/TypeSystem/tests/InstanceFieldLayoutTests.cs @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Internal.TypeSystem.Ecma; +using Internal.TypeSystem; + +using Xunit; + +namespace TypeSystemTests +{ + public class InstanceFieldLayoutTests + { + TestTypeSystemContext _context; + EcmaModule _testModule; + + public InstanceFieldLayoutTests() + { + _context = new TestTypeSystemContext(TargetArchitecture.X64); + var systemModule = _context.CreateModuleForSimpleName("CoreTestAssembly"); + _context.SetSystemModule(systemModule); + + _testModule = systemModule; + } + + [Fact] + public void TestExplicitLayout() + { + MetadataType t = _testModule.GetType("Explicit", "Class1"); + + // With 64bit, there should be 8 bytes for the System.Object EE data pointer + + // 10 bytes up until the offset of the char field + the char size of 2 + we + // round up the whole instance size to the next pointer size (+4) = 24 + Assert.Equal(24, t.InstanceByteCount); + + foreach (var field in t.GetFields()) + { + if (field.IsStatic) + continue; + + if (field.Name == "Bar") + { + // Bar has explicit offset 4 and is in a class (with S.O size overhead of ) + // Therefore it should have offset 4 + 8 = 12 + Assert.Equal(12, field.Offset); + } + else if (field.Name == "Baz") + { + // Baz has explicit offset 10. 10 + 8 = 18 + Assert.Equal(18, field.Offset); + } + else + { + Assert.True(false); + } + } + } + + [Fact] + public void TestExplicitLayoutThatIsEmpty() + { + var explicitEmptyClassType = _testModule.GetType("Explicit", "ExplicitEmptyClass"); + + // ExplicitEmpty class has 8 from System.Object overhead = 8 + Assert.Equal(8, explicitEmptyClassType.InstanceByteCount); + + var explicitEmptyStructType = _testModule.GetType("Explicit", "ExplicitEmptyStruct"); + + // ExplicitEmpty class has 0 bytes in it... so instance field size gets pushed up to 1. + Assert.Equal(1, explicitEmptyStructType.InstanceFieldSize); + } + + [Fact] + public void TestExplicitTypeLayoutWithSize() + { + var explicitSizeType = _testModule.GetType("Explicit", "ExplicitSize"); + Assert.Equal(48, explicitSizeType.InstanceByteCount); + } + + [Fact] + public void TestExplicitTypeLayoutWithInheritance() + { + MetadataType class2Type = _testModule.GetType("Explicit", "Class2"); + + // Class1 has size 24 which Class2 inherits from. Class2 adds a byte at offset 20, so + 21 + // = 45, rounding up to the next pointer size = 48 + Assert.Equal(48, class2Type.InstanceByteCount); + + foreach (var f in class2Type.GetFields()) + { + if (f.IsStatic) + continue; + + if (f.Name == "Lol") + { + // First field after base class, with offset 0 so it should lie on the byte count of + // the base class = 24 + Assert.Equal(24, f.Offset); + } + else if (f.Name == "Omg") + { + // Offset 20 from base class byte count = 44 + Assert.Equal(44, f.Offset); + } + else + { + Assert.True(false); + } + } + } + + [Fact] + public void TestSequentialTypeLayout() + { + MetadataType class1Type = _testModule.GetType("Sequential", "Class1"); + + // Byte count + // Base Class 8 + // MyInt 4 + // MyBool 1 + 1 padding + // MyChar 2 + // MyString 8 + // MyByteArray 8 + // MyClass1SelfRef 8 + // ------------------- + // 40 + Assert.Equal(0x28, class1Type.InstanceByteCount); + + foreach (var f in class1Type.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "MyInt": + Assert.Equal(0x8, f.Offset); + break; + case "MyBool": + Assert.Equal(0xC, f.Offset); + break; + case "MyChar": + Assert.Equal(0xE, f.Offset); + break; + case "MyString": + Assert.Equal(0x10, f.Offset); + break; + case "MyByteArray": + Assert.Equal(0x18, f.Offset); + break; + case "MyClass1SelfRef": + Assert.Equal(0x20, f.Offset); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void TestSequentialTypeLayoutInheritance() + { + MetadataType class2Type = _testModule.GetType("Sequential", "Class2"); + + // Byte count + // Base Class 40 + // MyInt2 4 + 4 byte padding to make class size % pointer size == 0 + // ------------------- + // 44 + Assert.Equal(0x30, class2Type.InstanceByteCount); + + foreach (var f in class2Type.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "MyInt2": + Assert.Equal(0x28, f.Offset); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void TestSequentialTypeLayoutStruct() + { + MetadataType struct0Type = _testModule.GetType("Sequential", "Struct0"); + + // Byte count + // bool b1 1 + // bool b2 1 + // bool b3 1 + 1 padding for int alignment + // int i1 4 + // string s1 8 + // ------------------- + // 16 (0x10) + Assert.Equal(0x10, struct0Type.InstanceByteCount); + + foreach (var f in struct0Type.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "b1": + Assert.Equal(0x0, f.Offset); + break; + case "b2": + Assert.Equal(0x1, f.Offset); + break; + case "b3": + Assert.Equal(0x2, f.Offset); + break; + case "i1": + Assert.Equal(0x4, f.Offset); + break; + case "s1": + Assert.Equal(0x8, f.Offset); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + // Test that when a struct is used as a field, we use its instance byte size as the size (ie, treat it + // as a value type) and not a pointer size. + public void TestSequentialTypeLayoutStructEmbedded() + { + MetadataType struct1Type = _testModule.GetType("Sequential", "Struct1"); + + // Byte count + // struct MyStruct0 16 + // bool MyBool 1 + // ----------------------- + // 24 (0x18) + Assert.Equal(0x18, struct1Type.InstanceByteCount); + + foreach (var f in struct1Type.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "MyStruct0": + Assert.Equal(0x0, f.Offset); + break; + case "MyBool": + Assert.Equal(0x10, f.Offset); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void TestTypeContainsPointers() + { + MetadataType type = _testModule.GetType("ContainsPointers", "NoPointers"); + Assert.False(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "StillNoPointers"); + Assert.False(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "ClassNoPointers"); + Assert.False(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "HasPointers"); + Assert.True(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "FieldHasPointers"); + Assert.True(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "ClassHasPointers"); + Assert.True(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "BaseClassHasPointers"); + Assert.True(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "ClassHasIntArray"); + Assert.True(type.ContainsPointers); + + type = _testModule.GetType("ContainsPointers", "ClassHasArrayOfClassType"); + Assert.True(type.ContainsPointers); + } + } +} diff --git a/src/TypeSystem/tests/TestTypeSystemContext.cs b/src/TypeSystem/tests/TestTypeSystemContext.cs new file mode 100644 index 00000000000..f81396f1a13 --- /dev/null +++ b/src/TypeSystem/tests/TestTypeSystemContext.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Debug = System.Diagnostics.Debug; + +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; +using System.Reflection.PortableExecutable; +using System.IO; + +namespace TypeSystemTests +{ + class TestTypeSystemContext : TypeSystemContext + { + static readonly string[] s_wellKnownTypeNames = new string[] { + "Void", + "Boolean", + "Char", + "SByte", + "Byte", + "Int16", + "UInt16", + "Int32", + "UInt32", + "Int64", + "UInt64", + "IntPtr", + "UIntPtr", + "Single", + "Double", + + "ValueType", + "Enum", + "Nullable`1", + + "Object", + "String", + "Array", + "MulticastDelegate", + + "RuntimeTypeHandle", + "RuntimeMethodHandle", + "RuntimeFieldHandle", + }; + + MetadataType[] _wellKnownTypes = new MetadataType[s_wellKnownTypeNames.Length]; + + EcmaModule _systemModule; + + Dictionary _modules = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public TestTypeSystemContext(TargetArchitecture arch) + : base(new TargetDetails(arch)) + { + } + + public override MetadataType GetWellKnownType(WellKnownType wellKnownType) + { + return _wellKnownTypes[(int)wellKnownType - 1]; + } + + public void SetSystemModule(EcmaModule systemModule) + { + _systemModule = systemModule; + + // Sanity check the name table + Debug.Assert(s_wellKnownTypeNames[(int)WellKnownType.MulticastDelegate - 1] == "MulticastDelegate"); + + // Initialize all well known types - it will save us from checking the name for each loaded type + for (int typeIndex = 0; typeIndex < _wellKnownTypes.Length; typeIndex++) + { + MetadataType type = _systemModule.GetType("System", s_wellKnownTypeNames[typeIndex]); + type.SetWellKnownType((WellKnownType)(typeIndex + 1)); + _wellKnownTypes[typeIndex] = type; + } + } + + public EcmaModule GetModuleForSimpleName(string simpleName) + { + EcmaModule existingModule; + if (_modules.TryGetValue(simpleName, out existingModule)) + return existingModule; + + return CreateModuleForSimpleName(simpleName); + } + + public EcmaModule CreateModuleForSimpleName(string simpleName) + { + EcmaModule module = new EcmaModule(this, new PEReader(File.OpenRead(simpleName + ".dll"))); + _modules.Add(simpleName, module); + return module; + } + + public override object ResolveAssembly(System.Reflection.AssemblyName name) + { + return GetModuleForSimpleName(name.Name); + } + } +} diff --git a/src/TypeSystem/tests/TypeSystem.Tests.csproj b/src/TypeSystem/tests/TypeSystem.Tests.csproj new file mode 100644 index 00000000000..c4e06dc0eed --- /dev/null +++ b/src/TypeSystem/tests/TypeSystem.Tests.csproj @@ -0,0 +1,45 @@ + + + + + Debug + AnyCPU + {8874CEEC-5594-511B-A44C-5CA9EC1CEB11} + Library + TypeSystem.Tests + TypeSystem.Tests + + + + + + + + + + {dd5b6baa-d41a-4a6e-9e7d-83060f394b10} + TypeSystem + + + {dd5b6baa-d41a-4a6e-9e7d-83060f394b10} + TypeSystem + + + + + false + Content + PreserveNewest + Build;DebugSymbolsProjectOutputGroup + + + + + + + + + + + + diff --git a/src/TypeSystem/tests/project.json b/src/TypeSystem/tests/project.json new file mode 100644 index 00000000000..d0c8706163e --- /dev/null +++ b/src/TypeSystem/tests/project.json @@ -0,0 +1,22 @@ +{ + "dependencies": { + "System.Collections": "4.0.10", + "System.Collections.Concurrent": "4.0.10", + "System.Console": "4.0.0-beta-*", + "System.Diagnostics.Debug": "4.0.10", + "System.Diagnostics.Tracing": "4.0.20", + "System.Linq": "4.0.0", + "System.IO.FileSystem": "4.0.0", + "System.Reflection": "4.0.10", + "System.Runtime": "4.0.20", + "System.Runtime.Extensions": "4.0.10", + "System.Threading": "4.0.10", + "System.Threading.Tasks": "4.0.10", + "xunit": "2.1.0-beta3-*", + "xunit.netcore.extensions": "1.0.0-prerelease-*", + "System.Reflection.Metadata": "1.0.22" + }, + "frameworks": { + "dotnet": {} + } +} \ No newline at end of file