diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs index bfcd255a5ff04d..c54eecb4fe8546 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs @@ -123,9 +123,10 @@ private static MemberDeclarationSyntax PrintGeneratedSource( Attribute(IdentifierName(Constants.DebuggerNonUserCodeAttribute)))))) .WithParameterList(ParameterList(SingletonSeparatedList( Parameter(Identifier(Constants.ArgumentsBuffer)).WithType(PointerType(ParseTypeName(Constants.JSMarshalerArgumentGlobal)))))) - .WithBody(wrapperStatements); + // The modifier above states the contract for callers; the body needs a context of its own. + .WithBody(wrapperStatements.WrapInUnsafeBlock()); - MemberDeclarationSyntax toPrint = containingSyntaxContext.WrapMembersInContainingSyntaxWithUnsafeModifier(wrappperMethod); + MemberDeclarationSyntax toPrint = containingSyntaxContext.WrapMembersInContainingSyntax(wrappperMethod); return toPrint; } @@ -262,9 +263,10 @@ private static NamespaceDeclarationSyntax GenerateRegSource( var ns = NamespaceDeclaration(IdentifierName(generatedNamespace)) .WithMembers( SingletonList( + // None of the members below name a pointer type, so the class needs no 'unsafe' + // modifier. Under the updated memory safety rules one on a type means nothing at + // all, and it never established a context for the members in the first place. ClassDeclaration(initializerClass) - .WithModifiers(TokenList(new SyntaxToken[]{ - Token(SyntaxKind.UnsafeKeyword)})) .WithMembers(List(new[] { field, initializerMethod, method })) .WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList( Attribute(IdentifierName(Constants.CompilerGeneratedAttributeGlobal))) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs index 4e0948df67ca3e..07f9f5f808d5b2 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs @@ -101,13 +101,17 @@ private static MemberDeclarationSyntax PrintGeneratedSource( })) .WithModifiers(StripTriviaFromModifiers(userDeclaredMethod.Modifiers)) .WithParameterList(ParameterList(SeparatedList(stub.SignatureContext.StubParameters))) - .WithBody(stubCode); + // The body is wrapped in an unsafe block rather than relying on an unsafe modifier on the + // containing type, which establishes no context for it under the updated memory safety rules. + // The marshallers call helpers such as Unsafe.SkipInit that are becoming caller-unsafe, so the + // block is emitted even for the shapes whose bodies name no pointer type today. + .WithBody(stubCode.WrapInUnsafeBlock()); FieldDeclarationSyntax sigField = FieldDeclaration(VariableDeclaration(IdentifierName(Constants.JSFunctionSignatureGlobal)) .WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(stub.BindingName))))) .AddModifiers(Token(SyntaxKind.StaticKeyword)); - MemberDeclarationSyntax toPrint = containingSyntaxContext.WrapMembersInContainingSyntaxWithUnsafeModifier(stubMethod, sigField); + MemberDeclarationSyntax toPrint = containingSyntaxContext.WrapMembersInContainingSyntax(stubMethod, sigField); return toPrint; } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs index dc7a702e33cec6..919d2254435aa5 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs @@ -64,102 +64,105 @@ public async Task ValidateGeneratedSourceOutput_AllAnnotatedParameters() "JSImports.g.cs", SourceText.From(""" // - unsafe partial class Basic + partial class Basic { [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Runtime.Versioning.SupportedOSPlatform("browser")] internal static partial void Annotated(object a1, long a2, long a3, global::System.Action a4, global::System.Func a5, global::System.Span a6, global::System.ArraySegment a7, global::System.Threading.Tasks.Task a8, object[] a9, global::System.DateTime a10, global::System.DateTimeOffset a11, global::System.Threading.Tasks.Task a12, global::System.Threading.Tasks.Task a13, global::System.Threading.Tasks.Task a14, global::System.Threading.Tasks.Task a15, global::System.ArraySegment a16) { - if (__signature_Annotated_2034238666 == null) + unsafe { - __signature_Annotated_2034238666 = global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindJSFunction("DoesNotExist", null, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Discard, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Action(), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Function(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Span(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Array(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Single)]); - } + if (__signature_Annotated_2034238666 == null) + { + __signature_Annotated_2034238666 = global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindJSFunction("DoesNotExist", null, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Discard, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Action(), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Function(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Span(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Array(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Single)]); + } - { - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a1_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a2_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a3_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a4_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a5_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a6_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a7_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a8_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a9_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a10_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a11_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a12_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a13_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a14_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a15_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a16_native; - // Setup - Perform required setup. - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_return_native); - ____arg_return_native.Initialize(); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_exception_native); - ____arg_exception_native.Initialize(); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a16_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a15_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a14_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a13_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a12_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a11_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a10_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a9_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a8_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a7_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a6_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a5_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a4_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a3_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a2_native); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a1_native); - // Marshal - Convert managed data to native data. - __a16_native.ToJS(a16); - __a11_native.ToJS(a11); - __a10_native.ToJS(a10); - __a9_native.ToJS(a9); - __a7_native.ToJS(a7); - __a6_native.ToJS(a6); - __a3_native.ToJSBig(a3); - __a2_native.ToJS(a2); - __a1_native.ToJS(a1); { - // PinnedMarshal - Convert managed data to native data that requires the managed data to be pinned. - __a15_native.ToJS(a15, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, long __task_result) => - { - __task_result_arg.ToJSBig(__task_result); - }); - __a14_native.ToJS(a14, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, long __task_result) => - { - __task_result_arg.ToJS(__task_result); - }); - __a13_native.ToJS(a13, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, global::System.DateTimeOffset __task_result) => - { - __task_result_arg.ToJS(__task_result); - }); - __a12_native.ToJS(a12, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, global::System.DateTime __task_result) => - { - __task_result_arg.ToJS(__task_result); - }); - __a8_native.ToJS(a8, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, object __task_result) => - { - __task_result_arg.ToJS(__task_result); - }); - __a5_native.ToJS(a5, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __delegate_arg_arg1, int __delegate_arg1) => + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a1_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a2_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a3_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a4_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a5_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a6_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a7_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a8_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a9_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a10_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a11_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a12_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a13_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a14_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a15_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a16_native; + // Setup - Perform required setup. + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_return_native); + ____arg_return_native.Initialize(); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_exception_native); + ____arg_exception_native.Initialize(); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a16_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a15_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a14_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a13_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a12_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a11_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a10_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a9_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a8_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a7_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a6_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a5_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a4_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a3_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a2_native); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out __a1_native); + // Marshal - Convert managed data to native data. + __a16_native.ToJS(a16); + __a11_native.ToJS(a11); + __a10_native.ToJS(a10); + __a9_native.ToJS(a9); + __a7_native.ToJS(a7); + __a6_native.ToJS(a6); + __a3_native.ToJSBig(a3); + __a2_native.ToJS(a2); + __a1_native.ToJS(a1); { - __delegate_arg_arg1.ToJS(__delegate_arg1); - }); - __a4_native.ToJS(a4); - __InvokeJSFunction(____arg_exception_native, ____arg_return_native, __a1_native, __a2_native, __a3_native, __a4_native, __a5_native, __a6_native, __a7_native, __a8_native, __a9_native, __a10_native, __a11_native, __a12_native, __a13_native, __a14_native, __a15_native, __a16_native); + // PinnedMarshal - Convert managed data to native data that requires the managed data to be pinned. + __a15_native.ToJS(a15, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, long __task_result) => + { + __task_result_arg.ToJSBig(__task_result); + }); + __a14_native.ToJS(a14, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, long __task_result) => + { + __task_result_arg.ToJS(__task_result); + }); + __a13_native.ToJS(a13, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, global::System.DateTimeOffset __task_result) => + { + __task_result_arg.ToJS(__task_result); + }); + __a12_native.ToJS(a12, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, global::System.DateTime __task_result) => + { + __task_result_arg.ToJS(__task_result); + }); + __a8_native.ToJS(a8, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, object __task_result) => + { + __task_result_arg.ToJS(__task_result); + }); + __a5_native.ToJS(a5, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __delegate_arg_arg1, int __delegate_arg1) => + { + __delegate_arg_arg1.ToJS(__delegate_arg1); + }); + __a4_native.ToJS(a4); + __InvokeJSFunction(____arg_exception_native, ____arg_return_native, __a1_native, __a2_native, __a3_native, __a4_native, __a5_native, __a6_native, __a7_native, __a8_native, __a9_native, __a10_native, __a11_native, __a12_native, __a13_native, __a14_native, __a15_native, __a16_native); + } } - } - [global::System.Diagnostics.DebuggerNonUserCode] - void __InvokeJSFunction(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a1_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a2_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a3_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a4_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a5_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a6_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a7_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a8_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a9_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a10_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a11_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a12_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a13_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a14_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a15_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a16_native) - { - global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.InvokeJS(__signature_Annotated_2034238666, [____arg_exception_native, ____arg_return_native, __a1_native, __a2_native, __a3_native, __a4_native, __a5_native, __a6_native, __a7_native, __a8_native, __a9_native, __a10_native, __a11_native, __a12_native, __a13_native, __a14_native, __a15_native, __a16_native]); + [global::System.Diagnostics.DebuggerNonUserCode] + void __InvokeJSFunction(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a1_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a2_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a3_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a4_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a5_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a6_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a7_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a8_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a9_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a10_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a11_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a12_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a13_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a14_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a15_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a16_native) + { + global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.InvokeJS(__signature_Annotated_2034238666, [____arg_exception_native, ____arg_return_native, __a1_native, __a2_native, __a3_native, __a4_native, __a5_native, __a6_native, __a7_native, __a8_native, __a9_native, __a10_native, __a11_native, __a12_native, __a13_native, __a14_native, __a15_native, __a16_native]); + } } } @@ -174,7 +177,7 @@ void __InvokeJSFunction(global::System.Runtime.InteropServices.JavaScript.JSMars namespace System.Runtime.InteropServices.JavaScript { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] - unsafe class __GeneratedInitializer + class __GeneratedInitializer { [global::System.ThreadStaticAttribute] static bool initialized; @@ -193,75 +196,78 @@ static void __Register_() } } } - unsafe partial class Basic + partial class Basic { [global::System.Diagnostics.DebuggerNonUserCode] internal static unsafe void __Wrapper_AnnotatedExport_2034238666(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __arguments_buffer) { - __Stub(__arguments_buffer[2], __arguments_buffer[3], __arguments_buffer[4], __arguments_buffer[5], __arguments_buffer[6], __arguments_buffer[7], __arguments_buffer[8], __arguments_buffer[9], __arguments_buffer[10], __arguments_buffer[11], __arguments_buffer[12], __arguments_buffer[13], __arguments_buffer[14], __arguments_buffer[15], __arguments_buffer[16], __arguments_buffer[17], __arguments_buffer); - [global::System.Diagnostics.DebuggerNonUserCode] - void __Stub(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a1_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a2_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a3_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a4_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a5_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a6_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a7_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a8_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a9_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a10_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a11_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a12_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a13_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a14_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a15_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a16_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* ____arg_exception_native__param) + unsafe { - object a1 = default; - long a2 = default; - long a3 = default; - global::System.Action a4 = default; - global::System.Func a5 = default; - global::System.Span a6 = default; - global::System.ArraySegment a7 = default; - global::System.Threading.Tasks.Task a8 = default; - object[] a9 = default; - global::System.DateTime a10 = default; - global::System.DateTimeOffset a11 = default; - global::System.Threading.Tasks.Task a12 = default; - global::System.Threading.Tasks.Task a13 = default; - global::System.Threading.Tasks.Task a14 = default; - global::System.Threading.Tasks.Task a15 = default; - global::System.ArraySegment a16 = default; - ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native = ref *____arg_exception_native__param; - try + __Stub(__arguments_buffer[2], __arguments_buffer[3], __arguments_buffer[4], __arguments_buffer[5], __arguments_buffer[6], __arguments_buffer[7], __arguments_buffer[8], __arguments_buffer[9], __arguments_buffer[10], __arguments_buffer[11], __arguments_buffer[12], __arguments_buffer[13], __arguments_buffer[14], __arguments_buffer[15], __arguments_buffer[16], __arguments_buffer[17], __arguments_buffer); + [global::System.Diagnostics.DebuggerNonUserCode] + void __Stub(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a1_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a2_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a3_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a4_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a5_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a6_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a7_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a8_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a9_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a10_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a11_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a12_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a13_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a14_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a15_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a16_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* ____arg_exception_native__param) { - // UnmarshalCapture - Capture the native data into marshaller instances in case conversion to managed data throws an exception. - __a16_native.ToManaged(out a16); - __a11_native.ToManaged(out a11); - __a10_native.ToManaged(out a10); - __a9_native.ToManaged(out a9); - __a7_native.ToManaged(out a7); - __a6_native.ToManaged(out a6); - __a3_native.ToManagedBig(out a3); - __a2_native.ToManaged(out a2); - __a1_native.ToManaged(out a1); - // Unmarshal - Convert native data to managed data. - __a15_native.ToManaged(out a15, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out long __task_result) => - { - __task_result_arg.ToManagedBig(out __task_result); - }); - __a14_native.ToManaged(out a14, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out long __task_result) => - { - __task_result_arg.ToManaged(out __task_result); - }); - __a13_native.ToManaged(out a13, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out global::System.DateTimeOffset __task_result) => - { - __task_result_arg.ToManaged(out __task_result); - }); - __a12_native.ToManaged(out a12, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out global::System.DateTime __task_result) => + object a1 = default; + long a2 = default; + long a3 = default; + global::System.Action a4 = default; + global::System.Func a5 = default; + global::System.Span a6 = default; + global::System.ArraySegment a7 = default; + global::System.Threading.Tasks.Task a8 = default; + object[] a9 = default; + global::System.DateTime a10 = default; + global::System.DateTimeOffset a11 = default; + global::System.Threading.Tasks.Task a12 = default; + global::System.Threading.Tasks.Task a13 = default; + global::System.Threading.Tasks.Task a14 = default; + global::System.Threading.Tasks.Task a15 = default; + global::System.ArraySegment a16 = default; + ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native = ref *____arg_exception_native__param; + try { - __task_result_arg.ToManaged(out __task_result); - }); - __a8_native.ToManaged(out a8, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out object __task_result) => + // UnmarshalCapture - Capture the native data into marshaller instances in case conversion to managed data throws an exception. + __a16_native.ToManaged(out a16); + __a11_native.ToManaged(out a11); + __a10_native.ToManaged(out a10); + __a9_native.ToManaged(out a9); + __a7_native.ToManaged(out a7); + __a6_native.ToManaged(out a6); + __a3_native.ToManagedBig(out a3); + __a2_native.ToManaged(out a2); + __a1_native.ToManaged(out a1); + // Unmarshal - Convert native data to managed data. + __a15_native.ToManaged(out a15, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out long __task_result) => + { + __task_result_arg.ToManagedBig(out __task_result); + }); + __a14_native.ToManaged(out a14, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out long __task_result) => + { + __task_result_arg.ToManaged(out __task_result); + }); + __a13_native.ToManaged(out a13, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out global::System.DateTimeOffset __task_result) => + { + __task_result_arg.ToManaged(out __task_result); + }); + __a12_native.ToManaged(out a12, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out global::System.DateTime __task_result) => + { + __task_result_arg.ToManaged(out __task_result); + }); + __a8_native.ToManaged(out a8, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out object __task_result) => + { + __task_result_arg.ToManaged(out __task_result); + }); + __a5_native.ToManaged(out a5, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __delegate_arg_arg1, out int __delegate_arg1) => + { + __delegate_arg_arg1.ToManaged(out __delegate_arg1); + }); + __a4_native.ToManaged(out a4); + global::Basic.AnnotatedExport(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); + } + catch (global::System.Exception __arg_exception) { - __task_result_arg.ToManaged(out __task_result); - }); - __a5_native.ToManaged(out a5, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __delegate_arg_arg1, out int __delegate_arg1) => - { - __delegate_arg_arg1.ToManaged(out __delegate_arg1); - }); - __a4_native.ToManaged(out a4); - global::Basic.AnnotatedExport(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); - } - catch (global::System.Exception __arg_exception) - { - ____arg_exception_native.ToJS(__arg_exception); + ____arg_exception_native.ToJS(__arg_exception); + } } } } @@ -289,45 +295,48 @@ public async Task ValidateGeneratedSourceOutput_Return() "JSImports.g.cs", SourceText.From(""" // - unsafe partial class Basic + partial class Basic { [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Runtime.Versioning.SupportedOSPlatform("browser")] public static partial global::System.Threading.Tasks.Task Import1() { - if (__signature_Import1_622134597 == null) + unsafe { - __signature_Import1_622134597 = global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindJSFunction("DoesNotExist", null, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32)]); - } - - { - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native; - global::System.Threading.Tasks.Task __retVal; - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __retVal_native; - // Setup - Perform required setup. - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_return_native); - ____arg_return_native.Initialize(); - global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_exception_native); - ____arg_exception_native.Initialize(); + if (__signature_Import1_622134597 == null) { - __retVal_native = __InvokeJSFunction(____arg_exception_native, ____arg_return_native); + __signature_Import1_622134597 = global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindJSFunction("DoesNotExist", null, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32)]); } - // UnmarshalCapture - Capture the native data into marshaller instances in case conversion to managed data throws an exception. - __retVal_native.ToManaged(out __retVal, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out int __task_result) => { - __task_result_arg.ToManaged(out __task_result); - }); - return __retVal; - } + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native; + global::System.Threading.Tasks.Task __retVal; + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __retVal_native; + // Setup - Perform required setup. + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_return_native); + ____arg_return_native.Initialize(); + global::System.Runtime.CompilerServices.Unsafe.SkipInit(out ____arg_exception_native); + ____arg_exception_native.Initialize(); + { + __retVal_native = __InvokeJSFunction(____arg_exception_native, ____arg_return_native); + } - [global::System.Diagnostics.DebuggerNonUserCode] - global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __InvokeJSFunction(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native) - { - global::System.Span __arguments_buffer = [____arg_exception_native, ____arg_return_native]; - global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.InvokeJS(__signature_Import1_622134597, __arguments_buffer); - return __arguments_buffer[1]; + // UnmarshalCapture - Capture the native data into marshaller instances in case conversion to managed data throws an exception. + __retVal_native.ToManaged(out __retVal, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, out int __task_result) => + { + __task_result_arg.ToManaged(out __task_result); + }); + return __retVal; + } + + [global::System.Diagnostics.DebuggerNonUserCode] + global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __InvokeJSFunction(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_return_native) + { + global::System.Span __arguments_buffer = [____arg_exception_native, ____arg_return_native]; + global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.InvokeJS(__signature_Import1_622134597, __arguments_buffer); + return __arguments_buffer[1]; + } } } @@ -342,7 +351,7 @@ unsafe partial class Basic namespace System.Runtime.InteropServices.JavaScript { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] - unsafe class __GeneratedInitializer + class __GeneratedInitializer { [global::System.ThreadStaticAttribute] static bool initialized; @@ -361,30 +370,33 @@ static void __Register_() } } } - unsafe partial class Basic + partial class Basic { [global::System.Diagnostics.DebuggerNonUserCode] internal static unsafe void __Wrapper_Export1_622134597(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __arguments_buffer) { - __Stub(__arguments_buffer, __arguments_buffer + 1); - [global::System.Diagnostics.DebuggerNonUserCode] - void __Stub(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* ____arg_exception_native__param, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __invokeRetValUnmanaged__param) + unsafe { - ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native = ref *____arg_exception_native__param; - ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __invokeRetValUnmanaged = ref *__invokeRetValUnmanaged__param; - global::System.Threading.Tasks.Task __invokeRetVal = default; - try + __Stub(__arguments_buffer, __arguments_buffer + 1); + [global::System.Diagnostics.DebuggerNonUserCode] + void __Stub(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* ____arg_exception_native__param, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __invokeRetValUnmanaged__param) { - __invokeRetVal = global::Basic.Export1(); - // Marshal - Convert managed data to native data. - __invokeRetValUnmanaged.ToJS(__invokeRetVal, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, int __task_result) => + ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument ____arg_exception_native = ref *____arg_exception_native__param; + ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __invokeRetValUnmanaged = ref *__invokeRetValUnmanaged__param; + global::System.Threading.Tasks.Task __invokeRetVal = default; + try { - __task_result_arg.ToJS(__task_result); - }); - } - catch (global::System.Exception __arg_exception) - { - ____arg_exception_native.ToJS(__arg_exception); + __invokeRetVal = global::Basic.Export1(); + // Marshal - Convert managed data to native data. + __invokeRetValUnmanaged.ToJS(__invokeRetVal, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, int __task_result) => + { + __task_result_arg.ToJS(__task_result); + }); + } + catch (global::System.Exception __arg_exception) + { + ____arg_exception_native.ToJS(__arg_exception); + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj index 95d183e4b51699..189dd62e7d9b46 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/UnsafeCodeGeneration.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/UnsafeCodeGeneration.cs new file mode 100644 index 00000000000000..bf4a824495355c --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/UnsafeCodeGeneration.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Interop.UnitTests; +using Xunit; + +namespace JSImportGenerator.Unit.Tests +{ + /// + /// Verifies that the generated output compiles under the updated memory safety rules ("unsafe evolution"), + /// where an unsafe modifier on a type establishes no context for the members inside it. + /// + public class UnsafeCodeGeneration + { + public static IEnumerable Snippets() + { + yield return new object[] { nameof(CodeSnippets.AllDefault), CodeSnippets.AllDefault }; + yield return new object[] { nameof(CodeSnippets.AllAnnotated), CodeSnippets.AllAnnotated }; + yield return new object[] { nameof(CodeSnippets.AllAnnotatedExport), CodeSnippets.AllAnnotatedExport }; + } + + [Theory] + [MemberData(nameof(Snippets))] + public void GeneratedOutputCompilesUnderUpdatedRules(string name, string source) + { + _ = name; + + Compilation comp = TestUtils.CreateCompilation(source, allowUnsafe: true); + + // Roslyn does not expose the memory safety rules version through a public API yet, so opt in through + // the same feature flag the compiler uses. It lives on the parse options, so every tree is re-parsed. + var parseOptions = ((CSharpParseOptions)comp.SyntaxTrees.First().Options) + .WithFeatures([new KeyValuePair("updated-memory-safety-rules", "")]); + comp = comp.RemoveAllSyntaxTrees().AddSyntaxTrees( + comp.SyntaxTrees.Select(t => CSharpSyntaxTree.ParseText(t.GetText(), parseOptions, t.FilePath))); + + // CS9377 ("the 'unsafe' modifier does not have any effect here") sits above the default warning + // level, so it has to be raised or the assertion below could never observe it. + comp = comp.WithOptions(((CSharpCompilationOptions)comp.Options).WithWarningLevel(9999)); + + Compilation newComp = TestUtils.RunGenerators(comp, out var generatorDiags, + new Microsoft.Interop.JavaScript.JSImportGenerator(), + new Microsoft.Interop.JavaScript.JSExportGenerator()); + + Assert.Empty(generatorDiags); + + // CS9377 reports an 'unsafe' modifier that has no effect under these rules. It is suppressed in + // generated files by default, so it is asserted on explicitly rather than left to the error check. + var unexpected = newComp.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error || d.Id is "CS9377") + .Select(d => $"{d.Id}: {d.GetMessage()} @ {d.Location.GetLineSpan()}") + .ToList(); + + Assert.Empty(unexpected); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassGenerator.cs index fb000dc2bc74a6..3ac6e64b032ae3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassGenerator.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.CodeDom.Compiler; @@ -51,12 +51,17 @@ public void Initialize(IncrementalGeneratorInitializationContext context) using IndentedTextWriter writer = new(sw); writer.WriteLine("// "); - writer.WriteLine($"file sealed unsafe class {ClassInfoTypeName} : global::System.Runtime.InteropServices.Marshalling.IComExposedClass"); + writer.WriteLine($"file sealed class {ClassInfoTypeName} : global::System.Runtime.InteropServices.Marshalling.IComExposedClass"); writer.WriteLine('{'); writer.Indent++; - writer.WriteLine("private static volatile global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry* s_vtables;"); + writer.WriteLine("private static volatile unsafe global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry* s_vtables;"); sw.WriteLine(); - writer.WriteLine("public static global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry* GetComInterfaceEntries(out int count)"); + writer.WriteLine("public static unsafe global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry* GetComInterfaceEntries(out int count)"); + writer.WriteLine('{'); + writer.Indent++; + // The modifiers above make the pointer types legal to name; the body still needs an unsafe + // context of its own, since under the updated rules a member modifier opens none for it. + writer.WriteLine("unsafe"); writer.WriteLine('{'); writer.Indent++; writer.WriteLine($"count = {implementedInterfaces.Length};"); @@ -84,10 +89,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) writer.WriteLine('}'); writer.Indent--; writer.WriteLine('}'); + writer.Indent--; + writer.WriteLine('}'); sw.WriteLine(); - data.ContainingSyntaxContext.WriteToWithUnsafeModifier(writer, data.ClassSyntax, static (writer, classSyntax) => + data.ContainingSyntaxContext.WriteToWithUnsafeModifier(data.UseUpdatedMemorySafetyRules, writer, data.ClassSyntax, static (writer, classSyntax) => { writer.WriteLine($"[global::System.Runtime.InteropServices.Marshalling.ComExposedClassAttribute<{ClassInfoTypeName}>]"); writer.WriteLine($"{string.Join(" ", classSyntax.Modifiers)} class {classSyntax.Identifier}{classSyntax.TypeParameters} {{ }}"); diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassInfo.cs index 58a3caa495ea58..1403f09eb3e492 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassInfo.cs @@ -16,6 +16,9 @@ internal sealed class ComClassInfo : IEquatable public ContainingSyntax ClassSyntax { get; init; } public SequenceEqualImmutableArray ImplementedInterfacesNames { get; init; } + /// + public bool UseUpdatedMemorySafetyRules { get; init; } + private ComClassInfo(string className, ContainingSyntaxContext containingSyntaxContext, ContainingSyntax classSyntax, SequenceEqualImmutableArray implementedInterfacesNames) { ClassName = className; @@ -44,7 +47,10 @@ public static ComClassInfo From(INamedTypeSymbol type, ClassDeclarationSyntax sy type.ToDisplayString(), new ContainingSyntaxContext(syntax), new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList), - new(names.ToImmutable())); + new(names.ToImmutable())) + { + UseUpdatedMemorySafetyRules = syntax.SyntaxTree.Options.Features.ContainsKey("updated-memory-safety-rules") + }; } public bool Equals(ComClassInfo? other) @@ -52,6 +58,7 @@ public bool Equals(ComClassInfo? other) return other is not null && ClassName == other.ClassName && ContainingSyntaxContext.Equals(other.ContainingSyntaxContext) + && UseUpdatedMemorySafetyRules == other.UseUpdatedMemorySafetyRules && ImplementedInterfacesNames.SequenceEqual(other.ImplementedInterfacesNames); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs index 3c209670718ef4..09e066017bbb91 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs @@ -534,12 +534,15 @@ static bool MethodEquals(ComMethodContext a, ComMethodContext b) private static void WriteImplementationVTableStruct(IndentedTextWriter writer, ComInterfaceAndMethodsContext interfaceMethods) { writer.WriteLine("[global::System.Runtime.InteropServices.StructLayoutAttribute(global::System.Runtime.InteropServices.LayoutKind.Sequential)]"); - writer.WriteLine("file unsafe struct InterfaceImplementationVtable"); + // The 'unsafe' modifier goes on each field rather than on the type: a field-level modifier makes the + // pointer legal to name under the legacy rules and has a meaning under the updated ones, whereas a + // type-level modifier is meaningless under the updated rules and warns (CS9377). + writer.WriteLine("file struct InterfaceImplementationVtable"); writer.WriteLine('{'); writer.Indent++; - writer.WriteLine("public delegate* unmanaged[MemberFunction] QueryInterface_0;"); - writer.WriteLine("public delegate* unmanaged[MemberFunction] AddRef_1;"); - writer.WriteLine("public delegate* unmanaged[MemberFunction] Release_2;"); + writer.WriteLine("public unsafe delegate* unmanaged[MemberFunction] QueryInterface_0;"); + writer.WriteLine("public unsafe delegate* unmanaged[MemberFunction] AddRef_1;"); + writer.WriteLine("public unsafe delegate* unmanaged[MemberFunction] Release_2;"); if (interfaceMethods.Interface.Base is not null) { foreach (ComMethodContext inheritedMethod in interfaceMethods.InheritedMethods) @@ -548,7 +551,7 @@ private static void WriteImplementationVTableStruct(IndentedTextWriter writer, C inheritedMethod.GenerationContext, ComInterfaceGeneratorHelpers.GetGeneratorResolver); - writer.WriteLine($"public {functionPointerType.NormalizeWhitespace()} {inheritedMethod.MethodInfo.MethodName}_{inheritedMethod.GenerationContext.VtableIndexData.Index};"); + writer.WriteLine($"public unsafe {functionPointerType.NormalizeWhitespace()} {inheritedMethod.MethodInfo.MethodName}_{inheritedMethod.GenerationContext.VtableIndexData.Index};"); } } @@ -560,7 +563,7 @@ private static void WriteImplementationVTableStruct(IndentedTextWriter writer, C declaredMethod.GenerationContext, ComInterfaceGeneratorHelpers.GetGeneratorResolver); - writer.WriteLine($"public {functionPointerType.NormalizeWhitespace()} {declaredMethod.MethodInfo.MethodName}_{declaredMethod.GenerationContext.VtableIndexData.Index};"); + writer.WriteLine($"public unsafe {functionPointerType.NormalizeWhitespace()} {declaredMethod.MethodInfo.MethodName}_{declaredMethod.GenerationContext.VtableIndexData.Index};"); } writer.Indent--; @@ -569,11 +572,28 @@ private static void WriteImplementationVTableStruct(IndentedTextWriter writer, C private static void WriteInterfaceInformation(IndentedTextWriter writer, ComInterfaceInfo interfaceInfo) { - writer.WriteLine("file unsafe sealed class InterfaceInformation : global::System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType"); + writer.WriteLine("file sealed class InterfaceInformation : global::System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType"); writer.WriteLine('{'); writer.Indent++; writer.WriteLine($"public static global::System.Guid Iid {{ get; }} = new([{string.Join(", ", interfaceInfo.InterfaceId.ToByteArray())}]);"); - writer.WriteLine($"public static void** ManagedVirtualMethodTable => {(interfaceInfo.Options.HasFlag(ComInterfaceOptions.ManagedObjectWrapper) ? "(void**)global::System.Runtime.CompilerServices.Unsafe.AsPointer(in InterfaceImplementation.Vtable)" : "null")};"); + // The modifier makes the pointer type legal to name; the accessor still needs an unsafe context of + // its own, since under the updated rules a member modifier opens none for the body. + writer.WriteLine("public static unsafe void** ManagedVirtualMethodTable"); + writer.WriteLine('{'); + writer.Indent++; + writer.WriteLine("get"); + writer.WriteLine('{'); + writer.Indent++; + writer.WriteLine("unsafe"); + writer.WriteLine('{'); + writer.Indent++; + writer.WriteLine($"return {(interfaceInfo.Options.HasFlag(ComInterfaceOptions.ManagedObjectWrapper) ? "(void**)global::System.Runtime.CompilerServices.Unsafe.AsPointer(in InterfaceImplementation.Vtable)" : "null")};"); + writer.Indent--; + writer.WriteLine('}'); + writer.Indent--; + writer.WriteLine('}'); + writer.Indent--; + writer.WriteLine('}'); writer.Indent--; writer.WriteLine('}'); } @@ -581,7 +601,12 @@ private static void WriteInterfaceInformation(IndentedTextWriter writer, ComInte private static void WriteInterfaceImplementation(IndentedTextWriter writer, ComInterfaceAndMethodsContext data) { writer.WriteLine("[global::System.Runtime.InteropServices.DynamicInterfaceCastableImplementationAttribute]"); - writer.WriteLine($"file unsafe interface InterfaceImplementation : {data.Interface.Info.Type.FullTypeName}"); + // This type holds the stubs that explicitly implement the user's interface members. Those stubs copy + // their modifiers from the user's declaration and cannot be marked 'unsafe' on their own without + // failing to implement a safe member (CS9366), so under the legacy rules the type modifier is what + // makes their pointer parameters legal to name. + string unsafeModifier = data.Interface.Info.UseUpdatedMemorySafetyRules ? "" : "unsafe "; + writer.WriteLine($"file {unsafeModifier}interface InterfaceImplementation : {data.Interface.Info.Type.FullTypeName}"); writer.WriteLine('{'); writer.Indent++; @@ -593,6 +618,11 @@ private static void WriteInterfaceImplementation(IndentedTextWriter writer, ComI writer.WriteLine("static InterfaceImplementation()"); writer.WriteLine('{'); writer.Indent++; + // The initialization takes addresses and writes through pointers, so it opens its own unsafe + // context rather than relying on one from the containing type. + writer.WriteLine("unsafe"); + writer.WriteLine('{'); + writer.Indent++; if (data.Interface.Base is { } baseInterface) { @@ -624,6 +654,9 @@ private static void WriteInterfaceImplementation(IndentedTextWriter writer, ComI writer.Indent--; writer.WriteLine('}'); + + writer.Indent--; + writer.WriteLine('}'); } BasePropertyDeclarationSyntax? bufferedDeclaredGetter = null; @@ -885,12 +918,15 @@ private static BasePropertyDeclarationSyntax MergePropertyAccessors( private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter writer, ComInterfaceAndMethodsContext data) { - data.Interface.Info.TypeDefinitionContext.WriteToWithUnsafeModifier(writer, (data.Interface.Info.ContainingSyntax, data.ShadowingMethods), static (writer, data) => + data.Interface.Info.TypeDefinitionContext.WriteToWithUnsafeModifier(data.Interface.Info.UseUpdatedMemorySafetyRules, writer, (data.Interface.Info.ContainingSyntax, data.ShadowingMethods, data.Interface.Info.UseUpdatedMemorySafetyRules), static (writer, data) => { - (ContainingSyntax syntax, IEnumerable? shadowingMethods) = data; + (ContainingSyntax syntax, IEnumerable? shadowingMethods, bool useUpdatedMemorySafetyRules) = data; writer.WriteLine("[global::System.Runtime.InteropServices.Marshalling.IUnknownDerivedAttribute]"); - writer.WriteLine($"{string.Join(" ", syntax.Modifiers.AddToModifiers(SyntaxKind.UnsafeKeyword))} {syntax.TypeKind.GetDeclarationKeyword()} {syntax.Identifier}{syntax.TypeParameters}"); + SyntaxTokenList typeModifiers = useUpdatedMemorySafetyRules + ? syntax.Modifiers + : syntax.Modifiers.AddToModifiers(SyntaxKind.UnsafeKeyword); + writer.WriteLine($"{string.Join(" ", typeModifiers)} {syntax.TypeKind.GetDeclarationKeyword()} {syntax.Identifier}{syntax.TypeParameters}"); writer.WriteLine('{'); writer.Indent++; @@ -901,13 +937,20 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter // so that overloaded indexers do not accidentally cross-pair. (string? PropName, string? DeclaringType, string? PropType, SequenceEqualImmutableArray PropAttrs, - string? IndexParamList, string? IndexArgList) pendingGetter = default; + string? IndexParamList, string? IndexArgList, bool IsUnsafe) pendingGetter = default; foreach (ComMethodContext shadow in shadowingMethods) { IncrementalMethodStubGenerationContext generationContext = shadow.GenerationContext; SignatureContext sigContext = generationContext.SignatureContext; + // A shadow forwards to a member of the base interface. When that member declares itself + // caller-unsafe the shadow has to say the same thing, or it would silently widen the + // contract and its implementation could no longer implement it, and the forwarding call + // needs an unsafe context of its own. + bool isUnsafe = shadow.MethodInfo.Syntax is { } shadowedSyntax + && shadowedSyntax.Modifiers.Any(SyntaxKind.UnsafeKeyword); + if (generationContext.MemberKind.IsPropertyOrIndexerAccessor()) { bool isSetter = generationContext.MemberKind.IsAccessorSetter(); @@ -946,7 +989,7 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter if (!isSetter) { FlushPendingGetter(writer, ref pendingGetter); - pendingGetter = (propName, declaringType, propType, propAttrs, indexParamList, indexArgList); + pendingGetter = (propName, declaringType, propType, propAttrs, indexParamList, indexArgList, isUnsafe); continue; } @@ -957,11 +1000,11 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter && pendingGetter.IndexParamList == indexParamList) { EmitPropertyAttributes(writer, pendingGetter.PropAttrs); - EmitDeclarationHead(writer, pendingGetter.PropType!, pendingGetter.PropName!, pendingGetter.IndexParamList); + EmitDeclarationHead(writer, pendingGetter.PropType!, pendingGetter.PropName!, pendingGetter.IndexParamList, pendingGetter.IsUnsafe || isUnsafe); writer.WriteLine('{'); writer.Indent++; - EmitAccessor(writer, isSetter: false, pendingGetter.DeclaringType!, pendingGetter.PropName!, pendingGetter.IndexArgList); - EmitAccessor(writer, isSetter: true, pendingGetter.DeclaringType!, pendingGetter.PropName!, pendingGetter.IndexArgList); + EmitAccessor(writer, isSetter: false, pendingGetter.DeclaringType!, pendingGetter.PropName!, pendingGetter.IndexArgList, pendingGetter.IsUnsafe); + EmitAccessor(writer, isSetter: true, pendingGetter.DeclaringType!, pendingGetter.PropName!, pendingGetter.IndexArgList, isUnsafe); writer.Indent--; writer.WriteLine('}'); pendingGetter = default; @@ -970,10 +1013,10 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter FlushPendingGetter(writer, ref pendingGetter); EmitPropertyAttributes(writer, propAttrs); - EmitDeclarationHead(writer, propType, propName, indexParamList); + EmitDeclarationHead(writer, propType, propName, indexParamList, isUnsafe); writer.WriteLine('{'); writer.Indent++; - EmitAccessor(writer, isSetter: true, declaringType, propName, indexArgList); + EmitAccessor(writer, isSetter: true, declaringType, propName, indexArgList, isUnsafe); writer.Indent--; writer.WriteLine('}'); continue; @@ -996,10 +1039,32 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter writer.WriteLine($"[{attrInfo.Type}({string.Join(", ", attrInfo.Arguments)})]"); } - writer.Write($"new {sigContext.StubReturnType} {shadow.MethodInfo.MethodName}"); + writer.Write($"new {(isUnsafe ? "unsafe " : "")}{sigContext.StubReturnType} {shadow.MethodInfo.MethodName}"); writer.Write($"({string.Join(", ", sigContext.StubParameters.Select(p => p.NormalizeWhitespace().ToString()))})"); - writer.Write($" => (({shadow.OriginalDeclaringInterface.Info.Type.FullTypeName})this).{shadow.MethodInfo.MethodName}"); - writer.WriteLine($"({string.Join(", ", sigContext.ManagedParameters.Select(mp => $"{(mp.IsByRef ? $"{MarshallerHelpers.GetManagedArgumentRefKindKeyword(mp)} " : "")}{mp.InstanceIdentifier}"))});"); + string forwardingCall = $"(({shadow.OriginalDeclaringInterface.Info.Type.FullTypeName})this).{shadow.MethodInfo.MethodName}" + + $"({string.Join(", ", sigContext.ManagedParameters.Select(mp => $"{(mp.IsByRef ? $"{MarshallerHelpers.GetManagedArgumentRefKindKeyword(mp)} " : "")}{mp.InstanceIdentifier}"))})"; + + if (isUnsafe) + { + // An expression body cannot host the required unsafe context: the 'unsafe(...)' + // expression form is not one of the forms a statement may consist of. + bool returnsVoid = sigContext.StubReturnType is PredefinedTypeSyntax { Keyword.RawKind: (int)SyntaxKind.VoidKeyword }; + writer.WriteLine(); + writer.WriteLine('{'); + writer.Indent++; + writer.WriteLine("unsafe"); + writer.WriteLine('{'); + writer.Indent++; + writer.WriteLine($"{(returnsVoid ? "" : "return ")}{forwardingCall};"); + writer.Indent--; + writer.WriteLine('}'); + writer.Indent--; + writer.WriteLine('}'); + } + else + { + writer.WriteLine($" => {forwardingCall};"); + } } FlushPendingGetter(writer, ref pendingGetter); @@ -1007,32 +1072,33 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter writer.Indent--; writer.WriteLine('}'); - static void FlushPendingGetter(IndentedTextWriter writer, ref (string? PropName, string? DeclaringType, string? PropType, SequenceEqualImmutableArray PropAttrs, string? IndexParamList, string? IndexArgList) pending) + static void FlushPendingGetter(IndentedTextWriter writer, ref (string? PropName, string? DeclaringType, string? PropType, SequenceEqualImmutableArray PropAttrs, string? IndexParamList, string? IndexArgList, bool IsUnsafe) pending) { if (pending.PropName is null) { return; } EmitPropertyAttributes(writer, pending.PropAttrs); - EmitDeclarationHead(writer, pending.PropType!, pending.PropName!, pending.IndexParamList); + EmitDeclarationHead(writer, pending.PropType!, pending.PropName!, pending.IndexParamList, pending.IsUnsafe); writer.WriteLine('{'); writer.Indent++; - EmitAccessor(writer, isSetter: false, pending.DeclaringType!, pending.PropName!, pending.IndexArgList); + EmitAccessor(writer, isSetter: false, pending.DeclaringType!, pending.PropName!, pending.IndexArgList, pending.IsUnsafe); writer.Indent--; writer.WriteLine('}'); pending = default; } // Writes either `new T Name` (property) or `new T this[]` (indexer) on its own line. - static void EmitDeclarationHead(IndentedTextWriter writer, string propType, string propName, string? indexParamList) + static void EmitDeclarationHead(IndentedTextWriter writer, string propType, string propName, string? indexParamList, bool isUnsafe) { + string modifiers = isUnsafe ? "new unsafe" : "new"; if (indexParamList is null) { - writer.WriteLine($"new {propType} {propName}"); + writer.WriteLine($"{modifiers} {propType} {propName}"); } else { - writer.WriteLine($"new {propType} this[{indexParamList}]"); + writer.WriteLine($"{modifiers} {propType} this[{indexParamList}]"); } } @@ -1040,14 +1106,33 @@ static void EmitDeclarationHead(IndentedTextWriter writer, string propType, stri // or `get => ((Base)this)[];` / `set => ((Base)this)[] = value;` for indexers. // For indexers the propName isn't part of the access expression (the IL-level naming comes // from `[IndexerName]` propagated via AssociatedAttributes). - static void EmitAccessor(IndentedTextWriter writer, bool isSetter, string declaringType, string propName, string? indexArgList) + static void EmitAccessor(IndentedTextWriter writer, bool isSetter, string declaringType, string propName, string? indexArgList, bool isUnsafe) { string access = indexArgList is null ? $"(({declaringType})this).{propName}" : $"(({declaringType})this)[{indexArgList}]"; - writer.WriteLine(isSetter - ? $"set => {access} = value;" - : $"get => {access};"); + + if (!isUnsafe) + { + writer.WriteLine(isSetter + ? $"set => {access} = value;" + : $"get => {access};"); + return; + } + + // Forwarding to a caller-unsafe member needs an unsafe context, which an expression-bodied + // accessor has nowhere to put. + writer.WriteLine(isSetter ? "set" : "get"); + writer.WriteLine('{'); + writer.Indent++; + writer.WriteLine("unsafe"); + writer.WriteLine('{'); + writer.Indent++; + writer.WriteLine(isSetter ? $"{access} = value;" : $"return {access};"); + writer.Indent--; + writer.WriteLine('}'); + writer.Indent--; + writer.WriteLine('}'); } static void EmitPropertyAttributes(IndentedTextWriter writer, SequenceEqualImmutableArray attrs) diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs index cbdffa67d62357..2a5e0bb379d5a8 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs @@ -31,6 +31,12 @@ internal sealed record ComInterfaceInfo public Location DiagnosticLocation { get; init; } public bool IsExternallyDefined { get; init; } + /// + /// Whether the compilation uses the updated memory safety rules ("unsafe evolution"), which decides + /// whether generated types need an unsafe modifier for their pointer members to be legal. + /// + public bool UseUpdatedMemorySafetyRules { get; init; } + private ComInterfaceInfo( ManagedTypeInfo type, string thisInterfaceKey, @@ -108,7 +114,10 @@ public static DiagnosticOrInterfaceInfo From(INamedTypeSymbol symbol, InterfaceD new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList), guid ?? Guid.Empty, interfaceAttributeData.Options, - syntax.Identifier.GetLocation()), + syntax.Identifier.GetLocation()) + { + UseUpdatedMemorySafetyRules = env.EnvironmentFlags.HasFlag(EnvironmentFlags.UpdatedMemorySafetyRules) + }, symbol); // Now that we've validated all of our requirements, we will check for some non-blocking scenarios diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs index 1d1e892fab2bb1..8d2c0917f5b61c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -133,12 +133,13 @@ private static MethodDeclarationSyntax PrintMethodStub( SignatureContext stub, BlockSyntax stubCode) { - // Create stub function + // Create stub function. The body calls through an unmanaged function pointer, so it opens an + // explicit unsafe block rather than relying on an unsafe modifier on the containing type. return MethodDeclaration(stub.StubReturnType, stubMethodSyntax.Identifier) .AddAttributeLists(stub.AdditionalAttributes.ToArray()) .WithModifiers(stubMethodSyntax.Modifiers.StripTriviaFromTokens()) .WithParameterList(ParameterList(SeparatedList(stub.StubParameters))) - .WithBody(stubCode); + .WithBody(stubCode.WrapInUnsafeBlock()); } private static BasePropertyDeclarationSyntax PrintPropertyOrIndexerAccessorStub( @@ -177,7 +178,7 @@ private static BasePropertyDeclarationSyntax PrintPropertyOrIndexerAccessorStub( AccessorDeclarationSyntax accessor = AccessorDeclaration(accessorKind) .AddAttributeLists(methodStub.SignatureContext.AdditionalAttributes.ToArray()) - .WithBody(stubCode); + .WithBody(stubCode.WrapInUnsafeBlock()); if (isIndexer) { @@ -257,10 +258,12 @@ public static (MemberDeclarationSyntax, ImmutableArray) Generate MethodDeclarationSyntax unmanagedToManagedStub = MethodDeclaration(returnType, $"ABI_{methodStub.StubMethodSyntaxTemplate.Identifier.Text}") - .WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword))) + // The signature names unmanaged pointer types and the body unmarshals through them, so the + // stub is both declared unsafe and opens an unsafe block for its body. + .WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.UnsafeKeyword))) .WithParameterList(unmanagedParameterList) .AddAttributeLists(AttributeList(SingletonSeparatedList(unmanagedCallersOnlyAttribute))) - .WithBody(code); + .WithBody(code.WrapInUnsafeBlock()); return ( unmanagedToManagedStub, diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs index d42e24dfd87fb8..ccf18c1efa81ea 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs @@ -66,7 +66,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var (stubSyntax, _) = VirtualMethodPointerStubGenerator.GenerateManagedToNativeStub(stub, VtableIndexStubGeneratorHelpers.GetGeneratorResolver); - stub.ContainingSyntaxContext.WriteToWithUnsafeModifier(writer, stubSyntax, static (writer, stubSyntax) => + stub.ContainingSyntaxContext.WriteToWithUnsafeModifier(stub.EnvironmentFlags.HasFlag(EnvironmentFlags.UpdatedMemorySafetyRules), writer, stubSyntax, static (writer, stubSyntax) => { writer.WriteLine("internal partial interface Native"); writer.WriteLine('{'); @@ -102,7 +102,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var (stubSyntax, _) = VirtualMethodPointerStubGenerator.GenerateNativeToManagedStub(stub, VtableIndexStubGeneratorHelpers.GetGeneratorResolver); - stub.ContainingSyntaxContext.WriteToWithUnsafeModifier(writer, stubSyntax, static (writer, stubSyntax) => + stub.ContainingSyntaxContext.WriteToWithUnsafeModifier(stub.EnvironmentFlags.HasFlag(EnvironmentFlags.UpdatedMemorySafetyRules), writer, stubSyntax, static (writer, stubSyntax) => { writer.WriteLine("internal partial interface Native"); writer.WriteLine('{'); @@ -135,7 +135,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { sw.WriteLine(); - syntaxContext.WriteToWithUnsafeModifier(writer, syntaxContext.ContainingSyntax[0].Identifier.Text, static (writer, baseTypeName) => + // This part of the partial interface declares no members at all, so it never needs an + // 'unsafe' modifier under either set of rules. + syntaxContext.WriteToWithUnsafeModifier(useUpdatedMemorySafetyRules: true, writer, syntaxContext.ContainingSyntax[0].Identifier.Text, static (writer, baseTypeName) => { writer.WriteLine("[global::System.Runtime.InteropServices.DynamicInterfaceCastableImplementationAttribute]"); writer.WriteLine($"internal partial interface Native : {baseTypeName} {{ }}"); @@ -161,14 +163,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Generate a method named PopulateUnmanagedVirtualMethodTable on the native interface implementation // that fills in a span with the addresses of the unmanaged-to-managed stub functions at their correct indices. - group.Key.WriteToWithUnsafeModifier(writer, group, static (writer, data) => + group.Key.WriteToWithUnsafeModifier(group.First().EnvironmentFlags.HasFlag(EnvironmentFlags.UpdatedMemorySafetyRules), writer, group, static (writer, data) => { - writer.WriteLine("internal unsafe partial interface Native"); + writer.WriteLine("internal partial interface Native"); writer.WriteLine('{'); writer.Indent++; writer.WriteLine("internal static unsafe void PopulateUnmanagedVirtualMethodTable(void** vtable)"); writer.WriteLine('{'); writer.Indent++; + // The body takes the address of each stub and writes through the vtable pointer, so it + // opens its own unsafe context rather than relying on one from the containing type. + writer.WriteLine("unsafe"); + writer.WriteLine('{'); + writer.Indent++; foreach (SourceAvailableIncrementalMethodStubGenerationContext method in data) { @@ -180,6 +187,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) writer.WriteLine('}'); writer.Indent--; writer.WriteLine('}'); + writer.Indent--; + writer.WriteLine('}'); }); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs index e67ca2a48c577f..104e802e877594 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs @@ -146,7 +146,7 @@ private static MethodDeclarationSyntax PrintGeneratedSource( .AddAttributeLists(stub.AdditionalAttributes.ToArray()) .WithModifiers(StripTriviaFromModifiers(userDeclaredMethod.Modifiers)) .WithParameterList(ParameterList(SeparatedList(stub.StubParameters))) - .WithBody(Block(UnsafeStatement(stubCode))); + .WithBody(stubCode.WrapInUnsafeBlock()); } private static LibraryImportCompilationData? ProcessLibraryImportAttribute(AttributeData attrData) diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs index e0a8747d9081ba..7f387676000b62 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs @@ -165,7 +165,7 @@ private static MethodDeclarationSyntax PrintGeneratedSource( .AddAttributeLists(stub.AdditionalAttributes.ToArray()) .WithModifiers(StripTriviaFromModifiers(userDeclaredMethod.Modifiers)) .WithParameterList(ParameterList(SeparatedList(stub.StubParameters))) - .WithBody(Block(UnsafeStatement(stubCode))); + .WithBody(stubCode.WrapInUnsafeBlock()); } private static LibraryImportCompilationData? ProcessLibraryImportAttribute(AttributeData attrData) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/CompilationExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/CompilationExtensions.cs index ba38c711698f7b..d615fc5317eb6d 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/CompilationExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/CompilationExtensions.cs @@ -22,6 +22,13 @@ public static EnvironmentFlags GetEnvironmentFlags(this Compilation compilation) { flags |= EnvironmentFlags.DisableRuntimeMarshalling; } + // Roslyn does not expose the memory safety rules version through a public API yet + // (https://github.com/dotnet/roslyn/issues/82546), so the same feature flag the compiler itself + // reads is used. Parse options are per-project, so any tree answers for the whole compilation. + if (compilation.SyntaxTrees.FirstOrDefault()?.Options.Features.ContainsKey("updated-memory-safety-rules") == true) + { + flags |= EnvironmentFlags.UpdatedMemorySafetyRules; + } return flags; } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ContainingSyntaxContext.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ContainingSyntaxContext.cs index 308af93e44083b..a64ad439de0afa 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ContainingSyntaxContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ContainingSyntaxContext.cs @@ -123,16 +123,37 @@ public MemberDeclarationSyntax WrapMemberInContainingSyntax(MemberDeclarationSyn return wrappedMember; } - public MemberDeclarationSyntax WrapMembersInContainingSyntaxWithUnsafeModifier(params MemberDeclarationSyntax[] members) + /// + /// Wraps in their containing types and namespace, reproducing the containing + /// types with the modifiers the user wrote and nothing more. + /// + /// + /// Suitable when every generated member that names a pointer type carries its own unsafe modifier + /// or sits inside an unsafe block, which makes the containing type's modifier unnecessary under + /// the legacy rules and meaningless under the updated ones. + /// + public MemberDeclarationSyntax WrapMembersInContainingSyntax(params MemberDeclarationSyntax[] members) + => WrapMembersInContainingSyntaxWithUnsafeModifier(useUpdatedMemorySafetyRules: true, members); + + /// + /// Wraps in their containing types and namespace, adding an unsafe + /// modifier to the containing types unless . + /// + /// + /// Under the legacy rules the modifier is what makes a pointer type legal to name in a generated member + /// whose own modifiers are copied from a user declaration that carries unsafe elsewhere. Under the + /// updated rules a pointer type needs no unsafe context to be named and the modifier has no effect at + /// all, so emitting it would only produce CS9377 in code the user cannot edit. + /// + public MemberDeclarationSyntax WrapMembersInContainingSyntaxWithUnsafeModifier(bool useUpdatedMemorySafetyRules, params MemberDeclarationSyntax[] members) { - bool addedUnsafe = false; MemberDeclarationSyntax? wrappedMember = null; foreach (var containingType in ContainingSyntax) { TypeDeclarationSyntax type = TypeDeclaration(containingType.TypeKind, containingType.Identifier) .WithModifiers(containingType.Modifiers) .AddMembers(wrappedMember is not null ? new[] { wrappedMember } : members); - if (!addedUnsafe) + if (!useUpdatedMemorySafetyRules) { type = type.WithModifiers(type.Modifiers.AddToModifiers(SyntaxKind.UnsafeKeyword)); } @@ -149,7 +170,8 @@ public MemberDeclarationSyntax WrapMembersInContainingSyntaxWithUnsafeModifier(p return wrappedMember; } - public void WriteToWithUnsafeModifier(IndentedTextWriter writer, TState writeMembersState, Action writeMembers) + /// + public void WriteToWithUnsafeModifier(bool useUpdatedMemorySafetyRules, IndentedTextWriter writer, TState writeMembersState, Action writeMembers) { if (ContainingNamespace is not null) { @@ -167,7 +189,10 @@ public void WriteToWithUnsafeModifier(IndentedTextWriter writer, TState { ContainingSyntax syntax = ContainingSyntax[i]; - writer.WriteLine($"{string.Join(" ", syntax.Modifiers.AddToModifiers(SyntaxKind.UnsafeKeyword))} {syntax.TypeKind.GetDeclarationKeyword()} {syntax.Identifier}{syntax.TypeParameters}"); + SyntaxTokenList modifiers = useUpdatedMemorySafetyRules + ? syntax.Modifiers + : syntax.Modifiers.AddToModifiers(SyntaxKind.UnsafeKeyword); + writer.WriteLine($"{string.Join(" ", modifiers)} {syntax.TypeKind.GetDeclarationKeyword()} {syntax.Identifier}{syntax.TypeParameters}"); writer.WriteLine('{'); writer.Indent++; } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs index 5db454c730e068..4c0c1196763a02 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs @@ -35,7 +35,18 @@ public static IncrementalValueProvider CreateEnvironmentFlagsP .Collect() .Select((topLevelAttrs, ct) => !topLevelAttrs.IsEmpty ? EnvironmentFlags.DisableRuntimeMarshalling : EnvironmentFlags.None); - return isModuleSkipLocalsInit.Combine(disabledRuntimeMarshalling).Select((data, ct) => data.Left | data.Right); + // Roslyn does not expose the memory safety rules version through a public API yet + // (https://github.com/dotnet/roslyn/issues/82546), so the same feature flag the compiler itself + // reads is used to determine whether the updated rules are in effect. + var updatedMemorySafetyRules = context.ParseOptionsProvider + .Select((options, ct) => options.Features.ContainsKey("updated-memory-safety-rules") + ? EnvironmentFlags.UpdatedMemorySafetyRules + : EnvironmentFlags.None); + + return isModuleSkipLocalsInit + .Combine(disabledRuntimeMarshalling) + .Combine(updatedMemorySafetyRules) + .Select((data, ct) => data.Left.Left | data.Left.Right | data.Right); } public static IncrementalValueProvider CreateStubEnvironmentProvider(this IncrementalGeneratorInitializationContext context) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StubEnvironment.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StubEnvironment.cs index 249be13c1de92c..5c25bf2e4715e5 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StubEnvironment.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StubEnvironment.cs @@ -12,6 +12,11 @@ public enum EnvironmentFlags None = 0, SkipLocalsInit = 0x1, DisableRuntimeMarshalling = 0x2, + /// + /// The compilation uses the updated memory safety rules ("unsafe evolution"), under which an + /// unsafe modifier on a type has no effect and pointer types need no unsafe context to be named. + /// + UpdatedMemorySafetyRules = 0x4, } public sealed record StubEnvironment( diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SyntaxExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SyntaxExtensions.cs index 737d266acc4c22..e64cfe551fef87 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SyntaxExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SyntaxExtensions.cs @@ -95,6 +95,18 @@ public static SyntaxTokenList StripAccessibilityModifiers(this SyntaxTokenList t return new SyntaxTokenList(strippedTokens); } + /// + /// Wraps a generated method body in an unsafe block. + /// + /// + /// An unsafe modifier on the containing type makes pointer types legal to name, but it does not + /// give the code inside an unsafe context under the updated memory safety rules ("unsafe evolution"). + /// Generated bodies that dereference pointers, take addresses or call a caller-unsafe API therefore + /// establish the context themselves, which is valid under both the legacy and the updated rules. + /// + public static BlockSyntax WrapInUnsafeBlock(this BlockSyntax body) + => SyntaxFactory.Block(SyntaxFactory.UnsafeStatement(body)); + public static SyntaxTokenList AddToModifiers(this SyntaxTokenList modifiers, SyntaxKind modifierToAdd) { if (modifiers.IndexOf(modifierToAdd) >= 0) diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj index 9ce73c44b35c9e..d41cb8ab349595 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs index aacebfe155ad18..a4f9513baa6d4e 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs @@ -368,7 +368,10 @@ static void VerifyCompilation(Compilation comp) var accessors = comp.SyntaxTrees .SelectMany(t => t.GetRoot().DescendantNodes().OfType()) .Where(a => a.Kind() is SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration - && a.Body is not null) + && a.Body is not null + // Only the generated stubs carry these attributes. Other generated types have + // accessors of their own, such as the vtable pointer on InterfaceInformation. + && a.Ancestors().OfType().First().Identifier.Text == "InterfaceImplementation") .ToList(); Assert.Equal(4, accessors.Count); diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/UnsafeCodeGeneration.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/UnsafeCodeGeneration.cs new file mode 100644 index 00000000000000..49c39c4cfecc1a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/UnsafeCodeGeneration.cs @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.Interop; +using Xunit; + +namespace ComInterfaceGenerator.Unit.Tests +{ + /// + /// Verifies that the generated output compiles under the updated memory safety rules ("unsafe evolution"), + /// where an unsafe modifier on a type establishes no context for the members inside it. + /// + public class UnsafeCodeGeneration + { + [Fact] + public async Task ComInterfaceOutputCompilesUnderUpdatedRules() + { + string source = """ + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] + partial interface INativeAPI + { + void Method(); + int MethodWithArgs(int a, string s); + int Property { get; set; } + } + """; + + await new UpdatedRulesTest + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }.RunAsync(); + } + + [Fact] + public async Task InheritedPointerSignatureCompilesUnderUpdatedRules() + { + // The base method carries 'unsafe' on the member rather than on the type, and the derived interface + // declares none at all, so the shadowing method the generator emits into the user's own partial part + // is the one shape that cannot borrow an unsafe context from anything the user wrote. + string source = """ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] + partial interface IComInterfaceBase + { + unsafe void Method(void* pBuffer); + } + + [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541E")] + partial interface IComInterfaceDerived : IComInterfaceBase + { + void Method2(); + } + """; + + await new UpdatedRulesTest + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }.RunAsync(); + } + + [Fact] + public async Task InheritedPointerSignatureCompilesUnderLegacyRules() + { + string source = """ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] + partial interface IComInterfaceBase + { + unsafe void Method(void* pBuffer); + } + + [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541E")] + partial interface IComInterfaceDerived : IComInterfaceBase + { + void Method2(); + } + """; + + await new LegacyRulesTest + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }.RunAsync(); + } + + [Fact] + public async Task PointerSignatureWithTypeLevelUnsafeCompilesUnderLegacyRules() + { + // The user is free to put 'unsafe' on the type rather than on the member. The generated stub copies + // the member's modifiers, so it has none of its own, and under the legacy rules it still has to end + // up inside some unsafe context. + string source = """ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] + unsafe partial interface IComInterfaceBase + { + void Method(void* pBuffer); + } + """; + + await new LegacyRulesTest + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }.RunAsync(); + } + + /// + /// Runs without the updated memory safety rules, so that the output is + /// checked against the rules today's users actually compile with. + /// + private sealed class LegacyRulesTest + : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier< + TGenerator, Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer>.Test + where TGenerator : new() + { + public LegacyRulesTest() + : base(referenceAncillaryInterop: true) + { + } + } + + [Fact] + public async Task ComClassOutputCompilesUnderUpdatedRules() + { + // The COM class output comes from a different generator than the interface output, so it needs its + // own verifier to be exercised at all. + string source = """ + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] + partial interface INativeAPI + { + void Method(); + } + + [GeneratedComClass] + partial class C : INativeAPI + { + public void Method() { } + } + """; + + await new UpdatedRulesTest + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }.RunAsync(); + } + + [Fact] + public async Task VtableIndexStubOutputCompilesUnderUpdatedRules() + { + string source = """ + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [UnmanagedObjectUnwrapper] + partial interface INativeAPI : IUnmanagedInterfaceType + { + static unsafe void* IUnmanagedInterfaceType.VirtualMethodTableManagedImplementation => null; + [VirtualMethodIndex(0)] + void Method(); + [VirtualMethodIndex(1)] + int MethodWithArgs(int a); + } + """; + + await new UpdatedRulesTest + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }.RunAsync(); + } + + /// + /// Runs with the updated memory safety rules enabled, so that any + /// generated member left without an unsafe context of its own fails the test. + /// + private sealed class UpdatedRulesTest + : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier< + TGenerator, Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer>.Test + where TGenerator : new() + { + public UpdatedRulesTest() + : base(referenceAncillaryInterop: true) + { + // CS9377 ("the 'unsafe' modifier does not have any effect here") reports an ineffective modifier + // on a generated type, which is the whole point of these tests. It sits above the test + // framework's default warning level, so without this it could never be observed. + SolutionTransforms.Add(static (solution, projectId) => + { + var options = (CSharpCompilationOptions)solution.GetProject(projectId)!.CompilationOptions!; + return solution.WithProjectCompilationOptions(projectId, options.WithWarningLevel(9999)); + }); + } + } + } +}