Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,23 @@ public async Task EnsureWasmAbiRulesAreFollowedInAOT(Configuration config, bool
public async Task EnsureWasmAbiRulesAreFollowedInInterpreter(Configuration config, bool aot) =>
await EnsureWasmAbiRulesAreFollowed(config, aot);

[Theory]
[BuildAndRun(aot: false)]
[TestCategory("native-mono")]
public void UnsupportedOSPlatformPInvokeIsSkipped(Configuration config, bool aot)
{
// https://github.com/dotnet/runtime/issues/110870: a Windows-only pinvoke with
// non-blittable parameters must be skipped (not analyzed) when building for the
// browser, so it must not emit WASM0060/WASM0062/WASM0001.
ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "osplatform_pinvoke",
extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnsupportedOSPlatform.cs"));
(_, string output) = BuildProject(info, config, new BuildOptions(AssertAppBundle: false, AOT: aot), isNativeBuild: true);
Assert.DoesNotContain("WASM0001", output);
Assert.DoesNotContain("WASM0060", output);
Assert.DoesNotContain("WASM0062", output);
}

[Theory]
[BuildAndRun(aot: true, config: Configuration.Release)]
[TestCategory("native-mono")]
Expand Down
1 change: 1 addition & 0 deletions src/mono/wasm/build/WasmApp.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@
PInvokeOutputPath="$(_WasmPInvokeTablePath)"
InterpToNativeOutputPath="$(_WasmInterpToNativeTablePath)"
CacheFilePath="$(_WasmM2NCachePath)"
TargetOS="$(TargetOS)"
IsLibraryMode="$(_IsLibraryMode)">
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</ManagedToNativeGenerator>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

public class Test
{
public static int Main(string[] argv)
{
Console.WriteLine("TestOutput -> Main running");
return 42;
}
}

// Regression coverage for https://github.com/dotnet/runtime/issues/110870:
// a Windows-only P/Invoke with non-blittable parameters must be skipped by the wasm
// pinvoke collector when building for the browser, and must not emit WASM0060/WASM0062/WASM0001.
[SupportedOSPlatform("windows")]
internal static class Win32Interop
{
[DllImport("gdi32.dll")]
public static extern int GetCharABCWidthsFloat(HandleRef hdc, uint iFirst, uint iLast, [Out] ABCFLOAT[] lpABCF);

[DllImport("user32.dll")]
public static extern int MethodWithNonBlittableObject(object arg, HandleRef handle);
}

internal struct ABCFLOAT
{
#pragma warning disable CS0649 // fields are only used to describe the native struct layout
public float abcfA;
public float abcfB;
public float abcfC;
#pragma warning restore CS0649
}
19 changes: 18 additions & 1 deletion src/tasks/WasmAppBuilder/mono/ManagedToNativeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public class ManagedToNativeGenerator : Task

public bool IsLibraryMode { get; set; }

public string TargetOS { get; set; } = "browser";

private static readonly string[] s_knownTargetOSes = new[] { "browser", "wasi" };

[Output]
public string[]? FileWrites { get; private set; }

Expand All @@ -51,6 +55,19 @@ public override bool Execute()
return false;
}

if (string.IsNullOrWhiteSpace(TargetOS))
{
Log.LogError($"{nameof(ManagedToNativeGenerator)}.{nameof(TargetOS)} cannot be empty; expected one of: {string.Join(", ", s_knownTargetOSes)}");
return false;
}

TargetOS = TargetOS.Trim().ToLowerInvariant();
if (Array.IndexOf(s_knownTargetOSes, TargetOS) < 0)
{
Log.LogError($"{nameof(ManagedToNativeGenerator)}.{nameof(TargetOS)} '{TargetOS}' is not recognized; expected one of: {string.Join(", ", s_knownTargetOSes)}");
return false;
}

