Skip to content

Commit 02bc0c2

Browse files
jevansaksCopilot
andauthored
Fix cross-winmd STATUS_SUCCESS lookup (#1678)
* Fix cross-winmd STATUS_SUCCESS lookup Add regression coverage for WNF metadata that returns NTSTATUS from a SafeHandle release method but relies on STATUS_SUCCESS from Windows.Win32 metadata. When a helper constant is missing from the active metadata file, fall back to the SuperGenerator inputs so SafeHandle generation can complete. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add WNF repro IL source Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Minify WNF repro metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a4369d5 commit 02bc0c2

8 files changed

Lines changed: 103 additions & 3 deletions

File tree

src/Microsoft.Windows.CsWin32/Generator.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,10 +1522,17 @@ private void TryGenerateTypeOrThrow(string possiblyQualifiedName)
15221522

15231523
private void TryGenerateConstantOrThrow(string possiblyQualifiedName)
15241524
{
1525-
if (!this.TryGenerateConstant(possiblyQualifiedName, out _))
1525+
if (this.TryGenerateConstant(possiblyQualifiedName, out _))
15261526
{
1527-
throw new GenerationFailedException("Unable to find expected constant: " + possiblyQualifiedName);
1527+
return;
1528+
}
1529+
1530+
if (this.SuperGenerator?.TryGenerateConstant(possiblyQualifiedName, out _) is true)
1531+
{
1532+
return;
15281533
}
1534+
1535+
throw new GenerationFailedException("Unable to find expected constant: " + possiblyQualifiedName);
15291536
}
15301537

15311538
private MethodDeclarationSyntax CreateAsSpanMethodOverValueAndLength(TypeSyntax spanType)

src/Microsoft.Windows.CsWin32/SuperGenerator.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,20 @@ public void GenerateIDispatch()
241241
}
242242
}
243243

244+
internal bool TryGenerateConstant(string possiblyQualifiedName, out IReadOnlyCollection<string> preciseApi)
245+
{
246+
List<string> preciseApiAccumulator = new();
247+
bool success = false;
248+
foreach (Generator generator in this.Generators.Values)
249+
{
250+
success |= generator.TryGenerateConstant(possiblyQualifiedName, out preciseApi);
251+
preciseApiAccumulator.AddRange(preciseApi);
252+
}
253+
254+
preciseApi = preciseApiAccumulator;
255+
return success;
256+
}
257+
244258
/// <summary>
245259
/// Looks up the <see cref="Generator"/> that owns a referenced type.
246260
/// </summary>

test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,17 @@ public async Task CrossWinMD_IInspectable(
606606
await this.InvokeGeneratorAndCompile($"{nameof(this.CrossWinMD_IInspectable)}_{tfm}_{allowMarshaling}_{pinvokeClassName ?? "null"}");
607607
}
608608

