Skip to content

Commit 09784cd

Browse files
authored
[release/8.0-staging] [LibraryImportGenerator] Use basic forwarder in down-level support if any parameters can't be marshalled (#104501)
1 parent 524fdcf commit 09784cd

File tree

6 files changed

+55
-84
lines changed

6 files changed

+55
-84
lines changed

src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
66
<IsPackable>true</IsPackable>
77
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
8-
<ServicingVersion>1</ServicingVersion>
8+
<ServicingVersion>2</ServicingVersion>
99
<PackageDescription>Provides a message handler for HttpClient based on the WinHTTP interface of Windows. While similar to HttpClientHandler, it provides developers more granular control over the application's HTTP communication than the HttpClientHandler.
1010

1111
Commonly Used Types:

src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public PInvokeStubCodeGenerator(
8080
}
8181

8282
bool noMarshallingNeeded = true;
83-
83+
bool hasForwardedTypes = false;
8484
foreach (BoundGenerator generator in _marshallers.SignatureMarshallers)
8585
{
8686
// Check if marshalling info and generator support the current target framework.
@@ -90,6 +90,19 @@ public PInvokeStubCodeGenerator(
9090
// Check if generator is either blittable or just a forwarder.
9191
noMarshallingNeeded &= generator is { Generator: BlittableMarshaller, TypeInfo.IsByRef: false }
9292
or { Generator: Forwarder };
93+
94+
// Track if any generators are just forwarders - for types other than void, this indicates
95+
// types that can't be marshalled by the source generated.
96+
// In .NET 7+ support, we would have emitted a diagnostic error about lack of support
97+
// In down-level support, we do not error - tracking this allows us to switch to generating a basic forwarder (DllImport declaration)
98+
hasForwardedTypes |= generator is { Generator: Forwarder, TypeInfo.ManagedType: not SpecialTypeInfo { SpecialType: Microsoft.CodeAnalysis.SpecialType.System_Void } };
99+
}
100+
101+
// For down-level support, if some parameters cannot be marshalled, consider the target framework as not supported
102+
if (hasForwardedTypes
103+
&& (targetFramework != TargetFramework.Net || targetFrameworkVersion.Major < 7))
104+
{
105+
SupportsTargetFramework = false;
93106
}
94107

95108
StubIsBasicForwarder = !setLastError

src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs

-74
Original file line numberDiff line numberDiff line change
@@ -331,80 +331,6 @@ await VerifySourceGeneratorAsync(
331331
});
332332
}
333333