try
{
var logAdapter = new LogAdapter(Log);
Expand All @@ -70,7 +87,7 @@ private void ExecuteInternal(LogAdapter log)
List<string> managedAssemblies = FilterOutUnmanagedBinaries(Assemblies);
if (ShouldRun(managedAssemblies))
{
var pinvoke = new PInvokeTableGenerator(FixupSymbolName, log, IsLibraryMode);
var pinvoke = new PInvokeTableGenerator(FixupSymbolName, log, IsLibraryMode, TargetOS);
var icall = new IcallTableGenerator(RuntimeIcallTableFile, FixupSymbolName, log, isCoreClr: false);

var resolver = new PathAssemblyResolver(managedAssemblies);
Expand Down
108 changes: 107 additions & 1 deletion src/tasks/WasmAppBuilder/mono/PInvokeCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ public int GetHashCode(PInvoke pinvoke)

internal sealed class PInvokeCollector {
private readonly Dictionary<Assembly, bool> _assemblyDisableRuntimeMarshallingAttributeCache = new();
private readonly Dictionary<Type, bool> _typeUnsupportedOnPlatformCache = new();
private readonly Dictionary<Assembly, bool> _assemblyUnsupportedOnPlatformCache = new();
private readonly string _targetOS;
private LogAdapter Log { get; init; }

public PInvokeCollector(LogAdapter log)
public PInvokeCollector(LogAdapter log, string targetOS)
{
Log = log;
_targetOS = targetOS;
}

public void CollectPInvokes(List<PInvoke> pinvokes, List<PInvokeCallback> callbacks, HashSet<string> signatures, Type type)
Expand Down Expand Up @@ -102,6 +106,9 @@ void CollectPInvokesForMethod(MethodInfo method)
{
if ((method.Attributes & MethodAttributes.PinvokeImpl) != 0)
{
if (IsUnsupportedOnPlatform(method))
return;

var dllimport = method.CustomAttributes.First(attr => attr.AttributeType.Name == "DllImportAttribute");
var wasmLinkage = method.CustomAttributes.Any(attr => attr.AttributeType.Name == "WasmImportLinkageAttribute");
var module = (string)dllimport.ConstructorArguments[0].Value!;
Expand All @@ -124,6 +131,9 @@ bool DoesMethodHaveCallbacks(MethodInfo method, LogAdapter log)
if (!MethodHasCallbackAttributes(method))
return false;

if (IsUnsupportedOnPlatform(method))
return false;

if (TryIsMethodGetParametersUnsupported(method, out string? reason))
{
Log.Warning("WASM0001", $"Skipping callback '{method.DeclaringType!.FullName}::{method.Name}' because '{reason}'.");
Expand Down Expand Up @@ -207,6 +217,102 @@ private bool HasAssemblyDisableRuntimeMarshallingAttribute(Assembly assembly)

return value;
}

private bool IsUnsupportedOnPlatform(MethodInfo method)
{
PlatformSupport methodResult = EvaluatePlatformAttributes(CustomAttributeData.GetCustomAttributes(method));
if (methodResult == PlatformSupport.Unsupported)
return true;
if (methodResult == PlatformSupport.Supported)
return false;

return IsUnsupportedOnPlatform(method.DeclaringType);
}

private bool IsUnsupportedOnPlatform(Type? type)
{
if (type is null)
return false;

if (_typeUnsupportedOnPlatformCache.TryGetValue(type, out bool cached))
return cached;

bool value;
PlatformSupport typeResult = EvaluatePlatformAttributes(CustomAttributeData.GetCustomAttributes(type));
if (typeResult == PlatformSupport.Unsupported)
{
value = true;
}
else if (typeResult == PlatformSupport.Supported)
{
value = false;
}
else if (type.DeclaringType is not null)
{
value = IsUnsupportedOnPlatform(type.DeclaringType);
}
else
{
value = IsAssemblyUnsupportedOnPlatform(type.Assembly);
}

_typeUnsupportedOnPlatformCache[type] = value;
return value;
}

private bool IsAssemblyUnsupportedOnPlatform(Assembly assembly)
{
if (!_assemblyUnsupportedOnPlatformCache.TryGetValue(assembly, out bool value))
{
PlatformSupport asmResult = EvaluatePlatformAttributes(assembly.GetCustomAttributesData());
value = asmResult == PlatformSupport.Unsupported;
_assemblyUnsupportedOnPlatformCache[assembly] = value;
}

return value;
}

private enum PlatformSupport
{
Unknown, // No platform attributes were observed at this scope
Supported, // Explicitly supported here (target appears in a SupportedOSPlatform list)
Unsupported, // Explicitly unsupported here (target matches UnsupportedOSPlatform, or
// SupportedOSPlatform is present and does not list the target)
}

private PlatformSupport EvaluatePlatformAttributes(IList<CustomAttributeData> attrs)
{
bool hasSupportedOSPlatform = false;
bool hasSupportedTarget = false;
foreach (CustomAttributeData cattr in attrs)
{
try
{
if (cattr.AttributeType.FullName == "System.Runtime.Versioning.UnsupportedOSPlatformAttribute" &&
cattr.ConstructorArguments.Count > 0 &&
cattr.ConstructorArguments[0].Value?.ToString() == _targetOS)
{
return PlatformSupport.Unsupported;
}
if (cattr.AttributeType.FullName == "System.Runtime.Versioning.SupportedOSPlatformAttribute" &&
cattr.ConstructorArguments.Count > 0)
{
hasSupportedOSPlatform = true;
if (cattr.ConstructorArguments[0].Value?.ToString() == _targetOS)
hasSupportedTarget = true;
}
}
catch
{
// Assembly not found, ignore
}
}

if (hasSupportedOSPlatform)
return hasSupportedTarget ? PlatformSupport.Supported : PlatformSupport.Unsupported;

return PlatformSupport.Unknown;
}
}

internal sealed class PInvokeCallbackComparer : IComparer<PInvokeCallback>
Expand Down
4 changes: 2 additions & 2 deletions src/tasks/WasmAppBuilder/mono/PInvokeTableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ internal sealed class PInvokeTableGenerator
private readonly PInvokeCollector _pinvokeCollector;
private readonly bool _isLibraryMode;

public PInvokeTableGenerator(Func<string, string> fixupSymbolName, LogAdapter log, bool isLibraryMode = false)
public PInvokeTableGenerator(Func<string, string> fixupSymbolName, LogAdapter log, bool isLibraryMode, string targetOS)
{
Log = log;
_fixupSymbolName = fixupSymbolName;
_pinvokeCollector = new(log);
_pinvokeCollector = new(log, targetOS);
_isLibraryMode = isLibraryMode;
}

Expand Down
Loading