609+
[Fact]
610+
public async Task CrossWinMD_NTSTATUSSafeHandleConstant()
611+
{
612+
this.compilation = this.starterCompilations["net8.0"];
613+
this.win32winmdPaths = [WnfWithoutStatusSuccessMetadataPath, .. this.win32winmdPaths!];
614+
this.nativeMethods.Add("RtlSubscribeWnfStateChangeNotification");
615+
await this.InvokeGeneratorAndCompileFromFact();
616+
617+
Assert.Contains(this.FindGeneratedType("RtlUnsubscribeWnfStateChangeNotificationSafeHandle"), static type => type is ClassDeclarationSyntax);
618+
}
619+
609620
[Fact]
610621
public async Task TestComVariantReturnValue()
611622
{
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
This folder contains winmd files obtained from other sources which we use for testing.
1+
This folder contains winmd files we use for testing. Some come from external sources, and some are small authored fixtures kept in source form in this repository.
22

33
Metadata | Source
44
--|--
55
ServiceFabric.winmd | [youyuanwu/fabric-metadata](https://github.com/youyuanwu/fabric-metadata/raw/a1bcca6ad6f6a772c9e5ff4bdba80ae5e5f24cfc/.windows/winmd/ServiceFabric.winmd)
66
CustomIInspectable.winmd | Generated using WinMDGenerator toolchain. Project in the subdirectory [CustomIInspectable](../../CustomIInspectable) with build instructions in [readme.md](../../CustomIInspectable/readme.md).
7+
WnfWithoutStatusSuccess.winmd | Authored fixture for issue #1677 regression coverage. Its source is checked in as [WnfWithoutStatusSuccess.il](WnfWithoutStatusSuccess.il). We keep this one as IL because that is much easier to maintain than reproducing the full end-to-end WinmdGenerator workflow for a focused test asset.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Source for WnfWithoutStatusSuccess.winmd.
2+
// This intentionally keeps only the minimal metadata required to reproduce
3+
// issue #1677: one WNF API, its NTSTATUS-based release method, and the
4+
// handle typedef that points to that release method.
5+
6+
.module extern ntdll.dll
7+
8+
.assembly extern Windows.Win32.winmd
9+
{
10+
.ver 0:0:0:0
11+
}
12+
13+
.assembly extern netstandard
14+
{
15+
.publickeytoken = (CC 7B 13 FF CD 2D DD 51 )
16+
.ver 2:1:0:0
17+
}
18+
19+
.assembly WnfWithoutStatusSuccess
20+
{
21+
.ver 1:0:0:0
22+
}
23+
24+
.module WnfWithoutStatusSuccess.winmd
25+
.imagebase 0x00400000
26+
.file alignment 0x00000200
27+
.stackreserve 0x00100000
28+
.subsystem 0x0003
29+
.corflags 0x00000001
30+
31+
.class public abstract auto autochar sealed beforefieldinit WNF.Apis
32+
extends [netstandard]System.Object
33+
{
34+
.method public hidebysig static pinvokeimpl("ntdll.dll" nomangle winapi)
35+
valuetype [Windows.Win32.winmd]Windows.Win32.Foundation.NTSTATUS
36+
RtlSubscribeWnfStateChangeNotification([out] valuetype WNF.Types.PWNF_USER_SUBSCRIPTION* Subscription) cil managed
37+
{
38+
}
39+
40+
.method public hidebysig static pinvokeimpl("ntdll.dll" nomangle winapi)
41+
valuetype [Windows.Win32.winmd]Windows.Win32.Foundation.NTSTATUS
42+
RtlUnsubscribeWnfStateChangeNotification([in] valuetype WNF.Types.PWNF_USER_SUBSCRIPTION Subscription) cil managed
43+
{
44+
}
45+
} // end of class WNF.Apis
46+
47+
.class public sequential ansi sealed beforefieldinit WNF.Types.PWNF_USER_SUBSCRIPTION
48+
extends [netstandard]System.ValueType
49+
{
50+
.custom instance void [Windows.Win32.winmd]Windows.Win32.Foundation.Metadata.RAIIFreeAttribute::.ctor(string) = ( 01 00 28 52 74 6C 55 6E 73 75 62 73 63 72 69 62 // ..(RtlUnsubscrib
51+
65 57 6E 66 53 74 61 74 65 43 68 61 6E 67 65 4E // eWnfStateChangeN
52+
6F 74 69 66 69 63 61 74 69 6F 6E 00 00 ) // otification..
53+
.custom instance void [Windows.Win32.winmd]Windows.Win32.Foundation.Metadata.InvalidHandleValueAttribute::.ctor(int64) = ( 01 00 00 00 00 00 00 00 00 00 00 00 )
54+
.custom instance void [Windows.Win32.winmd]Windows.Win32.Foundation.Metadata.NativeTypedefAttribute::.ctor() = ( 01 00 00 00 )
55+
.field public void* Value
56+
} // end of class WNF.Types.PWNF_USER_SUBSCRIPTION
Binary file not shown.

test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public abstract class GeneratorTestBase : IDisposable, IAsyncLifetime
1414
////protected static readonly string DiaMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "Microsoft.Dia.winmd");
1515
protected static readonly string ServiceFabricMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "ServiceFabric.winmd");
1616
protected static readonly string CustomIInspectableMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "CustomIInspectable.winmd");
17+
protected static readonly string WnfWithoutStatusSuccessMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "WnfWithoutStatusSuccess.winmd");
1718
protected static readonly string ApiDocsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "apidocs.msgpack");
1819

1920
protected readonly ITestOutputHelper logger;

test/Microsoft.Windows.CsWin32.Tests/MultiMetadataTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,14 @@ public void CrossWinMD_IInspectable(
3131
this.generator = this.CreateSuperGenerator([.. DefaultMetadataPaths, CustomIInspectableMetadataPath], options);
3232
this.GenerateApi("ITestDerivedFromInspectable");
3333
}
34+
35+
[Fact]
36+
public void CrossWinMD_NTSTATUSSafeHandleConstant()
37+
{
38+
this.generator = this.CreateSuperGenerator([WnfWithoutStatusSuccessMetadataPath, MetadataPath], DefaultTestGeneratorOptions);
39+
this.GenerateApi("RtlSubscribeWnfStateChangeNotification");
40+
41+
Assert.Contains(this.FindGeneratedType("RtlUnsubscribeWnfStateChangeNotificationSafeHandle"), static type => type is ClassDeclarationSyntax);
42+
Assert.NotEmpty(this.FindGeneratedConstant("STATUS_SUCCESS"));
43+
}
3444
}

0 commit comments

Comments
 (0)