334-
[Fact]
335-
[OuterLoop("Uses the network for downlevel ref packs")]
336-
public async Task InOutAttributes_Forwarded_To_ForwardedParameter()
337-
{
338-
// This code is invalid configuration from the source generator's perspective.
339-
// We just use it as validation for forwarding the In and Out attributes.
340-
string source = """
341-
using System.Runtime.CompilerServices;
342-
using System.Runtime.InteropServices;
343-
partial class C
344-
{
345-
[LibraryImportAttribute("DoesNotExist")]
346-
[return: MarshalAs(UnmanagedType.Bool)]
347-
public static partial bool Method1([In, Out] int {|SYSLIB1051:a|});
348-
}
349-
""" + CodeSnippets.LibraryImportAttributeDeclaration;
350-
351-
await VerifySourceGeneratorAsync(
352-
source,
353-
(targetMethod, newComp) =>
354-
{
355-
INamedTypeSymbol marshalAsAttribute = newComp.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_MarshalAsAttribute)!;
356-
INamedTypeSymbol inAttribute = newComp.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_InAttribute)!;
357-
INamedTypeSymbol outAttribute = newComp.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_OutAttribute)!;
358-
Assert.Collection(targetMethod.Parameters,
359-
param => Assert.Collection(param.GetAttributes(),
360-
attr =>
361-
{
362-
Assert.Equal(inAttribute, attr.AttributeClass, SymbolEqualityComparer.Default);
363-
Assert.Empty(attr.ConstructorArguments);
364-
Assert.Empty(attr.NamedArguments);
365-
},
366-
attr =>
367-
{
368-
Assert.Equal(outAttribute, attr.AttributeClass, SymbolEqualityComparer.Default);
369-
Assert.Empty(attr.ConstructorArguments);
370-
Assert.Empty(attr.NamedArguments);
371-
}));
372-
},
373-
TestTargetFramework.Standard);
374-
}
375-
376-
[Fact]
377-
[OuterLoop("Uses the network for downlevel ref packs")]
378-
public async Task MarshalAsAttribute_Forwarded_To_ForwardedParameter()
379-
{
380-
string source = """
381-
using System.Runtime.CompilerServices;
382-
using System.Runtime.InteropServices;
383-
partial class C
384-
{
385-
[LibraryImportAttribute("DoesNotExist")]
386-
[return: MarshalAs(UnmanagedType.Bool)]
387-
public static partial bool Method1([MarshalAs(UnmanagedType.I2)] int a);
388-
}
389-
""" + CodeSnippets.LibraryImportAttributeDeclaration;
390-
391-
await VerifySourceGeneratorAsync(
392-
source,
393-
(targetMethod, newComp) =>
394-
{
395-
INamedTypeSymbol marshalAsAttribute = newComp.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_MarshalAsAttribute)!;
396-
Assert.Collection(targetMethod.Parameters,
397-
param => Assert.Collection(param.GetAttributes(),
398-
attr =>
399-
{
400-
Assert.Equal(marshalAsAttribute, attr.AttributeClass, SymbolEqualityComparer.Default);
401-
Assert.Equal(UnmanagedType.I2, (UnmanagedType)attr.ConstructorArguments[0].Value!);
402-
Assert.Empty(attr.NamedArguments);
403-
}));
404-
},
405-
TestTargetFramework.Standard);
406-
}
407-
408334
private static Task VerifySourceGeneratorAsync(string source, Action<IMethodSymbol, Compilation> targetPInvokeAssertion, TestTargetFramework targetFramework = TestTargetFramework.Net)
409335
{
410336
var test = new GeneratedTargetPInvokeTest(targetPInvokeAssertion, targetFramework)

src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -756,13 +756,16 @@ partial class Test
756756
}
757757
""";
758758

759-
public static string BasicReturnAndParameterByValue(string returnType, string parameterType, string preDeclaration = "") => $$"""
759+
/// <summary>
760+
/// Declaration with a non-blittable parameter that is always supported for marshalling
761+
/// </summary>
762+
public static string BasicReturnAndParameterWithAlwaysSupportedParameter(string returnType, string parameterType, string preDeclaration = "") => $$"""
760763
using System.Runtime.InteropServices;
761764
{{preDeclaration}}
762765
partial class Test
763766
{
764767
[LibraryImport("DoesNotExist")]
765-
public static partial {{returnType}} Method({{parameterType}} p);
768+
public static partial {{returnType}} Method({{parameterType}} p, out int i);
766769
}
767770
""";
768771

src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs

+25-2
Original file line numberDiff line numberDiff line change
@@ -517,9 +517,32 @@ public static IEnumerable<object[]> CodeSnippetsToValidateFallbackForwarder()
517517
yield return new object[] { ID(), code, TestTargetFramework.Framework, true };
518518
}
519519

