File: CodeGen\CodeGenRefOutTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Emit\Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Emit.UnitTests)
// 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.
 
#nullable disable
 
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
    public class CodeGenRefOutTests : CSharpTestBase
    {
        [Fact]
        public void TestOutParamSignature()
        {
            var source = @"
class C
{
    void M(out int x)
    {
        x = 0;
    }
}";
            CompileAndVerify(source, expectedSignatures: new[]
            {
                Signature("C", "M", ".method private hidebysig instance System.Void M([out] System.Int32& x) cil managed")
            });
        }
 
        [Fact]
        public void TestRefParamSignature()
        {
            var source = @"
class C
{
    void M(ref int x)
    {
    }
}";
            CompileAndVerify(source, expectedSignatures: new[]
            {
                Signature("C", "M", ".method private hidebysig instance System.Void M(System.Int32& x) cil managed")
            });
        }
 
        [Fact]
        public void TestOneReferenceMultipleParameters()
        {
            var source = @"
class C
{
    static void Main()
    {
        int z = 0;
        Test(ref z, out z);
        System.Console.WriteLine(z);
    }
 
    static void Test(ref int x, out int y)
    {
        x = 1;
        y = 2;
    }
}";
            CompileAndVerify(source, expectedOutput: "2");
        }
 
        [Fact]
        public void TestReferenceParameterOrder()
        {
            var source = @"
public class Test
{
    static int[] array = new int[1];
 
    public static void Main(string[] args)
    {
        // Named parameters are in reversed order
        // Arguments have side effects
        // Arguments refer to the same array element
        Goo(y: out GetArray(""A"")[GetIndex(""B"")], x: ref GetArray(""C"")[GetIndex(""D"")]);
        System.Console.WriteLine(array[0]);
    }
 
    static void Goo(ref int x, out int y)
    {
        x = 1;
        y = 2;
    }
 
    static int GetIndex(string msg)
    {
        System.Console.WriteLine(""Index {0}"", msg);
        return 0;
    }
 
    static int[] GetArray(string msg)
    {
        System.Console.WriteLine(""Array {0}"", msg);
        return array;
    }
}";
            CompileAndVerify(source, expectedOutput: @"
Array A
Index B
Array C
Index D
2");
        }
 
        [Fact]
        public void TestPassMutableStructByReference()
        {
            var source = @"
class C
{
    static void Main()
    {
        MutableStruct s1 = new MutableStruct();
        s1.Dump();
        ByRef(ref s1, 2);
        s1.Dump();
 
        System.Console.WriteLine();
 
        MutableStruct s2 = new MutableStruct();
        s2.Dump();
        ByVal(s2, 2);
        s2.Dump();
    }
 
    static void ByRef(ref MutableStruct s, int depth)
    {
        if (depth <= 0)
        {
            s.Flag();
        }
        else
        {
            s.Dump();
            ByRef(ref s, depth - 1);
            s.Dump();
        }
    }
 
    static void ByVal(MutableStruct s, int depth)
    {
        if (depth <= 0)
        {
            s.Flag();
        }
        else
        {
            s.Dump();
            ByVal(s, depth - 1);
            s.Dump();
        }
    }
}
 
struct MutableStruct
{
    private bool flagged;
 
    public void Flag()
    {
        this.flagged = true;
    }
 
    public void Dump()
    {
        System.Console.WriteLine(flagged ? ""Flagged"" : ""Unflagged"");
    }
}";
            CompileAndVerify(source, expectedOutput: @"
Unflagged
Unflagged
Unflagged
Flagged
Flagged
Flagged
 
Unflagged
Unflagged
Unflagged
Unflagged
Unflagged
Unflagged");
        }
 
        [Fact]
        public void TestPassFieldByReference()
        {
            var source = @"
class C
{
    int field;
    int[] arrayField = new int[1];
 
    static int staticField;
    static int[] staticArrayField = new int[1];
 
    static void Main()
    {
        C c = new C();
 
        System.Console.WriteLine(c.field);
        TestRef(ref c.field);
        System.Console.WriteLine(c.field);
 
        System.Console.WriteLine(c.arrayField[0]);
        TestRef(ref c.arrayField[0]);
        System.Console.WriteLine(c.arrayField[0]);
 
        System.Console.WriteLine(C.staticField);
        TestRef(ref C.staticField);
        System.Console.WriteLine(C.staticField);
 
        System.Console.WriteLine(C.staticArrayField[0]);
        TestRef(ref C.staticArrayField[0]);
        System.Console.WriteLine(C.staticArrayField[0]);
    }
 
    static void TestRef(ref int x)
    {
        x++;
    }
}";
            CompileAndVerify(source, expectedOutput: @"
0
1
0
1
0
1
0
1");
        }
 
        [Fact]
        public void TestSetFieldViaOutParameter()
        {
            var source = @"
class C
{
    int field;
    int[] arrayField = new int[1];
 
    static int staticField;
    static int[] staticArrayField = new int[1];
 
    static void Main()
    {
        C c = new C();
 
        System.Console.WriteLine(c.field);
        TestOut(out c.field);
        System.Console.WriteLine(c.field);
 
        System.Console.WriteLine(c.arrayField[0]);
        TestOut(out c.arrayField[0]);
        System.Console.WriteLine(c.arrayField[0]);
 
        System.Console.WriteLine(C.staticField);
        TestOut(out C.staticField);
        System.Console.WriteLine(C.staticField);
 
        System.Console.WriteLine(C.staticArrayField[0]);
        TestOut(out C.staticArrayField[0]);
        System.Console.WriteLine(C.staticArrayField[0]);
    }
 
    static void TestOut(out int x)
    {
        x = 1;
    }
}";
            CompileAndVerify(source, expectedOutput: @"
0
1
0
1
0
1
0
1");
        }
 
        [WorkItem(543521, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543521")]
        [Fact()]
        public void TestConstructorWithOutParameter()
        {
            CompileAndVerify(@"
class Class1
{
	Class1(out bool outParam)
	{
		outParam = true;
	}
	static void Main()
	{
		var b = false;
		var c1 = new Class1(out b);
	}
}");
        }
 
        [WorkItem(24014, "https://github.com/dotnet/roslyn/issues/24014")]
        [Fact]
        public void RefExtensionMethods_OutParam()
        {
            var code = @"
using System;
public class C
{
    public static void Main()
    {
 
        var inst = new S1();
 
        int orig;
 
        var result = inst.Mutate(out orig);
 
        System.Console.Write(orig);
        System.Console.Write(inst.x);
    }
}
 
public static class S1_Ex
{
    public static bool Mutate(ref this S1 instance, out int orig)
    {
        orig = instance.x;
        instance.x = 42;
 
        return true;
    }
}
 
public struct S1
{
    public int x;
}
";
 
            var compilation = CreateCompilationWithMscorlib40AndSystemCore(code, options: TestOptions.ReleaseExe);
            var verifier = CompileAndVerify(compilation, expectedOutput: "042");
 
            verifier.VerifyIL("C.Main", @"
{
  // Code size       36 (0x24)
  .maxstack  2
  .locals init (S1 V_0, //inst
                int V_1) //orig
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""S1""
  IL_0008:  ldloca.s   V_0
  IL_000a:  ldloca.s   V_1
  IL_000c:  call       ""bool S1_Ex.Mutate(ref S1, out int)""
  IL_0011:  pop
  IL_0012:  ldloc.1
  IL_0013:  call       ""void System.Console.Write(int)""
  IL_0018:  ldloc.0
  IL_0019:  ldfld      ""int S1.x""
  IL_001e:  call       ""void System.Console.Write(int)""
  IL_0023:  ret
}");
 
        }
 
        [WorkItem(24014, "https://github.com/dotnet/roslyn/issues/24014")]
        [Fact]
        public void OutParamAndOptional()
        {
            var code = @"
using System;
public class C
{
    public static C cc => new C();
    readonly int x;
    readonly int y;
 
    public static void Main()
    {
        var v = new C(1);
        System.Console.WriteLine('Q');
    }
 
    private C()
    {
    }
 
    private C(int x)
    {
        var c = C.cc.Test(1, this, out x, out y);
    }
 
    public C Test(object arg1, C arg2, out int i1, out int i2, object opt = null)
    {
        i1 = 1;
        i2 = 2;
 
        return arg2;
    }
}
";
 
            var compilation = CreateCompilationWithMscorlib40AndSystemCore(code, options: TestOptions.ReleaseExe);
            var verifier = CompileAndVerify(compilation, expectedOutput: "Q");
 
            verifier.VerifyIL("C..ctor(int)", @"
{
  // Code size       34 (0x22)
  .maxstack  6
  IL_0000:  ldarg.0
  IL_0001:  call       ""object..ctor()""
  IL_0006:  call       ""C C.cc.get""
  IL_000b:  ldc.i4.1
  IL_000c:  box        ""int""
  IL_0011:  ldarg.0
  IL_0012:  ldarga.s   V_1
  IL_0014:  ldarg.0
  IL_0015:  ldflda     ""int C.y""
  IL_001a:  ldnull
  IL_001b:  callvirt   ""C C.Test(object, C, out int, out int, object)""
  IL_0020:  pop
  IL_0021:  ret
}");
        }
 
        [WorkItem(24014, "https://github.com/dotnet/roslyn/issues/24014")]
        [Fact]
        public void OutParamAndOptionalNested()
        {
            var code = @"
using System;
public class C
{
    public static C cc => new C();
 
    readonly int y;
 
    public static void Main()
    {
        var v = new C(1);
        System.Console.WriteLine('Q');
    }
 
    private C()
    {
    }
 
    private C(int x)
    {
        var captured = 2;
 
        C Test(object arg1, C arg2, out int i1, out int i2, object opt = null)
        {
            i1 = 1;
            i2 = captured++;
 
            return arg2;
        }
 
        var c = Test(1, this, out x, out y);
    }
}
";
 
            var compilation = CreateCompilationWithMscorlib40AndSystemCore(code, options: TestOptions.ReleaseExe);
            var verifier = CompileAndVerify(compilation, expectedOutput: "Q");
 
            verifier.VerifyIL("C..ctor(int)", @"
{
  // Code size       39 (0x27)
  .maxstack  6
  .locals init (C.<>c__DisplayClass5_0 V_0) //CS$<>8__locals0
  IL_0000:  ldarg.0
  IL_0001:  call       ""object..ctor()""
  IL_0006:  ldloca.s   V_0
  IL_0008:  ldc.i4.2
  IL_0009:  stfld      ""int C.<>c__DisplayClass5_0.captured""
  IL_000e:  ldc.i4.1
  IL_000f:  box        ""int""
  IL_0014:  ldarg.0
  IL_0015:  ldarga.s   V_1
  IL_0017:  ldarg.0
  IL_0018:  ldflda     ""int C.y""
  IL_001d:  ldnull
  IL_001e:  ldloca.s   V_0
  IL_0020:  call       ""C C.<.ctor>g__Test|5_0(object, C, out int, out int, object, ref C.<>c__DisplayClass5_0)""
  IL_0025:  pop
  IL_0026:  ret
}");
        }
 
        [Fact, WorkItem(53113, "https://github.com/dotnet/roslyn/issues/53113")]
        public void TestRefOnPointerIndirection()
        {
            var code = @"
using System;
 
unsafe
{
    M(ref *(int*)0);
    void M(ref int i) => Console.WriteLine(""run"");
}
";
 
            verify(TestOptions.UnsafeReleaseExe, @"
{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  ldc.i4.0
  IL_0001:  conv.i
  IL_0002:  call       ""void Program.<<Main>$>g__M|0_0(ref int)""
  IL_0007:  ret
}
");
 
            verify(TestOptions.UnsafeDebugExe, @"
{
  // Code size       12 (0xc)
  .maxstack  1
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  conv.i
  IL_0003:  call       ""void Program.<<Main>$>g__M|0_0(ref int)""
  IL_0008:  nop
  IL_0009:  nop
  IL_000a:  nop
  IL_000b:  ret
}
");
 
            void verify(CSharpCompilationOptions options, string expectedIL)
            {
                var comp = CreateCompilation(code, options: options);
                var verifier = CompileAndVerify(comp, expectedOutput: "run", verify: Verification.Fails);
                verifier.VerifyDiagnostics();
                verifier.VerifyIL("<top-level-statements-entry-point>", expectedIL);
            }
        }
 
        [Fact, WorkItem(53113, "https://github.com/dotnet/roslyn/issues/53113")]
        public void TestOutOnPointerIndirection()
        {
            var code = @"
using System;
 
unsafe
{
    try
    {
        M(out *(int*)0);
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
 
    void M(out int i)
    {
        throw new Exception(""run"");
    }
}
";
 
            verify(TestOptions.UnsafeReleaseExe, @"
{
  // Code size       22 (0x16)
  .maxstack  1
  .try
  {
    IL_0000:  ldc.i4.0
    IL_0001:  conv.i
    IL_0002:  call       ""void Program.<<Main>$>g__M|0_0(out int)""
    IL_0007:  leave.s    IL_0015
  }
  catch System.Exception
  {
    IL_0009:  callvirt   ""string System.Exception.Message.get""
    IL_000e:  call       ""void System.Console.WriteLine(string)""
    IL_0013:  leave.s    IL_0015
  }
  IL_0015:  ret
}
");
 
            verify(TestOptions.UnsafeDebugExe, @"
{
  // Code size       33 (0x21)
  .maxstack  1
  .locals init (System.Exception V_0) //e
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  ldc.i4.0
    IL_0003:  conv.i
    IL_0004:  call       ""void Program.<<Main>$>g__M|0_0(out int)""
    IL_0009:  nop
    IL_000a:  nop
    IL_000b:  leave.s    IL_001e
  }
  catch System.Exception
  {
    IL_000d:  stloc.0
    IL_000e:  nop
    IL_000f:  ldloc.0
    IL_0010:  callvirt   ""string System.Exception.Message.get""
    IL_0015:  call       ""void System.Console.WriteLine(string)""
    IL_001a:  nop
    IL_001b:  nop
    IL_001c:  leave.s    IL_001e
  }
  IL_001e:  nop
  IL_001f:  nop
  IL_0020:  ret
}
");
 
            void verify(CSharpCompilationOptions options, string expectedIL)
            {
                var comp = CreateCompilation(code, options: options);
                var verifier = CompileAndVerify(comp, expectedOutput: "run", verify: Verification.Fails);
                verifier.VerifyDiagnostics();
                verifier.VerifyIL("<top-level-statements-entry-point>", expectedIL);
            }
        }
 
        [Fact, WorkItem(53113, "https://github.com/dotnet/roslyn/issues/53113")]
        public void TestRefOnPointerIndirection_ThroughTernary_01()
        {
            var code = @"
using System;
 
unsafe
{
    bool b = true;
    M(ref b ? ref *(int*)0 : ref *(int*)1);
    void M(ref int i) => Console.WriteLine(""run"");
}
";
 
            verify(TestOptions.UnsafeReleaseExe, @"
{
  // Code size       15 (0xf)
  .maxstack  1
  IL_0000:  ldc.i4.1
  IL_0001:  brtrue.s   IL_0007
  IL_0003:  ldc.i4.1
  IL_0004:  conv.i
  IL_0005:  br.s       IL_0009
  IL_0007:  ldc.i4.0
  IL_0008:  conv.i
  IL_0009:  call       ""void Program.<<Main>$>g__M|0_0(ref int)""
  IL_000e:  ret
}
");
 
            verify(TestOptions.UnsafeDebugExe, @"
{
  // Code size       21 (0x15)
  .maxstack  1
  .locals init (bool V_0) //b
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  brtrue.s   IL_000a
  IL_0006:  ldc.i4.1
  IL_0007:  conv.i
  IL_0008:  br.s       IL_000c
  IL_000a:  ldc.i4.0
  IL_000b:  conv.i
  IL_000c:  call       ""void Program.<<Main>$>g__M|0_0(ref int)""
  IL_0011:  nop
  IL_0012:  nop
  IL_0013:  nop
  IL_0014:  ret
}
");
 
            void verify(CSharpCompilationOptions options, string expectedIL)
            {
                var comp = CreateCompilation(code, options: options);
                var verifier = CompileAndVerify(comp, expectedOutput: "run", verify: Verification.Fails);
                verifier.VerifyDiagnostics();
                verifier.VerifyIL("<top-level-statements-entry-point>", expectedIL);
            }
        }
 
        [Fact, WorkItem(53113, "https://github.com/dotnet/roslyn/issues/53113")]
        public void TestRefOnPointerIndirection_ThroughTernary_02()
        {
            var code = @"
using System;
 
unsafe
{
    int i1 = 0;
    int* p1 = &i1;
    bool b = true;
    M2(ref b ? ref *M1(*p1) : ref i1);
 
    int* M1(int i)
    {
        Console.Write(i);
        return (int*)0;
    }
 
    void M2(ref int i) => Console.WriteLine(""run"");
}
";
 
            verify(TestOptions.UnsafeReleaseExe, @"
{
  // Code size       26 (0x1a)
  .maxstack  1
  .locals init (int V_0, //i1
                int* V_1) //p1
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  conv.u
  IL_0005:  stloc.1
  IL_0006:  ldc.i4.1
  IL_0007:  brtrue.s   IL_000d
  IL_0009:  ldloca.s   V_0
  IL_000b:  br.s       IL_0014
  IL_000d:  ldloc.1
  IL_000e:  ldind.i4
  IL_000f:  call       ""int* Program.<<Main>$>g__M1|0_0(int)""
  IL_0014:  call       ""void Program.<<Main>$>g__M2|0_1(ref int)""
  IL_0019:  ret
}
");
 
            verify(TestOptions.UnsafeDebugExe, @"
{
  // Code size       33 (0x21)
  .maxstack  1
  .locals init (int V_0, //i1
                int* V_1, //p1
                bool V_2) //b
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldloca.s   V_0
  IL_0005:  conv.u
  IL_0006:  stloc.1
  IL_0007:  ldc.i4.1
  IL_0008:  stloc.2
  IL_0009:  ldloc.2
  IL_000a:  brtrue.s   IL_0010
  IL_000c:  ldloca.s   V_0
  IL_000e:  br.s       IL_0017
  IL_0010:  ldloc.1
  IL_0011:  ldind.i4
  IL_0012:  call       ""int* Program.<<Main>$>g__M1|0_0(int)""
  IL_0017:  call       ""void Program.<<Main>$>g__M2|0_1(ref int)""
  IL_001c:  nop
  IL_001d:  nop
  IL_001e:  nop
  IL_001f:  nop
  IL_0020:  ret
}
");
 
            void verify(CSharpCompilationOptions options, string expectedIL)
            {
                var comp = CreateCompilation(code, options: options);
                var verifier = CompileAndVerify(comp, expectedOutput: "0run", verify: Verification.Fails);
                verifier.VerifyDiagnostics();
                verifier.VerifyIL("<top-level-statements-entry-point>", expectedIL);
            }
        }
 
        [Fact, WorkItem(53113, "https://github.com/dotnet/roslyn/issues/53113")]
        public void TestRefOnPointerArrayAccess()
        {
            var code = @"
using System;
 
unsafe
{
    M(ref ((int*)0)[1]);
    void M(ref int i) => Console.WriteLine(""run"");
}
";
 
            verify(TestOptions.UnsafeReleaseExe, @"
{
  // Code size       10 (0xa)
  .maxstack  2
  IL_0000:  ldc.i4.0
  IL_0001:  conv.i
  IL_0002:  ldc.i4.4
  IL_0003:  add
  IL_0004:  call       ""void Program.<<Main>$>g__M|0_0(ref int)""
  IL_0009:  ret
}
");
 
            verify(TestOptions.UnsafeDebugExe, @"
{
  // Code size       14 (0xe)
  .maxstack  2
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  conv.i
  IL_0003:  ldc.i4.4
  IL_0004:  add
  IL_0005:  call       ""void Program.<<Main>$>g__M|0_0(ref int)""
  IL_000a:  nop
  IL_000b:  nop
  IL_000c:  nop
  IL_000d:  ret
}
");
 
            void verify(CSharpCompilationOptions options, string expectedIL)
            {
                var comp = CreateCompilation(code, options: options);
                var verifier = CompileAndVerify(comp, expectedOutput: "run", verify: Verification.Fails);
                verifier.VerifyDiagnostics();
                verifier.VerifyIL("<top-level-statements-entry-point>", expectedIL);
            }
        }
    }
}