diff --git a/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs b/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs new file mode 100644 index 00000000000..a813d3b2a24 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if NETCORE_RUNTIME + +namespace System.Runtime.CompilerServices +{ + /// + /// A special attribute recognized by CoreCLR that allows to ignore visibility checks for internal APIs. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class IgnoresAccessChecksToAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The assembly name to use. + public IgnoresAccessChecksToAttribute(string assemblyName) + { + AssemblyName = assemblyName; + } + + /// + /// Gets the assembly name to use. + /// + public string AssemblyName { get; } + } +} + +#endif diff --git a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj index 06c9e584da5..047743a0086 100644 --- a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj +++ b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj @@ -76,6 +76,16 @@ + + + + + + true + SilverlightPlatformPublicKey.snk + true + + + false + + + diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs index 97b00fe1d48..09a8fa14a30 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if NETCOREAPP3_1 +#if NETCOREAPP2_1 || NETCOREAPP3_1 using System; using System.Diagnostics.CodeAnalysis; @@ -12,6 +12,12 @@ namespace UnitTests.HighPerformance { + /* ==================================================================== + * NOTE + * ==================================================================== + * All thests here mirror the ones for NullableRef, as the two types + * are the same except for the fact that this wraps a readonly ref. See + * comments in the NullableRef test file for more info on these tests. */ [TestClass] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] public class Test_NullableReadOnlyRefOfT diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs index cc86c5bb3eb..f9f737eca5e 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if NETCOREAPP3_1 +#if NETCOREAPP2_1 || NETCOREAPP3_1 using System; using System.Diagnostics.CodeAnalysis; @@ -23,6 +23,9 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_Ok() int value = 1; var reference = new NullableRef(ref value); + // Create a NullableRef to a local, then validate that the ref + // has value (as in, the ref T is not null), and that the reference + // does match and correctly points to the local variable above. Assert.IsTrue(reference.HasValue); Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value)); @@ -35,6 +38,8 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_Ok() [TestMethod] public void Test_NullableRefOfT_CreateNullableRefOfT_Null() { + // Validate that different methods of creating a nullable ref + // are all correctly reported as not having a valid value. Assert.IsFalse(default(NullableRef).HasValue); Assert.IsFalse(NullableRef.Null.HasValue); @@ -49,6 +54,11 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_Null_Exception() { NullableRef reference = default; + // We try to access the value to trigger the null reference check within the + // NullableRef type. The type should correctly detect that a null reference + // is wrapped, and throw an InvalidOperationException (not a NullReferenceException). + // This is consistent with how Nullable works in the BCL as well (the type that + // is used to represent nullable value types). _ = reference.Value; } @@ -60,6 +70,8 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_ImplicitRefCast() var reference = new Ref(ref value); NullableRef nullableRef = reference; + // Verify that the inplicit Ref ==> NullableRef conversion works properly. + // The value should be available, and the internal ref should remain the same. Assert.IsTrue(nullableRef.HasValue); Assert.IsTrue(Unsafe.AreSame(ref reference.Value, ref nullableRef.Value)); } @@ -71,6 +83,9 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_ExplicitCastOfT() int value = 42; var reference = new NullableRef(ref value); + // Test the value equality by using the implicit T operator for NullableRef. + // As in, just like with normal refs, NullableRef has automatic dereferencing, + // and when cast to T it will dereference the internal ref and return th T value. Assert.AreEqual(value, (int)reference); } @@ -81,6 +96,9 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_ExplicitCastOfT_Exception() { NullableRef invalid = default; + // Same as above, but trying to dereference should throw an InvalidOperationException + // since our NullableRef here wraps a null reference. The value is discarded because + // here we just need to trigger the implicit operator to verify the exception is thrown. _ = (int)invalid; } } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs index 106b6c95892..37d09ce93e8 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs @@ -2,21 +2,29 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace UnitTests.HighPerformance { + /* ================================================================== + * NOTE + * ================================================================== + * All thests here mirror the ones for Ref, as the two types are + * the same except for the fact that this wraps a readonly ref. See + * comments in the Ref test file for more info on these tests. */ [TestClass] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] public class Test_ReadOnlyRefOfT { +#if WINDOWS_UWP [TestCategory("ReadOnlyRefOfT")] [TestMethod] -#if WINDOWS_UWP - public void Test_RefOfT_CreateRefOfT() + public void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfT() { var model = new ReadOnlyFieldOwner(); var reference = new ReadOnlyRef(model, model.Value); @@ -32,14 +40,62 @@ private sealed class ReadOnlyFieldOwner [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Ref readonly access for tests")] public readonly int Value = 1; } + + [Pure] + private static ReadOnlyRef CreateReadOnlyRefFromArray(T[] array) + { + return new ReadOnlyRef(array, array[0]); + } #else - public void Test_RefOfT_CreateRefOfT() + [TestCategory("ReadOnlyRefOfT")] + [TestMethod] + public void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfT() { int value = 1; var reference = new ReadOnlyRef(value); Assert.IsTrue(Unsafe.AreSame(ref value, ref Unsafe.AsRef(reference.Value))); } + + [Pure] + private static ReadOnlyRef CreateReadOnlyRefFromArray(T[] array) + { + return new ReadOnlyRef(array[0]); + } + + [TestCategory("ReadOnlyRefOfT")] + [TestMethod] + public unsafe void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfTFromPointer_Ok() + { + int value = 1; + var reference = new ReadOnlyRef(&value); + + Assert.IsTrue(Unsafe.AreSame(ref value, ref Unsafe.AsRef(reference.Value))); + } + + [TestCategory("ReadOnlyRefOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public unsafe void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfTFrompointer_Fail() + { + var reference = new ReadOnlyRef((void*)0); + + Assert.Fail(); + } #endif + + [TestCategory("ReadOnlyRefOfT")] + [TestMethod] + public void Test_ReadOnlyRefOfT_DangerousGetReferenceAt() + { + int[] array = { 1, 2, 3, 4, 5 }; + ReadOnlyRef reference = CreateReadOnlyRefFromArray(array); + + Assert.IsTrue(Unsafe.AreSame(ref array[0], ref reference.DangerousGetReference())); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref Unsafe.AsRef(reference[3]))); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref Unsafe.AsRef(reference[(IntPtr)3]))); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference.DangerousGetReferenceAt(3))); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference.DangerousGetReferenceAt((IntPtr)3))); + } } } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs index 556649c3cf6..29dc5bfab31 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -13,18 +15,21 @@ namespace UnitTests.HighPerformance [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] public class Test_RefOfT { +#if WINDOWS_UWP [TestCategory("RefOfT")] [TestMethod] -#if WINDOWS_UWP public void Test_RefOfT_CreateRefOfT() { var model = new FieldOwner { Value = 1 }; var reference = new Ref(model, ref model.Value); + // Create a Ref wrapping a ref to a field in an object and validate that + // the returned ref does indeed match one to that field directly. Assert.IsTrue(Unsafe.AreSame(ref model.Value, ref reference.Value)); reference.Value++; + // We increment the ref, and then verify the target field was updated correctly Assert.AreEqual(model.Value, 2); } @@ -36,18 +41,81 @@ private sealed class FieldOwner [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Quick ref access for tests")] public int Value; } + + // Helper method that creates a Ref to the first item of a T[] array, on UWP. In this + // case (portable version) we need to use both the target object, and an interior reference. + [Pure] + private static Ref CreateRefFromArray(T[] array) + { + return new Ref(array, ref array[0]); + } #else + [TestCategory("RefOfT")] + [TestMethod] public void Test_RefOfT_CreateRefOfT() { int value = 1; var reference = new Ref(ref value); + // Same as the UWP version above, but directly wrapping the target ref + Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value)); + + reference.Value++; + + Assert.AreEqual(value, 2); + } + + // Same as above, but here we can directly wrap the target ref + [Pure] + private static Ref CreateRefFromArray(T[] array) + { + return new Ref(ref array[0]); + } + + [TestCategory("RefOfT")] + [TestMethod] + public unsafe void Test_RefOfT_CreateRefOfTFromPointer_Ok() + { + int value = 1; + var reference = new Ref(&value); + + // Same as above, but this time we wrap a raw pointer to a local instead Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value)); reference.Value++; Assert.AreEqual(value, 2); } + + [TestCategory("RefOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public unsafe void Test_RefOfT_CreateRefOfTFrompointer_Fail() + { + var reference = new Ref((void*)0); + + // Creating a Ref from a pointer when T is a managed type (eg. string) is not + // allowed (this is consistent with APIs from the BCL such as Span). So the + // constructor above should always throw an ArgumentException. + Assert.Fail(); + } #endif + + [TestCategory("RefOfT")] + [TestMethod] + public void Test_RefOfT_DangerousGetReferenceAt() + { + int[] array = { 1, 2, 3, 4, 5 }; + Ref reference = CreateRefFromArray(array); + + // We created a Ref pointing to the first item of the target array, so here + // we test a number of ref accesses (both directly to the target ref, as well as + // using the helper indexers to do pointer arithmetic) to validate their results. + // Doing Ref[i] is conceptually eqivalent to doing p[i] on a given T* pointer, + // so here we compare these offsetting operations with refs from the target array. + Assert.IsTrue(Unsafe.AreSame(ref array[0], ref reference.Value)); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference[3])); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference[(IntPtr)3])); + } } } diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index dbb8ef27886..db16d9d9f79 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29230.61 @@ -112,6 +111,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{88C6FFBE-3 ThirdPartyNotices.txt = ThirdPartyNotices.txt EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Private.CoreLib", "System.Private.CoreLib\System.Private.CoreLib.csproj", "{5F66DDCC-2F40-43C1-902E-2C1F887DB650}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Controls.Markdown", "Microsoft.Toolkit.Uwp.UI.Controls.Markdown\Microsoft.Toolkit.Uwp.UI.Controls.Markdown.csproj", "{6FEDF199-B052-49DD-8F3E-2A9224998E0F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design", "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design\Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj", "{67FE47A0-CA93-4680-B770-A0A48C1DBC40}" @@ -851,6 +852,36 @@ Global {804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.ActiveCfg = Release|x86 {804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Build.0 = Release|x86 {804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Deploy.0 = Release|x86 + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM64.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x64.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x64.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x86.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x86.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|Any CPU.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|Any CPU.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM64.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM64.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x64.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x64.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x86.ActiveCfg = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x86.Build.0 = Debug|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|Any CPU.Build.0 = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM.ActiveCfg = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM.Build.0 = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM64.ActiveCfg = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM64.Build.0 = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x64.ActiveCfg = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x64.Build.0 = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x86.ActiveCfg = Release|Any CPU + {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x86.Build.0 = Release|Any CPU {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|ARM.ActiveCfg = Debug|Any CPU