520-
// Confirm that if support is missing for any type (like arrays), we fall back to a forwarder even if other types are supported.
520+
// Confirm that if support is missing for a type with a ITypeBasedMarshallingInfoProvider (like arrays), we fall back to a forwarder even if other types are supported.
521521
{
522-
string code = CodeSnippets.BasicReturnAndParameterByValue("System.Runtime.InteropServices.SafeHandle", "int[]", CodeSnippets.LibraryImportAttributeDeclaration);
522+
string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "int[]", CodeSnippets.LibraryImportAttributeDeclaration);
523+
yield return new object[] { ID(), code, TestTargetFramework.Net6, true };
524+
yield return new object[] { ID(), code, TestTargetFramework.Core, true };
525+
yield return new object[] { ID(), code, TestTargetFramework.Standard, true };
526+
yield return new object[] { ID(), code, TestTargetFramework.Framework, true };
527+
}
528+
{
529+
string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("int", "int[]", CodeSnippets.LibraryImportAttributeDeclaration);
530+
yield return new object[] { ID(), code, TestTargetFramework.Net6, true };
531+
yield return new object[] { ID(), code, TestTargetFramework.Core, true };
532+
yield return new object[] { ID(), code, TestTargetFramework.Standard, true };
533+
yield return new object[] { ID(), code, TestTargetFramework.Framework, true };
534+
}
535+
536+
// Confirm that if support is missing for a type without a ITypeBasedMarshallingInfoProvider (like StringBuilder), we fall back to a forwarder even if other types are supported.
537+
{
538+
string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "System.Text.StringBuilder", CodeSnippets.LibraryImportAttributeDeclaration);
539+
yield return new object[] { ID(), code, TestTargetFramework.Net6, true };
540+
yield return new object[] { ID(), code, TestTargetFramework.Core, true };
541+
yield return new object[] { ID(), code, TestTargetFramework.Standard, true };
542+
yield return new object[] { ID(), code, TestTargetFramework.Framework, true };
543+
}
544+
{
545+
string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("int", "System.Text.StringBuilder", CodeSnippets.LibraryImportAttributeDeclaration);
523546
yield return new object[] { ID(), code, TestTargetFramework.Net6, true };
524547
yield return new object[] { ID(), code, TestTargetFramework.Core, true };
525548
yield return new object[] { ID(), code, TestTargetFramework.Standard, true };

src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs

+10-4
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ partial class Test
250250
public static partial void {|#0:Method1|}(string s);
251251
252252
[LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Native))]
253-
public static partial void Method2(string {|#1:s|});
253+
public static partial void {|#2:Method2|}(string {|#1:s|});
254254
255255
struct Native
256256
{
@@ -266,7 +266,13 @@ public Native(string s) { }
266266
.WithArguments($"{nameof(TypeNames.LibraryImportAttribute)}{Type.Delimiter}{nameof(StringMarshalling)}={nameof(StringMarshalling)}{Type.Delimiter}{nameof(StringMarshalling.Utf8)}"),
267267
VerifyCS.Diagnostic(GeneratorDiagnostics.ParameterTypeNotSupportedWithDetails)
268268
.WithLocation(1)
269-
.WithArguments("Marshalling string or char without explicit marshalling information is not supported. Specify 'LibraryImportAttribute.StringMarshalling', 'LibraryImportAttribute.StringMarshallingCustomType', 'MarshalUsingAttribute' or 'MarshalAsAttribute'.", "s")
269+
.WithArguments("Marshalling string or char without explicit marshalling information is not supported. Specify 'LibraryImportAttribute.StringMarshalling', 'LibraryImportAttribute.StringMarshallingCustomType', 'MarshalUsingAttribute' or 'MarshalAsAttribute'.", "s"),
270+
VerifyCS.Diagnostic(GeneratorDiagnostics.CannotForwardToDllImport)
271+
.WithLocation(2)
272+
.WithArguments($"{nameof(TypeNames.LibraryImportAttribute)}{Type.Delimiter}{nameof(StringMarshalling)}={nameof(StringMarshalling)}{Type.Delimiter}{nameof(StringMarshalling.Custom)}"),
273+
VerifyCS.Diagnostic(GeneratorDiagnostics.CannotForwardToDllImport)
274+
.WithLocation(2)
275+
.WithArguments($"{nameof(TypeNames.LibraryImportAttribute)}{Type.Delimiter}{nameof(LibraryImportAttribute.StringMarshallingCustomType)}")
270276
};
271277

272278
var test = new VerifyCS.Test(TestTargetFramework.Standard)
@@ -289,10 +295,10 @@ partial class Test
289295
{
290296
[{|#0:LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Custom)|}]
291297
public static partial void Method1(out int i);
292-
298+
293299
[{|#1:LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf8, StringMarshallingCustomType = typeof(Native))|}]
294300
public static partial void Method2(out int i);
295-
301+
296302
struct Native
297303
{
298304
public Native(string s) { }

0 commit comments

Comments
 (0)