diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index 72df455b901df..3bdf68c24b54e 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -874,6 +874,12 @@ public override BoundNode VisitLocal(BoundLocal node) break; case ExprContext.Sideeffects: + if (node.LocalSymbol.RefKind != RefKind.None) + { + // Reading from a ref has a side effect since the read + // may result in a NullReferenceException. + RecordVarRead(node.LocalSymbol); + } break; case ExprContext.Value: diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs index 0e13045b73e20..f5cb76b9ddf2d 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs @@ -3710,5 +3710,262 @@ void verify(CSharpCompilationOptions options, Verification verify, string expect verifier.VerifyIL("", expectedIL); } } + + [Fact] + [WorkItem(60905, "https://github.com/dotnet/roslyn/issues/60905")] + public void ReadValueAndDiscard_01() + { + var source = +@"struct S { } +class Program +{ + static void Main() + { + F(new S[1]); + } + static void F(S[] a) + { + ref var b = ref a[0]; + _ = b; + } +}"; + var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 11 (0xb) + .maxstack 2 + .locals init (S& V_0) //b + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldc.i4.0 + IL_0003: ldelema ""S"" + IL_0008: stloc.0 + IL_0009: nop + IL_000a: ret +}"); + verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: ldelema ""S"" + IL_0007: pop + IL_0008: ret +}"); + } + + [Fact] + [WorkItem(60905, "https://github.com/dotnet/roslyn/issues/60905")] + public void ReadValueAndDiscard_02() + { + var source = +@"struct S +{ + public T F; +} +class Program +{ + static void Main() + { + F(new S()); + } + static void F(S s) + { + ref T t = ref s.F; + _ = t; + } +}"; + var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 11 (0xb) + .maxstack 1 + .locals init (T& V_0) //t + IL_0000: nop + IL_0001: ldarga.s V_0 + IL_0003: ldflda ""T S.F"" + IL_0008: stloc.0 + IL_0009: nop + IL_000a: ret +}"); + verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: ldarga.s V_0 + IL_0002: ldflda ""T S.F"" + IL_0007: pop + IL_0008: ret +}"); + } + + [Fact] + [WorkItem(60905, "https://github.com/dotnet/roslyn/issues/60905")] + public void ReadValueAndDiscard_03() + { + var source = +@"struct S +{ + public T F; +} +class Program +{ + static void Main() + { + var s = new S(); + F(ref s); + } + static void F(ref S s) + { + ref T t = ref s.F; + _ = t; + } +}"; + var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 10 (0xa) + .maxstack 1 + .locals init (T& V_0) //t + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldflda ""T S.F"" + IL_0007: stloc.0 + IL_0008: nop + IL_0009: ret +}"); + verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldflda ""T S.F"" + IL_0006: pop + IL_0007: ret +}"); + } + + [Fact] + [WorkItem(60905, "https://github.com/dotnet/roslyn/issues/60905")] + public void ReadValueAndDiscard_04() + { + var source = +@"struct S +{ + public T F; +} +class Program +{ + static void Main() + { + F(new S()); + } + static void F(in S s) + { + ref readonly T t = ref s.F; + _ = t; + } +}"; + var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 10 (0xa) + .maxstack 1 + .locals init (T& V_0) //t + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldflda ""T S.F"" + IL_0007: stloc.0 + IL_0008: nop + IL_0009: ret +}"); + verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldflda ""T S.F"" + IL_0006: pop + IL_0007: ret +}"); + } + + [Fact] + public void ReadValueAndDiscard_05() + { + var source = +@"struct S +{ +} +class Program +{ + static void Main() + { + var s = new S(); + F(ref s); + } + static void F(ref S s) + { + _ = s; + } +}"; + var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 3 (0x3) + .maxstack 0 + IL_0000: nop + IL_0001: nop + IL_0002: ret +}"); + verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + } + + [Fact] + public void ReadValueAndDiscard_06() + { + var source = +@"struct S +{ +} +class Program +{ + static void Main() + { + F(new S()); + } + static void F(in S s) + { + _ = s; + } +}"; + var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 3 (0x3) + .maxstack 0 + IL_0000: nop + IL_0001: nop + IL_0002: ret +}"); + verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: ""); + verifier.VerifyIL("Program.F", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + } } }