Skip to content

Commit 9c16d6e

Browse files
jtschusterCopilot
andauthored
Add test for runtime async and update linker handling of method body (#120683)
Fixes #120340 Adds test with some runtime async methods, and fixes the check for invalid IL when a Task returning async method doesn't have a value on the stack for `ret`. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d148b5c commit 9c16d6e

File tree

5 files changed

+144
-7
lines changed

5 files changed

+144
-7
lines changed

src/tools/illink/src/linker/Linker.Dataflow/MethodBodyScanner.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -694,14 +694,15 @@ protected virtual void Scan(MethodIL methodIL, ref InterproceduralState interpro
694694

695695
case Code.Ret:
696696
{
697-
698-
bool hasReturnValue = !methodBody.Method.ReturnsVoid();
699-
700-
if (currentStack.Count != (hasReturnValue ? 1 : 0))
697+
if (!methodBody.Method.IsRuntimeAsync())
701698
{
702-
WarnAboutInvalidILInMethod(methodIL, operation.Offset);
699+
bool ilHasReturnValue = !methodBody.Method.ReturnsVoid();
700+
if (currentStack.Count != (ilHasReturnValue ? 1 : 0))
701+
{
702+
WarnAboutInvalidILInMethod(methodIL, operation.Offset);
703+
}
703704
}
704-
if (hasReturnValue)
705+
if (currentStack.Count == 1)
705706
{
706707
StackSlot retStackSlot = PopUnknown(currentStack, 1, methodIL, operation.Offset);
707708
// If the return value is a reference, treat it as the value itself for now

src/tools/illink/src/linker/Linker/MethodDefinitionExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ public static bool IsEventMethod(this MethodDefinition md)
5454
(md.SemanticsAttributes & MethodSemanticsAttributes.RemoveOn) != 0;
5555
}
5656

57+
public static bool IsRuntimeAsync(this MethodDefinition md)
58+
{
59+
return (md.ImplAttributes & (MethodImplAttributes)0x2000) == (MethodImplAttributes)0x2000;
60+
}
61+
5762
public static bool TryGetProperty(this MethodDefinition md, [NotNullWhen(true)] out PropertyDefinition? property)
5863
{
5964
property = null;

src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public Task ModifierDataFlow()
4343
return RunTest(allowMissingWarnings: true);
4444
}
4545

46+
[Fact]
47+
public Task RuntimeAsyncMethods()
48+
{
49+
return RunTest(allowMissingWarnings: true);
50+
}
51+
4652
[Fact]
4753
public Task StaticInterfaceMethodDataflow()
4854
{
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
7+
using System.Threading.Tasks;
8+
using Mono.Linker.Tests.Cases.Expectations.Assertions;
9+
using Mono.Linker.Tests.Cases.Expectations.Helpers;
10+
using Mono.Linker.Tests.Cases.Expectations.Metadata;
11+
12+
namespace Mono.Linker.Tests.Cases.DataFlow
13+
{
14+
[SkipKeptItemsValidation]
15+
[IgnoreTestCase("NativeAOT doesn't support runtime async yet", IgnoredBy = Tool.NativeAot)]
16+
[SetupCompileArgument("/features:runtime-async=on")]
17+
[SetupCompileArgument("/nowarn:SYSLIB5007")]
18+
public class RuntimeAsyncMethods
19+
{
20+
public static async Task Main()
21+
{
22+
await BasicRuntimeAsyncMethod();
23+
await RuntimeAsyncWithDataFlowAnnotations(null);
24+
await RuntimeAsyncWithCapturedLocalDataFlow();
25+
await RuntimeAsyncWithMultipleAwaits();
26+
await RuntimeAsyncWithReassignment(true);
27+
await RuntimeAsyncReturningAnnotatedType();
28+
await RuntimeAsyncWithCorrectParameter(null);
29+
await RuntimeAsyncWithLocalAll();
30+
}
31+
32+
static async Task BasicRuntimeAsyncMethod()
33+
{
34+
await Task.Delay(1);
35+
Console.WriteLine("Basic runtime async");
36+
}
37+
38+
[ExpectedWarning("IL2067", "type", nameof(DataFlowTypeExtensions.RequiresAll), nameof(RuntimeAsyncWithDataFlowAnnotations))]
39+
static async Task RuntimeAsyncWithDataFlowAnnotations([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
40+
{
41+
await Task.Delay(1);
42+
type.RequiresAll();
43+
}
44+
45+
[ExpectedWarning("IL2072", nameof(DataFlowTypeExtensions.RequiresAll), nameof(GetWithPublicMethods))]
46+
static async Task RuntimeAsyncWithCapturedLocalDataFlow()
47+
{
48+
Type t = GetWithPublicMethods();
49+
await Task.Delay(1);
50+
t.RequiresAll();
51+
}
52+
53+
[ExpectedWarning("IL2072", nameof(DataFlowTypeExtensions.RequiresAll), nameof(GetWithPublicMethods))]
54+
static async Task RuntimeAsyncWithMultipleAwaits()
55+
{
56+
Type t = GetWithPublicMethods();
57+
await Task.Delay(1);
58+
t.RequiresPublicMethods();
59+
await Task.Delay(1);
60+
t.RequiresAll();
61+
}
62+
63+
[ExpectedWarning("IL2072", nameof(DataFlowTypeExtensions.RequiresAll), nameof(GetWithPublicMethods))]
64+
[ExpectedWarning("IL2072", nameof(DataFlowTypeExtensions.RequiresAll), nameof(GetWithPublicFields))]
65+
static async Task RuntimeAsyncWithReassignment(bool condition)
66+
{
67+
Type t = GetWithPublicMethods();
68+
await Task.Delay(1);
69+
if (condition)
70+
{
71+
t = GetWithPublicFields();
72+
}
73+
await Task.Delay(1);
74+
t.RequiresAll();
75+
}
76+
77+
[ExpectedWarning("IL2106", nameof(RuntimeAsyncReturningAnnotatedType))]
78+
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
79+
static async Task<Type> RuntimeAsyncReturningAnnotatedType()
80+
{
81+
await Task.Delay(1);
82+
return GetWithPublicMethods();
83+
}
84+
85+
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
86+
static Type GetWithPublicMethods() => null;
87+
88+
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
89+
static Type GetWithPublicFields() => null;
90+
91+
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
92+
static Type GetWithAllMembers() => null;
93+
94+
static async Task RuntimeAsyncWithCorrectParameter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type)
95+
{
96+
type ??= GetWithAllMembers();
97+
await Task.Delay(1);
98+
type.RequiresAll();
99+
}
100+
101+
static async Task RuntimeAsyncWithLocalAll()
102+
{
103+
Type t = GetWithAllMembers();
104+
await Task.Delay(1);
105+
t.RequiresAll();
106+
}
107+
}
108+
}

src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options)
267267
// Default debug info format for the current platform.
268268
DebugInformationFormat debugType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DebugInformationFormat.Pdb : DebugInformationFormat.PortablePdb;
269269
bool emitPdb = false;
270+
Dictionary<string, string> features = [];
270271
if (options.AdditionalArguments != null)
271272
{
272273
foreach (var option in options.AdditionalArguments)
@@ -303,11 +304,27 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options)
303304
compilationOptions = compilationOptions.WithMainTypeName(mainTypeName);
304305
break;
305306
}
307+
else if (splitIndex != -1 && option[..splitIndex] == "/features")
308+
{
309+
var feature = option[(splitIndex + 1)..];
310+
var featureSplit = feature.IndexOf('=');
311+
if (featureSplit == -1)
312+
throw new InvalidOperationException($"Argument is malformed: '{option}'");
313+
features.Add(feature[..featureSplit], feature[(featureSplit + 1)..]);
314+
break;
315+
}
316+
else if (splitIndex != -1 && option[..splitIndex] == "/nowarn")
317+
{
318+
var nowarn = option[(splitIndex + 1)..];
319+
var withNoWarn = compilationOptions.SpecificDiagnosticOptions.SetItem(nowarn, ReportDiagnostic.Suppress);
320+
compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(withNoWarn);
321+
break;
322+
}
306323
throw new NotImplementedException(option);
307324
}
308325
}
309326
}
310-
var parseOptions = new CSharpParseOptions(preprocessorSymbols: options.Defines, languageVersion: languageVersion);
327+
var parseOptions = new CSharpParseOptions(preprocessorSymbols: options.Defines, languageVersion: languageVersion).WithFeatures(features);
311328
var emitOptions = new EmitOptions(debugInformationFormat: debugType);
312329
var pdbPath = (!emitPdb || debugType == DebugInformationFormat.Embedded) ? null : options.OutputPath.ChangeExtension(".pdb").ToString();
313330

0 commit comments

Comments
 (0)