|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests;
public sealed class StackOverflowProbingTests : CSharpTestBase
{
#if NET
const string NativeIntDisplay = "nint";
#else
const string NativeIntDisplay = "System.IntPtr";
#endif
private static readonly EmitOptions s_emitOptions = EmitOptions.Default.WithInstrumentationKinds([InstrumentationKind.StackOverflowProbing]);
private CompilationVerifier CompileAndVerify(string source, string? expectedOutput = null, CSharpCompilationOptions? options = null, Verification? verification = null)
=> CompileAndVerify(
source,
options: options ?? (expectedOutput != null ? TestOptions.UnsafeDebugExe : TestOptions.UnsafeDebugDll),
emitOptions: s_emitOptions,
verify: verification ?? Verification.Passes,
targetFramework: TargetFramework.NetLatest,
expectedOutput: expectedOutput);
private static void AssertNotInstrumented(CompilationVerifier verifier, string qualifiedMethodName)
{
var il = verifier.VisualizeIL(qualifiedMethodName);
var isInstrumented = il.Contains("System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack");
Assert.False(isInstrumented,
$"Method '{qualifiedMethodName}' should not be instrumented. Actual IL:{Environment.NewLine}{il}");
}
[Fact]
public void LambdaAndLocalFunction()
{
var source = """
using System;
class C
{
public void F(Func<int> a)
{
void L()
{
L();
}
F(() => 1);
}
}
""";
var verifier = CompileAndVerify(source);
verifier.VerifyMethodBody("C.F", $$"""
{
// Code size 47 (0x2f)
.maxstack 3
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
IL_0007: nop
// sequence point: F(() => 1);
IL_0008: ldarg.0
IL_0009: ldsfld "System.Func<int> C.<>c.<>9__0_1"
IL_000e: dup
IL_000f: brtrue.s IL_0028
IL_0011: pop
IL_0012: ldsfld "C.<>c C.<>c.<>9"
IL_0017: ldftn "int C.<>c.<F>b__0_1()"
IL_001d: newobj "System.Func<int>..ctor(object, {{NativeIntDisplay}})"
IL_0022: dup
IL_0023: stsfld "System.Func<int> C.<>c.<>9__0_1"
IL_0028: call "void C.F(System.Func<int>)"
IL_002d: nop
// sequence point: }
IL_002e: ret
}
""");
verifier.VerifyMethodBody("C.<F>g__L|0_0", """
{
// Code size 14 (0xe)
.maxstack 0
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
// sequence point: L();
IL_0007: call "void C.<F>g__L|0_0()"
IL_000c: nop
// sequence point: }
IL_000d: ret
}
""");
verifier.VerifyMethodBody("C.<>c.<F>b__0_1", """
{
// Code size 8 (0x8)
.maxstack 1
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: 1
IL_0006: ldc.i4.1
IL_0007: ret
}
""");
}
[Fact]
public void LambdaAndLocalFunction_TopLevel()
{
var source = """
F();
void F()
{
F();
}
""";
var verifier = CompileAndVerify(source, options: TestOptions.DebugExe);
verifier.VerifyMethodBody("<top-level-statements-entry-point>", """
{
// Code size 14 (0xe)
.maxstack 0
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: F();
IL_0006: call "void Program.<<Main>$>g__F|0_0()"
IL_000b: nop
IL_000c: nop
IL_000d: ret
}
""");
verifier.VerifyMethodBody("Program.<<Main>$>g__F|0_0", """
{
// Code size 14 (0xe)
.maxstack 0
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
// sequence point: F();
IL_0007: call "void Program.<<Main>$>g__F|0_0()"
IL_000c: nop
// sequence point: }
IL_000d: ret
}
""");
}
[Fact]
public void StaticConstructor_Explicit()
{
var source = """
using System;
class C
{
static Func<int> s_f = () => s_f();
static C()
{
Console.WriteLine(123);
}
}
""";
var verifier = CompileAndVerify(source);
// no probe since static constructor can only be invoked once:
AssertNotInstrumented(verifier, "C..cctor");
// lambda should be instrumented
verifier.VerifyMethodBody("C.<>c.<.cctor>b__1_0", """
{
// Code size 17 (0x11)
.maxstack 1
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: s_f()
IL_0006: ldsfld "System.Func<int> C.s_f"
IL_000b: callvirt "int System.Func<int>.Invoke()"
IL_0010: ret
}
""");
}
[Fact]
public void StaticConstructor_Implicit()
{
var source = """
using System;
class C
{
static Func<int> s_f = () => s_f();
}
""";
var verifier = CompileAndVerify(source);
// no probe since static constructor can only be invoked once:
AssertNotInstrumented(verifier, "C..cctor");
// lambda should be instrumented
verifier.VerifyMethodBody("C.<>c.<.cctor>b__2_0", """
{
// Code size 17 (0x11)
.maxstack 1
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: s_f()
IL_0006: ldsfld "System.Func<int> C.s_f"
IL_000b: callvirt "int System.Func<int>.Invoke()"
IL_0010: ret
}
""");
}
[Fact]
public void InstanceConstructor_Implicit()
{
var source = """
class B
{
C C = new();
}
class C : B
{
}
""";
var verifier = CompileAndVerify(source);
verifier.VerifyMethodBody("B..ctor", """
{
// Code size 25 (0x19)
.maxstack 2
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: C C = new();
IL_0006: ldarg.0
IL_0007: newobj "C..ctor()"
IL_000c: stfld "C B.C"
IL_0011: ldarg.0
IL_0012: call "object..ctor()"
IL_0017: nop
IL_0018: ret
}
""");
verifier.VerifyMethodBody("C..ctor", """
{
// Code size 14 (0xe)
.maxstack 1
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
IL_0006: ldarg.0
IL_0007: call "B..ctor()"
IL_000c: nop
IL_000d: ret
}
""");
}
[Fact]
public void InstanceConstructors_Explicit()
{
var source = """
class B(int a)
{
}
class C(int a) : B(a)
{
C c = new C(1);
}
""";
var verifier = CompileAndVerify(source);
verifier.VerifyMethodBody("B..ctor", """
{
// Code size 14 (0xe)
.maxstack 1
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: B(int a)
IL_0006: ldarg.0
IL_0007: call "object..ctor()"
IL_000c: nop
IL_000d: ret
}
""");
verifier.VerifyMethodBody("C..ctor", """
{
// Code size 27 (0x1b)
.maxstack 2
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: C c = new C(1);
IL_0006: ldarg.0
IL_0007: ldc.i4.1
IL_0008: newobj "C..ctor(int)"
IL_000d: stfld "C C.c"
// sequence point: B(a)
IL_0012: ldarg.0
IL_0013: ldarg.1
IL_0014: call "B..ctor(int)"
IL_0019: nop
IL_001a: ret
}
""");
}
[Fact]
public void PropertyAccessors_Auto()
{
var source = """
class C
{
int P { get; set; }
}
""";
var verifier = CompileAndVerify(source);
AssertNotInstrumented(verifier, "C.P.get");
AssertNotInstrumented(verifier, "C.P.set");
}
[Fact]
public void PropertyAccessors_Explicit()
{
var source = """
class C
{
int P { get { return P; } set { P = value; } }
}
""";
var verifier = CompileAndVerify(source);
verifier.VerifyMethodBody("C.P.get", """
{
// Code size 18 (0x12)
.maxstack 1
.locals init (int V_0)
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
// sequence point: return P;
IL_0007: ldarg.0
IL_0008: call "int C.P.get"
IL_000d: stloc.0
IL_000e: br.s IL_0010
// sequence point: }
IL_0010: ldloc.0
IL_0011: ret
}
""");
verifier.VerifyMethodBody("C.P.set", """
{
// Code size 16 (0x10)
.maxstack 2
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
// sequence point: P = value;
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: call "void C.P.set"
IL_000e: nop
// sequence point: }
IL_000f: ret
}
""");
}
[Fact]
public void EventAccessors_Field()
{
var source = """
using System;
class C
{
event Action E;
}
""";
var verifier = CompileAndVerify(source);
AssertNotInstrumented(verifier, "C.E.add");
AssertNotInstrumented(verifier, "C.E.remove");
}
[Fact]
public void EventAccessors_Explicit()
{
var source = """
using System;
class C
{
event Action E
{
add { }
remove { }
}
}
""";
var verifier = CompileAndVerify(source);
verifier.VerifyMethodBody("C.E.add", """
{
// Code size 8 (0x8)
.maxstack 0
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
// sequence point: }
IL_0007: ret
}
""");
verifier.VerifyMethodBody("C.E.remove", """
{
// Code size 8 (0x8)
.maxstack 0
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
// sequence point: }
IL_0007: ret
}
""");
}
[Fact]
public void StateMachine_Iterator()
{
var source = """
using System.Collections.Generic;
class C
{
public IEnumerable<int> F()
{
yield return 1;
yield return 2;
}
}
""";
var verifier = CompileAndVerify(source);
AssertNotInstrumented(verifier, "C.F");
AssertNotInstrumented(verifier, "C.<F>d__0..ctor");
AssertNotInstrumented(verifier, "C.<F>d__0.System.IDisposable.Dispose");
AssertNotInstrumented(verifier, "C.<F>d__0.System.Collections.IEnumerator.get_Current");
AssertNotInstrumented(verifier, "C.<F>d__0.System.Collections.IEnumerator.Reset");
AssertNotInstrumented(verifier, "C.<F>d__0.System.Collections.IEnumerable.GetEnumerator");
AssertNotInstrumented(verifier, "C.<F>d__0.System.Collections.Generic.IEnumerator<int>.get_Current");
AssertNotInstrumented(verifier, "C.<F>d__0.System.Collections.Generic.IEnumerable<int>.GetEnumerator");
verifier.VerifyMethodBody("C.<F>d__0.System.Collections.IEnumerator.MoveNext", """
{
// Code size 97 (0x61)
.maxstack 2
.locals init (int V_0)
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: <hidden>
IL_0006: ldarg.0
IL_0007: ldfld "int C.<F>d__0.<>1__state"
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: switch (
IL_0021,
IL_0023,
IL_0025)
IL_001f: br.s IL_0027
IL_0021: br.s IL_0029
IL_0023: br.s IL_0041
IL_0025: br.s IL_0058
IL_0027: ldc.i4.0
IL_0028: ret
IL_0029: ldarg.0
IL_002a: ldc.i4.m1
IL_002b: stfld "int C.<F>d__0.<>1__state"
// sequence point: {
IL_0030: nop
// sequence point: yield return 1;
IL_0031: ldarg.0
IL_0032: ldc.i4.1
IL_0033: stfld "int C.<F>d__0.<>2__current"
IL_0038: ldarg.0
IL_0039: ldc.i4.1
IL_003a: stfld "int C.<F>d__0.<>1__state"
IL_003f: ldc.i4.1
IL_0040: ret
// sequence point: <hidden>
IL_0041: ldarg.0
IL_0042: ldc.i4.m1
IL_0043: stfld "int C.<F>d__0.<>1__state"
// sequence point: yield return 2;
IL_0048: ldarg.0
IL_0049: ldc.i4.2
IL_004a: stfld "int C.<F>d__0.<>2__current"
IL_004f: ldarg.0
IL_0050: ldc.i4.2
IL_0051: stfld "int C.<F>d__0.<>1__state"
IL_0056: ldc.i4.1
IL_0057: ret
// sequence point: <hidden>
IL_0058: ldarg.0
IL_0059: ldc.i4.m1
IL_005a: stfld "int C.<F>d__0.<>1__state"
// sequence point: }
IL_005f: ldc.i4.0
IL_0060: ret
}
""");
}
[Fact]
public void StateMachine_Async()
{
var source = """
using System.Threading.Tasks;
class C
{
static async Task F()
{
await F();
}
}
""";
var verifier = CompileAndVerify(source);
AssertNotInstrumented(verifier, "C.F");
AssertNotInstrumented(verifier, "C.<F>d__0..ctor");
AssertNotInstrumented(verifier, "C.<F>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.SetStateMachine");
verifier.VerifyMethodBody("C.<F>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", """
{
// Code size 160 (0xa0)
.maxstack 3
.locals init (int V_0,
System.Runtime.CompilerServices.TaskAwaiter V_1,
C.<F>d__0 V_2,
System.Exception V_3)
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: <hidden>
IL_0006: ldarg.0
IL_0007: ldfld "int C.<F>d__0.<>1__state"
IL_000c: stloc.0
.try
{
// sequence point: <hidden>
IL_000d: ldloc.0
IL_000e: brfalse.s IL_0012
IL_0010: br.s IL_0014
IL_0012: br.s IL_004d
// sequence point: {
IL_0014: nop
// sequence point: await F();
IL_0015: call "System.Threading.Tasks.Task C.F()"
IL_001a: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"
IL_001f: stloc.1
// sequence point: <hidden>
IL_0020: ldloca.s V_1
IL_0022: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"
IL_0027: brtrue.s IL_0069
IL_0029: ldarg.0
IL_002a: ldc.i4.0
IL_002b: dup
IL_002c: stloc.0
IL_002d: stfld "int C.<F>d__0.<>1__state"
// async: yield
IL_0032: ldarg.0
IL_0033: ldloc.1
IL_0034: stfld "System.Runtime.CompilerServices.TaskAwaiter C.<F>d__0.<>u__1"
IL_0039: ldarg.0
IL_003a: stloc.2
IL_003b: ldarg.0
IL_003c: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.<F>d__0.<>t__builder"
IL_0041: ldloca.s V_1
IL_0043: ldloca.s V_2
IL_0045: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted<System.Runtime.CompilerServices.TaskAwaiter, C.<F>d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.<F>d__0)"
IL_004a: nop
IL_004b: leave.s IL_009f
// async: resume
IL_004d: ldarg.0
IL_004e: ldfld "System.Runtime.CompilerServices.TaskAwaiter C.<F>d__0.<>u__1"
IL_0053: stloc.1
IL_0054: ldarg.0
IL_0055: ldflda "System.Runtime.CompilerServices.TaskAwaiter C.<F>d__0.<>u__1"
IL_005a: initobj "System.Runtime.CompilerServices.TaskAwaiter"
IL_0060: ldarg.0
IL_0061: ldc.i4.m1
IL_0062: dup
IL_0063: stloc.0
IL_0064: stfld "int C.<F>d__0.<>1__state"
IL_0069: ldloca.s V_1
IL_006b: call "void System.Runtime.CompilerServices.TaskAwaiter.GetResult()"
IL_0070: nop
IL_0071: leave.s IL_008b
}
catch System.Exception
{
// sequence point: <hidden>
IL_0073: stloc.3
IL_0074: ldarg.0
IL_0075: ldc.i4.s -2
IL_0077: stfld "int C.<F>d__0.<>1__state"
IL_007c: ldarg.0
IL_007d: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.<F>d__0.<>t__builder"
IL_0082: ldloc.3
IL_0083: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"
IL_0088: nop
IL_0089: leave.s IL_009f
}
// sequence point: }
IL_008b: ldarg.0
IL_008c: ldc.i4.s -2
IL_008e: stfld "int C.<F>d__0.<>1__state"
// sequence point: <hidden>
IL_0093: ldarg.0
IL_0094: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.<F>d__0.<>t__builder"
IL_0099: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"
IL_009e: nop
IL_009f: ret
}
""");
}
[Fact]
public void Records()
{
var source = """
using System.Text;
record class R(int P)
{
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append("x");
return true;
}
}
""" + IsExternalInitTypeDefinition;
var verifier = CompileAndVerify(source, verification: Verification.FailsPEVerify);
AssertNotInstrumented(verifier, "R.P.get");
AssertNotInstrumented(verifier, "R.P.init");
AssertNotInstrumented(verifier, "R.<Clone>$()");
AssertNotInstrumented(verifier, "R.Deconstruct(out int)");
AssertNotInstrumented(verifier, "R.Equals(object)");
AssertNotInstrumented(verifier, "R.Equals(R)");
AssertNotInstrumented(verifier, "R.GetHashCode()");
AssertNotInstrumented(verifier, "R.EqualityContract.get");
AssertNotInstrumented(verifier, "R.ToString()");
AssertNotInstrumented(verifier, "bool R.op_Equality(R, R)");
AssertNotInstrumented(verifier, "bool R.op_Inequality(R, R)");
verifier.VerifyMethodBody("R.PrintMembers(System.Text.StringBuilder)", """
{
// Code size 25 (0x19)
.maxstack 2
.locals init (bool V_0)
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: {
IL_0006: nop
// sequence point: builder.Append("x");
IL_0007: ldarg.1
IL_0008: ldstr "x"
IL_000d: callvirt "System.Text.StringBuilder System.Text.StringBuilder.Append(string)"
IL_0012: pop
// sequence point: return true;
IL_0013: ldc.i4.1
IL_0014: stloc.0
IL_0015: br.s IL_0017
// sequence point: }
IL_0017: ldloc.0
IL_0018: ret
}
""");
// We instrument the copy constructor for simplicity, even though it does not contain any user code.
verifier.VerifyMethodBody("R..ctor(R)", """
{
// Code size 27 (0x1b)
.maxstack 2
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
IL_0006: ldarg.0
IL_0007: call "object..ctor()"
IL_000c: nop
IL_000d: ldarg.0
IL_000e: ldarg.1
IL_000f: ldfld "int R.<P>k__BackingField"
IL_0014: stfld "int R.<P>k__BackingField"
// sequence point: R
IL_0019: nop
IL_001a: ret
}
""");
// We instrument the primary constructor for simplicity, even though it does not contain any user code.
verifier.VerifyMethodBody("R..ctor(int)", """
{
// Code size 21 (0x15)
.maxstack 2
// sequence point: <hidden>
IL_0000: call "void System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()"
IL_0005: nop
// sequence point: <hidden>
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: stfld "int R.<P>k__BackingField"
// sequence point: R(int P)
IL_000d: ldarg.0
IL_000e: call "object..ctor()"
IL_0013: nop
IL_0014: ret
}
""");
}
}
|