diff --git a/eng/pipelines/runtime-linker-tests.yml b/eng/pipelines/runtime-linker-tests.yml index 314249cd181f4..0dbb5942c7731 100644 --- a/eng/pipelines/runtime-linker-tests.yml +++ b/eng/pipelines/runtime-linker-tests.yml @@ -74,7 +74,7 @@ extends: - linux_x64 jobParameters: testGroup: innerloop - testResultsFormat: 'vstest' + enablePublishTestResults: true timeoutInMinutes: 120 nameSuffix: ILLink_Runtime_Testing condition: diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs index d3baea7569db4..e9c9b52621369 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs @@ -91,6 +91,15 @@ public static MethodIL TryGetIL(EcmaMethod method) return GenerateAccessorBadImageFailure(method); } + // If the non-static method access is for a + // value type, the instance must be byref. + if (kind == UnsafeAccessorKind.Method + && firstArgType.IsValueType + && !firstArgType.IsByRef) + { + return GenerateAccessorBadImageFailure(method); + } + context.TargetType = ValidateTargetType(firstArgType); if (context.TargetType == null) { diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCases/TestSuites.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCases/TestSuites.cs index ee591592b5a14..f0b0246960659 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCases/TestSuites.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCases/TestSuites.cs @@ -44,6 +44,7 @@ public void Reflection (string t) switch (t) { case "TypeHierarchyReflectionWarnings": case "ParametersUsedViaReflection": + case "UnsafeAccessor": Run (t); break; default: diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 2985432222a09..014d06c000b39 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1503,6 +1503,15 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET if (firstArgType.IsNull()) ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + // If the non-static method access is for a + // value type, the instance must be byref. + if (kind == UnsafeAccessorKind::Method + && firstArgType.IsValueType() + && !firstArgType.IsByRef()) + { + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); + } + context.TargetType = ValidateTargetType(firstArgType); context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod; if (!TrySetTargetMethod(context, name.GetUTF8())) diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index 65c50f0041956..5077691409fe0 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -79,6 +79,8 @@ struct UserDataValue private static string _M(string s, ref string sr, in string si) => s; private string _m(string s, ref string sr, in string si) => s; + + public string GetFieldValue() => _f; } [UnsafeAccessor(UnsafeAccessorKind.Constructor)] @@ -119,6 +121,18 @@ public static void Verify_CallCtorValue() Assert.Equal(PrivateArg, local.Value); } + [Fact] + public static void Verify_CallCtorWithEmptyNotNullName() + { + Console.WriteLine($"Running {nameof(Verify_CallCtorWithEmptyNotNullName)}"); + + var local = CallPrivateConstructorWithEmptyName(); + Assert.Equal(nameof(UserDataClass), local.GetType().Name); + + [UnsafeAccessor(UnsafeAccessorKind.Constructor, Name="")] + extern static UserDataClass CallPrivateConstructorWithEmptyName(); + } + [Fact] public static void Verify_CallCtorAsMethod() { @@ -191,6 +205,10 @@ public static void Verify_AccessFieldValue() UserDataValue local = new(); Assert.Equal(Private, GetPrivateField(ref local)); + const string newValue = "__NewValue__"; + GetPrivateField(ref local) = newValue; + Assert.Equal(newValue, local.GetFieldValue()); + [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] extern static ref string GetPrivateField(ref UserDataValue d); } @@ -355,6 +373,50 @@ public static void Verify_ManagedUnmanagedFunctionPointersDontMatch() extern static string CallManagedMethod(UserDataClass d, delegate* unmanaged[Cdecl] fptr); } + class InheritanceBase + { + private static string OnBase() => nameof(OnBase); + private static string FieldOnBase = nameof(FieldOnBase); + } + + class InheritanceDerived : InheritanceBase + { + private static string OnDerived() => nameof(OnDerived); + private static string FieldOnDerived = nameof(FieldOnDerived); + } + + [Fact] + public static void Verify_InheritanceMethodResolution() + { + Console.WriteLine($"Running {nameof(Verify_InheritanceMethodResolution)}"); + + var instance = new InheritanceDerived(); + Assert.Throws(() => OnBase(instance)); + Assert.Equal(nameof(OnDerived), OnDerived(instance)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(OnBase))] + extern static string OnBase(InheritanceDerived i); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(OnDerived))] + extern static string OnDerived(InheritanceDerived i); + } + + [Fact] + public static void Verify_InheritanceFieldResolution() + { + Console.WriteLine($"Running {nameof(Verify_InheritanceFieldResolution)}"); + + var instance = new InheritanceDerived(); + Assert.Throws(() => FieldOnBase(instance)); + Assert.Equal(nameof(FieldOnDerived), FieldOnDerived(instance)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = nameof(FieldOnBase))] + extern static ref string FieldOnBase(InheritanceDerived i); + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = nameof(FieldOnDerived))] + extern static ref string FieldOnDerived(InheritanceDerived i); + } + [Fact] public static void Verify_InvalidTargetUnsafeAccessor() { @@ -389,6 +451,13 @@ public static void Verify_InvalidTargetUnsafeAccessor() AssertExtensions.ThrowsMissingMemberException( isNativeAot ? null : UserDataClass.MethodPointerName, () => CallPointerMethod(null, null)); + AssertExtensions.ThrowsMissingMemberException( + isNativeAot ? null : UserDataClass.StaticMethodName, + () => { string sr = string.Empty; StaticMethodWithDifferentReturnType(null, null, ref sr, string.Empty); }); + + AssertExtensions.ThrowsMissingMemberException( + isNativeAot ? null : UserDataClass.StaticMethodName, + () => { string sr = string.Empty; StaticMethodWithDifferentReturnType(null, null, ref sr, string.Empty); }); [UnsafeAccessor(UnsafeAccessorKind.Method, Name=DoesNotExist)] extern static void MethodNotFound(UserDataClass d); @@ -412,6 +481,8 @@ public static void Verify_InvalidTargetUnsafeAccessor() [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.MethodPointerName)] extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall] fptr); + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodName)] + extern static int StaticMethodWithDifferentReturnType(UserDataClass d, string s, ref string sr, in string si); } [Fact] @@ -467,6 +538,12 @@ public static void Verify_InvalidUseUnsafeAccessor() Assert.Throws(() => new Invalid().NonStatic(string.Empty)); Assert.Throws(() => Invalid.CallToString(string.Empty)); Assert.Throws(() => Invalid.CallToString(string.Empty)); + Assert.Throws(() => + { + string str = string.Empty; + UserDataValue local = new(); + InvokeMethodOnValueWithoutRef(local, null, ref str, str); + }); [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)] extern static string FieldReturnMustBeByRefClass(UserDataClass d); @@ -503,5 +580,8 @@ public static void Verify_InvalidUseUnsafeAccessor() [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))] extern static string LookUpFailsOnFunctionPointers(delegate* fptr); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = UserDataValue.MethodName)] + extern static string InvokeMethodOnValueWithoutRef(UserDataValue target, string s, ref string sr, in string si); } } diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index ab3619c5958d1..2ad9c4d13329e 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -36,6 +36,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection.Runtime.TypeParsing; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using ILLink.Shared; using ILLink.Shared.TrimAnalysis; @@ -127,6 +128,7 @@ internal DynamicallyAccessedMembersTypeHierarchy DynamicallyAccessedMembersTypeH DependencyKind.ReferencedBySpecialAttribute, DependencyKind.TypePreserve, DependencyKind.XmlDescriptor, + DependencyKind.UnsafeAccessorTarget, }; static readonly DependencyKind[] _typeReasons = new DependencyKind[] { @@ -211,6 +213,7 @@ internal DynamicallyAccessedMembersTypeHierarchy DynamicallyAccessedMembersTypeH DependencyKind.FieldMarshalSpec, DependencyKind.ReturnTypeMarshalSpec, DependencyKind.XmlDescriptor, + DependencyKind.UnsafeAccessorTarget, }; #endif @@ -1870,6 +1873,7 @@ void ProcessAnalysisAnnotationsForField (FieldDefinition field, DependencyKind d case DependencyKind.DynamicallyAccessedMember: case DependencyKind.InteropMethodDependency: case DependencyKind.Ldtoken: + case DependencyKind.UnsafeAccessorTarget: if (isReflectionAccessCoveredByDAM = Annotations.FlowAnnotations.ShouldWarnWhenAccessedForReflection (field)) Context.LogWarning (origin, DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, field.GetDisplayName ()); @@ -3264,6 +3268,10 @@ protected virtual void ProcessMethod (MethodDefinition method, in DependencyInfo ProcessInteropMethod (method); } + if (!method.HasBody || method.Body.CodeSize == 0) { + ProcessUnsafeAccessorMethod (method); + } + if (ShouldParseMethodBody (method)) MarkMethodBody (method.Body); @@ -3494,6 +3502,11 @@ void ProcessInteropMethod (MethodDefinition method) #pragma warning restore RS0030 } + void ProcessUnsafeAccessorMethod (MethodDefinition method) + { + (new UnsafeAccessorMarker (Context, this)).ProcessUnsafeAccessorMethod (method); + } + protected virtual bool ShouldParseMethodBody (MethodDefinition method) { if (!method.HasBody) diff --git a/src/tools/illink/src/linker/Linker.Steps/UnsafeAccessorMarker.cs b/src/tools/illink/src/linker/Linker.Steps/UnsafeAccessorMarker.cs new file mode 100644 index 0000000000000..d6155b8a50b5e --- /dev/null +++ b/src/tools/illink/src/linker/Linker.Steps/UnsafeAccessorMarker.cs @@ -0,0 +1,141 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Mono.Cecil; + +namespace Mono.Linker.Steps +{ + // This class only handles static methods (all the unsafe accessors should be static) + // so there's no problem with forgetting the implicit "this". +#pragma warning disable RS0030 // MethodReference.Parameters is banned + + readonly struct UnsafeAccessorMarker (LinkContext context, MarkStep markStep) + { + readonly LinkContext _context = context; + readonly MarkStep _markStep = markStep; + + // We don't perform method overload resolution based on list of parameters (or return type) for now + // Mono.Cecil's method resolution is problematic and has bugs. It's also not extensible + // and we would need that to correctly implement the desired behavior around custom modifiers. So for now we decided to not + // duplicate the logic to tweak it and will just mark entire method groups. + + public void ProcessUnsafeAccessorMethod (MethodDefinition method) + { + if (!method.IsStatic || !method.HasCustomAttributes) + return; + + foreach (CustomAttribute customAttribute in method.CustomAttributes) { + if (customAttribute.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.UnsafeAccessorAttribute") { + if (customAttribute.HasConstructorArguments && customAttribute.ConstructorArguments[0].Value is int kindValue) { + UnsafeAccessorKind kind = (UnsafeAccessorKind) kindValue; + string? name = null; + if (customAttribute.HasProperties) { + foreach (CustomAttributeNamedArgument prop in customAttribute.Properties) { + if (prop.Name == "Name") { + name = prop.Argument.Value as string; + break; + } + } + } + + switch (kind) { + case UnsafeAccessorKind.Constructor: + ProcessConstructorAccessor (method, name); + break; + case UnsafeAccessorKind.StaticMethod: + ProcessMethodAccessor (method, name, isStatic: true); + break; + case UnsafeAccessorKind.Method: + ProcessMethodAccessor (method, name, isStatic: false); + break; + case UnsafeAccessorKind.StaticField: + ProcessFieldAccessor (method, name, isStatic: true); + break; + case UnsafeAccessorKind.Field: + ProcessFieldAccessor (method, name, isStatic: false); + break; + default: + break; + } + + // Intentionally only process the first such attribute + // if there's more than one runtime will fail on it anyway. + break; + } + } + } + } + + void ProcessConstructorAccessor (MethodDefinition method, string? name) + { + // A return type is required for a constructor, otherwise + // we don't know the type to construct. + // Types should not be parameterized (that is, by-ref). + // The name is defined by the runtime and should be empty. + if (method.ReturnsVoid () || method.ReturnType.IsByRefOrPointer () || !string.IsNullOrEmpty (name)) + return; + + if (_context.TryResolve (method.ReturnType) is not TypeDefinition targetType) + return; + + foreach (MethodDefinition targetMethod in targetType.Methods) { + if (!targetMethod.IsConstructor || targetMethod.IsStatic) + continue; + + _markStep.MarkMethodVisibleToReflection (targetMethod, new DependencyInfo (DependencyKind.UnsafeAccessorTarget, method), new MessageOrigin (method)); + } + } + + void ProcessMethodAccessor (MethodDefinition method, string? name, bool isStatic) + { + // Method access requires a target type. + if (method.Parameters.Count == 0) + return; + + if (string.IsNullOrEmpty (name)) + name = method.Name; + + TypeReference targetTypeReference = method.Parameters[0].ParameterType; + if (_context.TryResolve (targetTypeReference) is not TypeDefinition targetType) + return; + + if (!isStatic && targetType.IsValueType && !targetTypeReference.IsByReference) + return; + + foreach (MethodDefinition targetMethod in targetType.Methods) { + if (targetMethod.Name != name || targetMethod.IsStatic != isStatic) + continue; + + _markStep.MarkMethodVisibleToReflection (targetMethod, new DependencyInfo (DependencyKind.UnsafeAccessorTarget, method), new MessageOrigin (method)); + } + } + + void ProcessFieldAccessor (MethodDefinition method, string? name, bool isStatic) + { + // Field access requires exactly one parameter + if (method.Parameters.Count != 1) + return; + + if (string.IsNullOrEmpty (name)) + name = method.Name; + + if (!method.ReturnType.IsByReference) + return; + + TypeReference targetTypeReference = method.Parameters[0].ParameterType; + if (_context.TryResolve (targetTypeReference) is not TypeDefinition targetType) + return; + + if (!isStatic && targetType.IsValueType && !targetTypeReference.IsByReference) + return; + + foreach (FieldDefinition targetField in targetType.Fields) { + if (targetField.Name != name || targetField.IsStatic != isStatic) + continue; + + _markStep.MarkFieldVisibleToReflection (targetField, new DependencyInfo (DependencyKind.UnsafeAccessorTarget, method), new MessageOrigin (method)); + } + } + } +} diff --git a/src/tools/illink/src/linker/Linker/DependencyInfo.cs b/src/tools/illink/src/linker/Linker/DependencyInfo.cs index 1d147c9165755..7eb0681c5a934 100644 --- a/src/tools/illink/src/linker/Linker/DependencyInfo.cs +++ b/src/tools/illink/src/linker/Linker/DependencyInfo.cs @@ -143,6 +143,8 @@ public enum DependencyKind PreservedOperator = 87, // operator method preserved on a type DynamicallyAccessedMemberOnType = 88, // type with DynamicallyAccessedMembers annotations (including those inherited from base types and interfaces) + + UnsafeAccessorTarget = 89, // the member is referenced via UnsafeAccessor attribute } public readonly struct DependencyInfo : IEquatable diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index d589a6f3a0457..3bf7964cd56bb 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -16,6 +16,12 @@ public Task AnnotatedMembersAccessedViaReflection () return RunTest (nameof (AnnotatedMembersAccessedViaReflection)); } + [Fact] + public Task AnnotatedMembersAccessedViaUnsafeAccessor () + { + return RunTest (); + } + [Fact] public Task ApplyTypeAnnotations () { @@ -296,4 +302,4 @@ public Task XmlAnnotations () return RunTest (nameof (XmlAnnotations)); } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/ReflectionTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/ReflectionTests.g.cs index b729ec4a892f7..70539f229f2ee 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/ReflectionTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/ReflectionTests.g.cs @@ -73,6 +73,12 @@ public Task UnderlyingSystemType () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task UnsafeAccessor () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task UsedViaReflectionIntegrationTest () { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaUnsafeAccessor.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaUnsafeAccessor.cs new file mode 100644 index 0000000000000..9b8cd8959651b --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaUnsafeAccessor.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [SkipKeptItemsValidation] + [ExpectedNoWarnings] + class AnnotatedMembersAccessedViaUnsafeAccessor + { + // NativeAOT has basically the best behavior in these cases - it produces the same warnings + // as if the accessor has a direct access to the target member. So if the accessor is correctly annotated + // there will be no warnings. + // + // Trimmer is the worst - due to the fact that we mark all methods with a given name, we can't compare + // annotations and thus we need to warn on all annotated methods. In addition the accesses are modeled + // as reflection accesses so the warning codes are "Reflection" codes. When/If we implement + // correct target resolution, we should be able to emulate the behavior of NativeAOT + // + // Analyzer doesn't warn at all in these cases - analyzer simply can't resolve targets (at least sometimes) + // and so for now we're not doing anything with UnsafeAccessor in the analyzer. + + public static void Main () + { + MethodWithAnnotatedParameter (null, null); + StaticMethodWithAnnotatedReturnValue (null); + VirtualMethodWithAnnotatedReturnValue (null); + AnnotatedField (null); + + MethodWithAnnotationMismatch (null, null); + FieldWithAnnotationMismatch (null); + } + + class Target + { + private static void MethodWithAnnotatedParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type type) { } + + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + private static Type StaticMethodWithAnnotatedReturnValue () => null; + + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + protected virtual Type VirtualMethodWithAnnotatedReturnValue () => null; + + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + private static Type AnnotatedField; + + private static void MethodWithAnnotationMismatch ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type type) { } + + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + private static Type FieldWithAnnotationMismatch; + } + + [ExpectedWarning ("IL2111", ProducedBy = Tool.Trimmer)] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void MethodWithAnnotatedParameter (Target target, [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type type); + + // No warning - reflection access to a static method with annotated return value is not a problem + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + extern static Type StaticMethodWithAnnotatedReturnValue (Target target); + + [ExpectedWarning ("IL2111", ProducedBy = Tool.Trimmer)] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + extern static Type VirtualMethodWithAnnotatedReturnValue (Target target); + + [ExpectedWarning ("IL2110", ProducedBy = Tool.Trimmer)] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + extern static ref Type AnnotatedField (Target target); + + [ExpectedWarning ("IL2111", ProducedBy = Tool.Trimmer)] + [ExpectedWarning ("IL2067", ProducedBy = Tool.NativeAot)] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void MethodWithAnnotationMismatch (Target target, [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] Type type); + + [ExpectedWarning ("IL2110", ProducedBy = Tool.Trimmer)] + [ExpectedWarning ("IL2078", ProducedBy = Tool.NativeAot)] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] + extern static ref Type FieldWithAnnotationMismatch (Target target); + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs new file mode 100644 index 0000000000000..2d9aadff272fc --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/UnsafeAccessor.cs @@ -0,0 +1,906 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Reflection +{ + [SetupCompileArgument ("/unsafe")] + [ExpectedNoWarnings] + unsafe class UnsafeAccessor + { + public static void Main () + { + ConstructorAccess.Test (); + StaticMethodAccess.Test (); + InstanceMethodAccess.Test (); + StaticFieldAccess.Test (); + InstanceFieldAccess.Test (); + InheritanceTest.Test (); + } + + // Trimmer doesn't use method overload resolution for UnsafeAccessor and instead marks entire method groups (by name) + // NativeAOT on the other hand performs exact resolution + // This difference is currently by design - Mono.Cecil's method resolution is problematic and has bugs. It's also not extensible + // and we would need that to correctly implement the desired behavior around custom modifiers. So for now we decided to not + // duplicate the logic to tweak it and will just mark entire method groups. + + class ConstructorAccess + { + [Kept] + class DefaultConstructor + { + [Kept] + class DefaultConstructorTarget + { + [Kept] + private DefaultConstructorTarget () { } + + [Kept (By = Tool.Trimmer)] + private DefaultConstructorTarget (int i) { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static DefaultConstructorTarget InvokeDefaultConstructor (); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // This should not resolve since Name is not allowed for Constructor + [UnsafeAccessor (UnsafeAccessorKind.Constructor, Name = ".ctor")] + extern static DefaultConstructorTarget InvokeWithName (int i); + + [Kept] + class UseLocalFunction + { + [Kept] + private UseLocalFunction () { } + + [Kept] + public static void Test () + { + InvokeDefaultConstructorLocal (); + + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static UseLocalFunction InvokeDefaultConstructorLocal (); + } + } + + [Kept] + class AccessCtorAsMethod + { + [Kept] + private AccessCtorAsMethod () { } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method, Name = ".ctor")] + extern static void CallPrivateConstructor (AccessCtorAsMethod _this); + + [Kept] + public static void Test () + { + var instance = (AccessCtorAsMethod) RuntimeHelpers.GetUninitializedObject (typeof (AccessCtorAsMethod)); + CallPrivateConstructor (instance); + } + } + + [Kept] + public static void Test () + { + InvokeDefaultConstructor (); + InvokeWithName (0); + UseLocalFunction.Test (); + AccessCtorAsMethod.Test (); + } + } + + [Kept] + [KeptMember (".ctor()")] + class ConstructorWithParameter + { + [Kept] + class ConstructorWithParameterTarget + { + [Kept (By = Tool.Trimmer)] + private ConstructorWithParameterTarget () { } + + [Kept] + private ConstructorWithParameterTarget (int i) { } + + [Kept] + protected ConstructorWithParameterTarget (string s) { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static ConstructorWithParameterTarget InvokeConstructorWithParameter (int i); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Constructor, Name = "")] + extern static ConstructorWithParameterTarget InvokeWithEmptyName (string s); + + // Validate that instance methods are ignored + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern ConstructorWithParameterTarget InvokeOnInstance (); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // Test that invoking non-existent constructor doesn't break anything + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static ConstructorWithParameterTarget InvokeNonExistent (double d); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // Test that invoke without a return type is ignored + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static void InvokeWithoutReturnType (); + + [Kept] + public static void Test () + { + InvokeConstructorWithParameter (42); + InvokeWithEmptyName (null); + (new ConstructorWithParameter ()).InvokeOnInstance (); + InvokeNonExistent (0); + InvokeWithoutReturnType (); + } + } + + [Kept] + class ConstructorOnValueType + { + [Kept] + struct ConstructorOnValueTypeTarget + { + [Kept] + public ConstructorOnValueTypeTarget () { } + + [Kept] + public ConstructorOnValueTypeTarget (int i) { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static ConstructorOnValueTypeTarget InvokeDefaultConstructor (); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void InvokeConstructorAsMethod (ref ConstructorOnValueTypeTarget target, int i); + + [Kept] + struct ConstructorAsMethodOnValueWithoutRefTarget + { + [Kept] // This is actually always kept by RuntimeHelpers.GetUninitializedObject - annotation + public ConstructorAsMethodOnValueWithoutRefTarget (int i) { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void InvokeConstructorAsMethodWithoutRef (ConstructorAsMethodOnValueWithoutRefTarget target, int i); + + [Kept] + public static void Test () + { + InvokeDefaultConstructor (); + + var instance = (ConstructorOnValueTypeTarget) RuntimeHelpers.GetUninitializedObject (typeof (ConstructorOnValueTypeTarget)); + InvokeConstructorAsMethod (ref instance, 0); + + var instanceWithoutRef = (ConstructorAsMethodOnValueWithoutRefTarget) RuntimeHelpers.GetUninitializedObject (typeof (ConstructorAsMethodOnValueWithoutRefTarget)); + InvokeConstructorAsMethodWithoutRef (instanceWithoutRef, 0); + } + } + + [Kept] + public static void Test () + { + DefaultConstructor.Test (); + ConstructorWithParameter.Test (); + ConstructorOnValueType.Test (); + } + } + + class StaticMethodAccess + { + [Kept] + class MethodWithoutParameters + { + [Kept] + class MethodWithoutParametersTarget + { + [Kept] + private static void TargetMethod () { } + + [Kept] + internal static void SecondTarget () { } + + private void InstanceTarget () { } + + private static void DifferentName () { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void TargetMethod (MethodWithoutParametersTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod, Name = nameof (MethodWithoutParametersTarget.SecondTarget))] + extern static void SpecifyNameParameter (MethodWithoutParametersTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // StaticMethod kind doesn't work on instance methods + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void InstanceTarget (MethodWithoutParametersTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod, Name = "NonExistingName")] + extern static void DifferentName (MethodWithoutParametersTarget target); + + [Kept] + public static void Test () + { + TargetMethod (null); + SpecifyNameParameter (null); + InstanceTarget (null); + DifferentName (null); + } + } + + [Kept] + class MethodWithParameter + { + [Kept] + class MethodWithParameterTarget + { + [Kept (By = Tool.Trimmer)] + private static void MethodWithOverloads () { } + + [Kept] + private static void MethodWithOverloads (int i) { } + + [Kept (By = Tool.Trimmer)] + private static void MethodWithGenericAndSpecificOverload (object o) { } + + [Kept] + private static void MethodWithGenericAndSpecificOverload (string o) { } + + [Kept (By = Tool.Trimmer)] + private static void MethodWithThreeInheritanceOverloads (SuperBase o) { } + [Kept] + private static void MethodWithThreeInheritanceOverloads (Base o) { } + [Kept (By = Tool.Trimmer)] + private static void MethodWithThreeInheritanceOverloads (Derived o) { } + + [Kept (By = Tool.Trimmer)] + private static void MethodWithImperfectMatch (SuperBase o) { } + [Kept (By = Tool.Trimmer)] + private static void MethodWithImperfectMatch (Derived o) { } + + [Kept] + private static string MoreParameters (string s, ref string sr, in string si) => s; + + [Kept (By = Tool.Trimmer)] + private static string MoreParametersWithReturnValueMismatch (string s, ref string sr, in string si) => s; + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void MethodWithOverloads (MethodWithParameterTarget target, int i); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void MethodWithGenericAndSpecificOverload (MethodWithParameterTarget target, string s); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void MethodWithThreeInheritanceOverloads (MethodWithParameterTarget target, Base o); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void MethodWithImperfectMatch (MethodWithParameterTarget target, Base o); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static string MoreParameters (MethodWithParameterTarget target, string s, ref string src, in string si); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static int MoreParametersWithReturnValueMismatch (MethodWithParameterTarget target, string s, ref string src, in string si); + + [Kept] + public static void Test () + { + MethodWithOverloads (null, 0); + MethodWithGenericAndSpecificOverload (null, null); + MethodWithThreeInheritanceOverloads (null, null); + MethodWithImperfectMatch (null, null); + + string sr = string.Empty; + MoreParameters (null, null, ref sr, string.Empty); + MoreParametersWithReturnValueMismatch (null, null, ref sr, string.Empty); + } + } + + [Kept] + class MethodOnValueType + { + [Kept] + struct MethodOnValueTypeTarget + { + [Kept] + private static void Method () { } + + [Kept] + private static void MethodCalledWithoutRef () { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod, Name = "Method")] + extern static void InvokeMethod (ref MethodOnValueTypeTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod, Name = "MethodCalledWithoutRef")] + extern static void InvokeMethodWithoutRef (MethodOnValueTypeTarget target); + + [Kept] + public static void Test () + { + MethodOnValueTypeTarget instance = new MethodOnValueTypeTarget (); + InvokeMethod (ref instance); + InvokeMethodWithoutRef (instance); + } + } + + [Kept] + public static void Test () + { + MethodWithoutParameters.Test (); + MethodWithParameter.Test (); + MethodOnValueType.Test (); + } + } + + class InstanceMethodAccess + { + [Kept] + class MethodWithoutParameters + { + [Kept] + [KeptMember (".ctor()")] + class MethodWithoutParametersTarget + { + [Kept] + private void TargetMethod () { } + + [Kept] + internal void SecondTarget () { } + + private static void StaticTarget () { } + + private void DifferentName () { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void TargetMethod (MethodWithoutParametersTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method, Name = nameof (MethodWithoutParametersTarget.SecondTarget))] + extern static void SpecifyNameParameter (MethodWithoutParametersTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // Method kind doesn't work on static methods + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void StaticTarget (MethodWithoutParametersTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method, Name = "NonExistingName")] + extern static void DifferentName (MethodWithoutParametersTarget target); + + [Kept] + public static void Test () + { + var instance = new MethodWithoutParametersTarget (); + TargetMethod (null); + SpecifyNameParameter (null); + StaticTarget (null); + DifferentName (null); + } + } + + [Kept] + class MethodWithParameter + { + [Kept] + [KeptMember (".ctor()")] + class MethodWithParameterTarget + { + [Kept (By = Tool.Trimmer)] + private void MethodWithOverloads () { } + + [Kept] + private void MethodWithOverloads (int i) { } + + [Kept (By = Tool.Trimmer)] + private void MethodWithGenericAndSpecificOverload (object o) { } + + [Kept] + private void MethodWithGenericAndSpecificOverload (string o) { } + + [Kept (By = Tool.Trimmer)] + private void MethodWithThreeInheritanceOverloads (SuperBase o) { } + [Kept] + private void MethodWithThreeInheritanceOverloads (Base o) { } + [Kept (By = Tool.Trimmer)] + private void MethodWithThreeInheritanceOverloads (Derived o) { } + + [Kept (By = Tool.Trimmer)] + private void MethodWithImperfectMatch (SuperBase o) { } + [Kept (By = Tool.Trimmer)] + private void MethodWithImperfectMatch (Derived o) { } + + [Kept] + private string MoreParameters (string s, ref string sr, in string si) => s; + + [Kept (By = Tool.Trimmer)] + private string MoreParametersWithReturnValueMismatch (string s, ref string sr, in string si) => s; + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void MethodWithOverloads (MethodWithParameterTarget target, int i); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void MethodWithGenericAndSpecificOverload (MethodWithParameterTarget target, string s); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void MethodWithThreeInheritanceOverloads (MethodWithParameterTarget target, Base o); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void MethodWithImperfectMatch (MethodWithParameterTarget target, Base o); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static string MoreParameters (MethodWithParameterTarget target, string s, ref string src, in string si); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static int MoreParametersWithReturnValueMismatch (MethodWithParameterTarget target, string s, ref string src, in string si); + + [Kept] + public static void Test () + { + var instance = new MethodWithParameterTarget (); + + MethodWithOverloads (null, 0); + MethodWithGenericAndSpecificOverload (null, null); + MethodWithThreeInheritanceOverloads (null, null); + MethodWithImperfectMatch (null, null); + + string sr = string.Empty; + MoreParameters (null, null, ref sr, string.Empty); + MoreParametersWithReturnValueMismatch (null, null, ref sr, string.Empty); + } + } + + [Kept] + class CustomModifiersTest + { + [Kept] + class CustomModifiersTestTarget + { + [Kept (By = Tool.Trimmer)] + private static string _Ambiguous (delegate* unmanaged[Cdecl, MemberFunction] fp) => nameof (CallConvCdecl); + + [Kept] + private static string _Ambiguous (delegate* unmanaged[Stdcall, MemberFunction] fp) => nameof (CallConvStdcall); + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod, Name = "_Ambiguous")] + extern static string CallPrivateMethod (CustomModifiersTestTarget d, delegate* unmanaged[Stdcall, MemberFunction] fp); + + [Kept] + public static void Test () + { + CallPrivateMethod (null, null); + } + } + + [Kept] + class MethodOnValueType + { + [Kept] + struct MethodOnValueTypeTarget + { + [Kept] + private void Method () { } + + private void MethodCalledWithoutRef () { } + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method, Name = "Method")] + extern static void InvokeMethod (ref MethodOnValueTypeTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method, Name = "MethodCalledWithoutRef")] + extern static void InvokeMethodWithoutRef (MethodOnValueTypeTarget target); + + [Kept] + public static void Test () + { + MethodOnValueTypeTarget instance = new MethodOnValueTypeTarget (); + InvokeMethod (ref instance); + InvokeMethodWithoutRef (instance); + } + } + + [Kept] + public static void Test () + { + MethodWithoutParameters.Test (); + MethodWithParameter.Test (); + CustomModifiersTest.Test (); + MethodOnValueType.Test (); + } + } + + // We currently don't track fields in NativeAOT testing infra + [Kept] + class StaticFieldAccess + { + [Kept] + [KeptMember (".ctor()")] + class StaticFieldTarget + { + [Kept (By = Tool.Trimmer)] + private static int Field; + + [Kept (By = Tool.Trimmer)] + private static int FieldWithDifferentType; + + private static int FieldWithVoidType; + + [Kept (By = Tool.Trimmer)] + private static int FieldByName; + + private static int FieldWithParameters; + + private int InstanceField; + + private static int ExistingField; + + private static string FieldWithoutRef; + + [Kept (By = Tool.Trimmer)] + private static string FieldWithRef; + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static ref int Field (StaticFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // Verify that declaring more parameters means the accessor is ignored + [UnsafeAccessor (UnsafeAccessorKind.StaticField, Name = "FieldWithParameters")] + extern static ref int FieldWithParameters (StaticFieldTarget target, int i); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // Verify that access the field with different type still marks it (same as method overload resolution problems) + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static ref string FieldWithDifferentType (StaticFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + // Verify that access the field with different type still marks it (same as method overload resolution problems) + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static void FieldWithVoidType (StaticFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField, Name = "FieldByName")] + extern static ref int FieldByName (StaticFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField, Name = "InstanceField")] + extern static ref int InstanceFieldAsStaticField (StaticFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static ref int NonExistentField (StaticFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static string FieldWithoutRef (StaticFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static ref string FieldWithRef (StaticFieldTarget target); + + [Kept] + class FieldOnValueType + { + [Kept (By = Tool.Trimmer)] + struct Target + { + [Kept (By = Tool.Trimmer)] + private static int Field; + + [Kept (By = Tool.Trimmer)] + private static int FieldWithoutRefOnThis; + + private static int FieldWithoutRefOnReturn; + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static ref int Field (ref Target target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static ref int FieldWithoutRefOnThis (Target target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.StaticField)] + extern static int FieldWithoutRefOnReturn (ref Target target); + + [Kept] + public static void Test () + { + Target target = new Target (); + Field (ref target); + FieldWithoutRefOnThis (target); + FieldWithoutRefOnReturn (ref target); + } + } + + [Kept] + public static void Test () + { + Field (null); + FieldWithParameters (null, 0); + FieldWithDifferentType (null); + FieldWithVoidType (null); + FieldByName (null); + NonExistentField (null); + FieldWithoutRef (null); + FieldWithRef (null); + + StaticFieldTarget target = new StaticFieldTarget (); + InstanceFieldAsStaticField (target); + + FieldOnValueType.Test (); + } + } + + [Kept] + class InstanceFieldAccess + { + [Kept] + [KeptMember (".ctor()")] + class InstanceFieldTarget + { + [Kept (By = Tool.Trimmer)] + private int Field; + + private static int StaticField; + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field)] + extern static ref int Field (InstanceFieldTarget target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field, Name = "StaticField")] + extern static ref int StaticFieldAsInstanceField (InstanceFieldTarget target); + + [Kept] + class FieldOnValueType + { + [Kept (By = Tool.Trimmer)] + [StructLayout (LayoutKind.Auto)] // Otherwise trimmer will keep all the fields + struct Target + { + [Kept (By = Tool.Trimmer)] + private int Field; + + private int FieldWithoutRefOnThis; + + private int FieldWithoutRefOnReturn; + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field)] + extern static ref int Field (ref Target target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field)] + extern static ref int FieldWithoutRefOnThis (Target target); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field)] + extern static int FieldWithoutRefOnReturn (ref Target target); + + [Kept] + public static void Test () + { + Target target = new Target (); + Field (ref target); + FieldWithoutRefOnThis (target); + FieldWithoutRefOnReturn (ref target); + } + } + + [Kept] + public static void Test () + { + InstanceFieldTarget target = new InstanceFieldTarget (); + Field (target); + StaticFieldAsInstanceField (target); + FieldOnValueType.Test (); + } + } + + [Kept] + class InheritanceTest + { + [Kept] + [KeptMember (".ctor()")] + class InheritanceTargetBase + { + private void OnBase () { } + + private void OnBoth () { } + + public void PublicOnBase () { } + + public void PublicOnBoth () { } + + private int FieldOnBase; + + public int PublicFieldOnBase; + } + + [Kept] + [KeptMember (".ctor()")] + [KeptBaseType (typeof (InheritanceTargetBase))] + class InheritanceTargetDerived : InheritanceTargetBase + { + [Kept] + private void OnDerived () { } + + [Kept (By = Tool.Trimmer)] + private void OnBoth (string s) { } + + [Kept (By = Tool.Trimmer)] + public void PublicOnBoth (string s) { } + + [Kept (By = Tool.Trimmer)] + private int FieldOnDerived; + } + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void OnBase (InheritanceTargetDerived t); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void OnDerived (InheritanceTargetDerived t); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void OnBoth (InheritanceTargetDerived t); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void PublicOnBase (InheritanceTargetDerived t); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Method)] + extern static void PublicOnBoth (InheritanceTargetDerived t); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field)] + extern static ref int FieldOnBase (InheritanceTargetDerived t); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field)] + extern static ref int FieldOnDerived (InheritanceTargetDerived t); + + [Kept] + [KeptAttributeAttribute (typeof (UnsafeAccessorAttribute))] + [UnsafeAccessor (UnsafeAccessorKind.Field)] + extern static ref int PublicFieldOnBase (InheritanceTargetDerived t); + + [Kept] + public static void Test () + { + InheritanceTargetDerived derived = new InheritanceTargetDerived (); + OnBase (derived); + OnDerived (derived); + OnBoth (derived); + PublicOnBase (derived); + PublicOnBoth (derived); + + FieldOnBase (derived); + FieldOnDerived (derived); + PublicFieldOnBase (derived); + } + } + + [Kept (By = Tool.Trimmer)] // NativeAOT doesn't preserve base type if it's not used anywhere + class SuperBase { } + + [Kept (By = Tool.Trimmer)] // NativeAOT won't keep the type since it's only used as a parameter type and never instantiated + [KeptBaseType (typeof (SuperBase), By = Tool.Trimmer)] + class Base : SuperBase { } + + [Kept (By = Tool.Trimmer)] // NativeAOT doesn't preserve base type if it's not used anywhere + [KeptBaseType (typeof (Base), By = Tool.Trimmer)] + class Derived : Base { } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresAccessedThrough.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresAccessedThrough.cs index 052f45a811ba0..727463e800801 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresAccessedThrough.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresAccessedThrough.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Mono.Linker.Tests.Cases.Expectations.Assertions; @@ -27,6 +28,7 @@ public static void Main () AccessThroughNewConstraint.TestNewConstraintOnTypeParameterInAnnotatedType (); AccessThroughLdToken.Test (); AccessThroughDelegate.Test (); + AccessThroughUnsafeAccessor.Test (); } class TestType { } @@ -321,5 +323,86 @@ public static void Test () LocalFunctionThroughDelegate (); } } + + class AccessThroughUnsafeAccessor + { + // Analyzer has no support for UnsafeAccessor right now + + class Target + { + [RequiresUnreferencedCode ("--Target..ctor--")] + [RequiresAssemblyFiles ("--Target..ctor--")] + [RequiresDynamicCode ("--Target..ctor--")] + private Target (int i) { } + + [RequiresUnreferencedCode ("--Target.MethodRequires--")] + [RequiresAssemblyFiles ("--Target.MethodRequires--")] + [RequiresDynamicCode ("--Target.MethodRequires--")] + private static void MethodRequires () { } + } + + [ExpectedWarning ("IL2026", "--Target.MethodRequires--", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL3002", "--Target.MethodRequires--", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL3050", "--Target.MethodRequires--", ProducedBy = Tool.NativeAot)] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod)] + extern static void MethodRequires (Target target); + + [ExpectedWarning ("IL2026", "--Target..ctor--", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL3002", "--Target..ctor--", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL3050", "--Target..ctor--", ProducedBy = Tool.NativeAot)] + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static Target Constructor (int i); + + [RequiresUnreferencedCode ("--TargetWitRequires--")] + class TargetWithRequires + { + private TargetWithRequires () { } + + private static void StaticMethod () { } + + private void InstanceMethod () { } + + private static int StaticField; + + private int InstanceField; + } + + [ExpectedWarning ("IL2026", "--TargetWitRequires--", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [UnsafeAccessor (UnsafeAccessorKind.Constructor)] + extern static TargetWithRequires TargetRequiresConstructor (); + + [ExpectedWarning ("IL2026", "--TargetWitRequires--", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [UnsafeAccessor (UnsafeAccessorKind.StaticMethod, Name = "StaticMethod")] + extern static void TargetRequiresStaticMethod (TargetWithRequires target); + + // For trimmer this is a reflection access to an instance method - and as such it must warn (since it's in theory possible + // to invoke the method via reflection on a null instance) + // For NativeAOT this is a direct call to an instance method (there's no reflection involved) and as such it doesn't need to warn + [ExpectedWarning ("IL2026", "--TargetWitRequires--", ProducedBy = Tool.Trimmer)] + [UnsafeAccessor (UnsafeAccessorKind.Method, Name = "InstanceMethod")] + extern static void TargetRequiresInstanceMethod (TargetWithRequires target); + + [ExpectedWarning ("IL2026", "--TargetWitRequires--", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [UnsafeAccessor (UnsafeAccessorKind.StaticField, Name = "StaticField")] + extern static ref int TargetRequiresStaticField (TargetWithRequires target); + + // Access to instance fields never produces these warnings due to RUC on type + [UnsafeAccessor (UnsafeAccessorKind.Field, Name = "InstanceField")] + extern static ref int TargetRequiresInstanceField (TargetWithRequires target); + + public static void Test () + { + MethodRequires (null); + Constructor (0); + + TargetRequiresConstructor (); + + TargetWithRequires targetWithRequires = TargetRequiresConstructor (); + TargetRequiresStaticMethod (targetWithRequires); + TargetRequiresInstanceMethod (targetWithRequires); + TargetRequiresStaticField (targetWithRequires); + TargetRequiresInstanceField (targetWithRequires); + } + } } }