From 96cc3c6a336e34d3dd9927469b16e8c294592f83 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Aug 2026 17:57:05 +0000
Subject: [PATCH 1/2] Initial plan
From b594430b3673ba34c2465463def9d5cd045c0427 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Aug 2026 18:04:28 +0000
Subject: [PATCH 2/2] Fix CoreCLR PInvokeCollector versioned OS platform
matching, add regression test
Co-authored-by: akoeplinger <1376924+akoeplinger@users.noreply.github.com>
---
.../PInvokeTableGeneratorTests.cs | 37 +++++++++++++++++++
.../PInvoke/VersionedOSPlatform.cs | 26 +++++++++++++
.../native-libs/versioned-osplatform.c | 4 ++
.../coreclr/PInvokeCollector.cs | 20 +++++++++-
4 files changed, 85 insertions(+), 2 deletions(-)
create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/VersionedOSPlatform.cs
create mode 100644 src/mono/wasm/testassets/native-libs/versioned-osplatform.c
diff --git a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs
index c64eefc6650922..0c7fe35ecdf6e3 100644
--- a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs
@@ -442,6 +442,43 @@ public void UnsupportedOSPlatformPInvokeIsSkipped(Configuration config, bool aot
Assert.DoesNotContain("WASM0062", output);
}
+ [Theory]
+ [BuildAndRun(aot: false)]
+ [TestCategory("native-mono")]
+ public async Task VersionedOSPlatformPInvokeIsIncluded(Configuration config, bool aot)
+ {
+ // Regression coverage for https://github.com/dotnet/runtime/issues/132297:
+ // a versioned platform attribute like [SupportedOSPlatform("browser1.0")] must still
+ // be treated as matching TargetOS=browser, so the pinvoke must be kept (not silently
+ // filtered out), while a versioned attribute for a different OS (e.g. "windows1.0")
+ // must still be filtered out.
+ string extraItems = @"";
+ ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "versioned_osplatform_pinvoke",
+ extraItems: extraItems, extraProperties: "true");
+ ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "VersionedOSPlatform.cs"));
+ File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "versioned-osplatform.c"), Path.Combine(_projectDir, "versioned-osplatform.c"));
+
+ (_, string output) = BuildProject(info, config, new BuildOptions(AssertAppBundle: false, AOT: aot), isNativeBuild: true);
+ Assert.DoesNotContain("WASM0001", output);
+
+ string objDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFramework, "wasm", "for-build");
+ string pinvokeTableFileName = IsCoreClrRuntime ? "callhelpers-pinvoke.cpp" : "pinvoke-table.h";
+ string pinvokeTable = File.ReadAllText(Path.Combine(objDir, pinvokeTableFileName));
+
+ string includedPInvokeTableEntry = IsCoreClrRuntime
+ ? "DllImportEntry(versioned_browser_add)"
+ : "\"versioned_browser_add\", versioned_browser_add";
+ Assert.Contains(includedPInvokeTableEntry, pinvokeTable);
+
+ string excludedPInvokeTableEntry = IsCoreClrRuntime
+ ? "DllImportEntry(versioned_windows_add)"
+ : "\"versioned_windows_add\", versioned_windows_add";
+ Assert.DoesNotContain(excludedPInvokeTableEntry, pinvokeTable);
+
+ RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 42));
+ Assert.Contains("sum: 42", result.TestOutput);
+ }
+
[Theory]
[BuildAndRun(aot: true, config: Configuration.Release)]
[TestCategory("native-mono")]
diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/VersionedOSPlatform.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/VersionedOSPlatform.cs
new file mode 100644
index 00000000000000..a339d9ba8a7121
--- /dev/null
+++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/VersionedOSPlatform.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+Console.WriteLine($"TestOutput -> sum: {VersionedBrowserInterop.versioned_browser_add(19, 23)}");
+return 42;
+
+// Regression coverage for https://github.com/dotnet/runtime/issues/132297:
+// a versioned platform attribute like [SupportedOSPlatform("browser1.0")] must still match
+// TargetOS=browser, so this pinvoke must be included in the generated pinvoke table and
+// callable at runtime.
+[SupportedOSPlatform("browser1.0")]
+internal static class VersionedBrowserInterop
+{
+ [DllImport("versioned-osplatform")]
+ public static extern int versioned_browser_add(int a, int b);
+}
+
+// A versioned platform attribute for a different OS must not match TargetOS=browser, so this
+// pinvoke must be skipped and must not appear in the generated pinvoke table.
+[SupportedOSPlatform("windows1.0")]
+internal static class VersionedWindowsInterop
+{
+ [DllImport("versioned-osplatform")]
+ public static extern int versioned_windows_add(int a, int b);
+}
diff --git a/src/mono/wasm/testassets/native-libs/versioned-osplatform.c b/src/mono/wasm/testassets/native-libs/versioned-osplatform.c
new file mode 100644
index 00000000000000..1868b6fc2456c8
--- /dev/null
+++ b/src/mono/wasm/testassets/native-libs/versioned-osplatform.c
@@ -0,0 +1,4 @@
+int versioned_browser_add(int a, int b)
+{
+ return a + b;
+}
diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs
index 150a2e50361454..ba59810c1b54c8 100644
--- a/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs
+++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs
@@ -291,7 +291,7 @@ private PlatformSupport EvaluatePlatformAttributes(IList at
{
if (cattr.AttributeType.FullName == "System.Runtime.Versioning.UnsupportedOSPlatformAttribute" &&
cattr.ConstructorArguments.Count > 0 &&
- cattr.ConstructorArguments[0].Value?.ToString() == _targetOS)
+ MatchesTargetOS(cattr.ConstructorArguments[0].Value?.ToString()))
{
return PlatformSupport.Unsupported;
}
@@ -299,7 +299,7 @@ private PlatformSupport EvaluatePlatformAttributes(IList at
cattr.ConstructorArguments.Count > 0)
{
hasSupportedOSPlatform = true;
- if (cattr.ConstructorArguments[0].Value?.ToString() == _targetOS)
+ if (MatchesTargetOS(cattr.ConstructorArguments[0].Value?.ToString()))
hasSupportedTarget = true;
}
}
@@ -314,6 +314,22 @@ private PlatformSupport EvaluatePlatformAttributes(IList at
return PlatformSupport.Unknown;
}
+
+ private bool MatchesTargetOS(string? platformName)
+ {
+ if (string.Equals(platformName, _targetOS, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ if (platformName?.StartsWith(_targetOS, StringComparison.OrdinalIgnoreCase) != true)
+ return false;
+
+#if NETFRAMEWORK
+ string version = platformName.Substring(_targetOS.Length);
+#else
+ ReadOnlySpan version = platformName.AsSpan(_targetOS.Length);
+#endif
+ return Version.TryParse(version, out _);
+ }
}
internal sealed class PInvokeCallbackComparer : IComparer