未验证 提交 a4c008b7 编写于 作者: A Andy Gocke 提交者: GitHub

Merge pull request #25911 from agocke/finish-ref-local-tests

While implementing the tests for the ref-reassignment test plan (#22466)
I found a bug in the handling of the ref-escape scope of a ref local in foreach. That bug is fixed
and the rest of the items in the test plan should have a test.

Fixes #22466
......@@ -1021,7 +1021,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
/// NOTE: we need scopeOfTheContainingExpression as some expressions such as optional `in` parameters or `ref dynamic` behave as
/// local variables declared at the scope of the invocation.
/// </summary>
private static uint GetInvocationEscapeScope(
internal static uint GetInvocationEscapeScope(
Symbol symbol,
BoundExpression receiverOpt,
ImmutableArray<ParameterSymbol> parameters,
......
......@@ -240,6 +240,14 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
local.SetType(iterationVariableType);
local.SetValEscape(collectionEscape);
if (local.RefKind != RefKind.None)
{
// The ref-escape of a ref-returning property is decided
// by the value escape of its receiverm, in this case the
// collection
local.SetRefEscape(collectionEscape);
}
if (!hasErrors)
{
BindValueKind requiredCurrentKind;
......
......@@ -241,7 +241,7 @@ internal override bool IsPinned
internal virtual void SetRefEscape(uint value)
{
throw ExceptionUtilities.Unreachable;
_refEscapeScope = value;
}
internal virtual void SetValEscape(uint value)
......
......@@ -12,6 +12,253 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests
[CompilerTrait(CompilerFeature.RefLocalsReturns)]
public class CodeGenRefLocalTests : CompilingTestBase
{
[Fact]
public void RefReassignInArrayElement()
{
const string src = @"
using System;
class C
{
void M()
{
object o = string.Empty;
M2(o);
}
void M2(in object o)
{
o = ref (new object[1])[0];
Console.WriteLine(o?.GetHashCode() ?? 5);
}
}";
var verifier = CompileAndVerify(src, verify: Verification.Fails);
const string expectedIL = @"
{
// Code size 36 (0x24)
.maxstack 2
IL_0000: ldc.i4.1
IL_0001: newarr ""object""
IL_0006: ldc.i4.0
IL_0007: readonly.
IL_0009: ldelema ""object""
IL_000e: starg.s V_1
IL_0010: ldarg.1
IL_0011: ldind.ref
IL_0012: dup
IL_0013: brtrue.s IL_0019
IL_0015: pop
IL_0016: ldc.i4.5
IL_0017: br.s IL_001e
IL_0019: callvirt ""int object.GetHashCode()""
IL_001e: call ""void System.Console.WriteLine(int)""
IL_0023: ret
}";
verifier.VerifyIL("C.M2", expectedIL);
// N.B. Even with PEVerify compat this generates unverifiable code.
// Compat mode has no effect because it would generate a temp variable
// which, if we assign to the in parameter, violates safety by allowing
// a local to be returned outside of the method scope.
verifier = CompileAndVerify(src,
parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(),
verify: Verification.Fails);
verifier.VerifyIL("C.M2", expectedIL);
}
[Fact]
public void ReassignmentFixed()
{
var verifier = CompileAndVerify(@"
using System;
class C
{
unsafe static void Main()
{
int x = 5, y = 11;
ref int rx = ref x;
fixed (int* ptr = &(rx = ref y))
{
Console.WriteLine(*ptr);
rx = ref *ptr;
Console.WriteLine(rx);
rx = ref ptr[0];
Console.WriteLine(rx);
}
}
}", options: TestOptions.UnsafeReleaseExe,
verify: Verification.Fails,
expectedOutput: @"11
11
11");
verifier.VerifyIL("C.Main", @"
{
// Code size 34 (0x22)
.maxstack 2
.locals init (int V_0, //x
int V_1, //y
pinned int& V_2)
IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.s 11
IL_0004: stloc.1
IL_0005: ldloca.s V_1
IL_0007: stloc.2
IL_0008: ldloc.2
IL_0009: conv.u
IL_000a: dup
IL_000b: ldind.i4
IL_000c: call ""void System.Console.WriteLine(int)""
IL_0011: dup
IL_0012: ldind.i4
IL_0013: call ""void System.Console.WriteLine(int)""
IL_0018: ldind.i4
IL_0019: call ""void System.Console.WriteLine(int)""
IL_001e: ldc.i4.0
IL_001f: conv.u
IL_0020: stloc.2
IL_0021: ret
}");
}
[Fact]
public void ReassignmentInOut()
{
var verifier = CompileAndVerify(@"
using System;
class C
{
static void Main()
{
int x = 1, y = 2;
ref int rx = ref x;
M(out (rx = ref y));
Console.WriteLine(rx);
Console.WriteLine(x);
Console.WriteLine(y);
}
static void M(out int rx)
{
rx = 5;
}
}", expectedOutput: @" 5
1
5");
}
[Fact]
public void ReassignmentWithReorderParameters()
{
var verifier = CompileAndVerify(@"
using System;
class C
{
static void Main()
{
int x1 = 5, x2 = 7;
ref int rx1 = ref x2, rx2 = ref x2;
M2(p2: (rx1 = ref x2), p1: (rx1 = ref x1));
}
static void M2(int p1, int p2)
{
Console.WriteLine(p1);
Console.WriteLine(p2);
}
}", expectedOutput: @"5
7");
verifier.VerifyIL("C.Main", @"
{
// Code size 14 (0xe)
.maxstack 2
.locals init (int V_0, //x1
int V_1, //x2
int V_2)
IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.7
IL_0003: stloc.1
IL_0004: ldloc.1
IL_0005: stloc.2
IL_0006: ldloc.0
IL_0007: ldloc.2
IL_0008: call ""void C.M2(int, int)""
IL_000d: ret
}");
}
[Fact]
public void ReassignmentWithReorderRefParameters()
{
var verifier = CompileAndVerify(@"
using System;
class C
{
static void Main()
{
int x1 = 5, x2 = 7;
ref int rx1 = ref x2, rx2 = ref x2;
M2(p2: ref (rx1 = ref x2), p1: ref (rx1 = ref x1));
}
static void M2(ref int p1, ref int p2)
{
Console.WriteLine(p1);
Console.WriteLine(p2);
}
}", expectedOutput: @"5
7");
verifier.VerifyIL("C.Main", @"
{
// Code size 16 (0x10)
.maxstack 2
.locals init (int V_0, //x1
int V_1, //x2
int& V_2)
IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.7
IL_0003: stloc.1
IL_0004: ldloca.s V_1
IL_0006: stloc.2
IL_0007: ldloca.s V_0
IL_0009: ldloc.2
IL_000a: call ""void C.M2(ref int, ref int)""
IL_000f: ret
}");
}
[Fact]
public void InReassignmentWithConversion()
{
var verifier = CompileAndVerify(@"
class C
{
void M(string s)
{
ref string rs = ref s;
M2((rs = ref s));
}
void M2(in object o) {}
}");
verifier.VerifyIL("C.M(string)", @"
{
// Code size 18 (0x12)
.maxstack 3
.locals init (string& V_0, //rs
object V_1)
IL_0000: ldarga.s V_1
IL_0002: stloc.0
IL_0003: ldarg.0
IL_0004: ldarga.s V_1
IL_0006: dup
IL_0007: stloc.0
IL_0008: ldind.ref
IL_0009: stloc.1
IL_000a: ldloca.s V_1
IL_000c: call ""void C.M2(in object)""
IL_0011: ret
}");
}
[Fact]
public void RefExprNullPropagation()
{
......
......@@ -12,6 +12,240 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics
[CompilerTrait(CompilerFeature.RefLocalsReturns)]
public class RefLocalsAndReturnsTests : CompilingTestBase
{
[Fact]
public void ReassignExpressionTree()
{
var comp = CreateCompilation(@"
using System;
using System.Linq.Expressions;
class C
{
void M()
{
int x = 0;
ref int rx = ref x;
Expression<Func<int>> e = () => (rx = ref x);
}
}");
comp.VerifyDiagnostics(
// (10,42): error CS8175: Cannot use ref local 'rx' inside an anonymous method, lambda expression, or query expression
// Expression<Func<int>> e = () => (rx = ref x);
Diagnostic(ErrorCode.ERR_AnonDelegateCantUseLocal, "rx").WithArguments("rx").WithLocation(10, 42));
}
[Fact]
public void RefEscapeInFor()
{
var comp = CreateCompilation(@"
class C
{
void M(ref int r1)
{
int x = 0;
ref int rx = ref x;
for (int i = 0; i < (r1 = ref rx); i++)
{
}
for (int i = 0; i < 5; (r1 = ref rx)++)
{
}
}
}");
comp.VerifyDiagnostics(
// (8,30): error CS8374: Cannot ref-assign 'rx' to 'r1' because 'rx' has a narrower escape scope than 'r1'.
// for (int i = 0; i < (r1 = ref rx); i++)
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "r1 = ref rx").WithArguments("r1", "rx").WithLocation(8, 30),
// (11,33): error CS8374: Cannot ref-assign 'rx' to 'r1' because 'rx' has a narrower escape scope than 'r1'.
// for (int i = 0; i < 5; (r1 = ref rx)++)
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "r1 = ref rx").WithArguments("r1", "rx").WithLocation(11, 33));
}
[Fact]
public void RefForMultipleDeclarations()
{
var comp = CreateCompilation(@"
class C
{
void M()
{
int x = 0;
for (ref int rx = ref x, ry = ref x;;)
{
rx += ry;
}
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void RefForNoInitializer()
{
var comp = CreateCompilation(@"
class C
{
void M()
{
int i = 0;
for (ref int rx; i < 5; i++) { }
}
}");
comp.VerifyDiagnostics(
// (7,22): error CS8174: A declaration of a by-reference variable must have an initializer
// for (ref int rx; i < 5; i++) { }
Diagnostic(ErrorCode.ERR_ByReferenceVariableMustBeInitialized, "rx").WithLocation(7, 22),
// (7,22): warning CS0168: The variable 'rx' is declared but never used
// for (ref int rx; i < 5; i++) { }
Diagnostic(ErrorCode.WRN_UnreferencedVar, "rx").WithArguments("rx").WithLocation(7, 22));
}
[Fact]
public void RefReassignVolatileField()
{
var comp = CreateCompilation(@"
class C
{
volatile int _f;
void M()
{
ref int rx = ref _f;
rx = ref _f;
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void RefReassignDynamic()
{
var comp = CreateCompilation(@"
class C
{
void M(ref dynamic rd)
{
ref var rd2 = ref rd.Length; // Legal
rd = ref rd.Length; // Error, escape scope is local
}
}");
comp.VerifyDiagnostics(
// (7,9): error CS8374: Cannot ref-assign 'rd.Length' to 'rd' because 'rd.Length' has a narrower escape scope than 'rd'.
// rd = ref rd.Length; // Error, escape scope is local
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "rd = ref rd.Length").WithArguments("rd", "rd.Length").WithLocation(7, 9));
}
[Fact]
public void RefReassignIsUse()
{
var comp = CreateCompilation(@"
class C
{
private int _f = 0;
void M()
{
int x = 0, y = 0;
ref int rx = ref x;
rx = ref y;
rx = ref _f;
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void NoRefReassignThisInStruct()
{
var comp = CreateCompilation(@"
struct S
{
void M(ref S s)
{
s = ref this;
this = ref s;
}
}");
comp.VerifyDiagnostics(
// (6,9): error CS8374: Cannot ref-assign 'this' to 's' because 'this' has a narrower escape scope than 's'.
// s = ref this;
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s = ref this").WithArguments("s", "this").WithLocation(6, 9),
// (7,9): error CS8373: The left-hand side of a ref assignment must be a ref local or parameter.
// this = ref s;
Diagnostic(ErrorCode.ERR_RefLocalOrParamExpected, "this").WithLocation(7, 9));
}
[Fact]
public void RefReassignLifetimeIsLHS()
{
var comp = CreateCompilation(@"
class C
{
ref int M()
{
int x = 0;
ref int rx = ref x;
return ref (rx = ref (new int[1])[0]);
}
}");
comp.VerifyDiagnostics(
// (8,21): error CS8157: Cannot return 'rx' by reference because it was initialized to a value that cannot be returned by reference
// return ref (rx = ref (new int[1])[0]);
Diagnostic(ErrorCode.ERR_RefReturnNonreturnableLocal, "rx = ref (new int[1])[0]").WithArguments("rx").WithLocation(8, 21));
}
[Fact]
public void InReassignmentWithConversion()
{
var comp = CreateCompilation(@"
class C
{
void M(string s)
{
ref string rs = ref s;
M2(in (rs = ref s));
}
void M2(in object o) {}
}");
comp.VerifyDiagnostics(
// (7,16): error CS1503: Argument 1: cannot convert from 'in string' to 'in object'
// M2(in (rs = ref s));
Diagnostic(ErrorCode.ERR_BadArgType, "rs = ref s").WithArguments("1", "in string", "in object").WithLocation(7, 16));
}
[Fact]
public void RefEscapeInForeach()
{
var comp = CreateCompilationWithMscorlibAndSpan(@"
using System;
class C
{
ref int M(Span<int> s)
{
foreach (ref int x in s)
{
if (x == 0)
{
return ref x; // OK
}
}
Span<int> s2 = stackalloc int[10];
foreach (ref int x in s2)
{
if (x == 0)
{
return ref x; // error
}
}
return ref s[0];
}
}");
comp.VerifyDiagnostics(
// (20,28): error CS8157: Cannot return 'x' by reference because it was initialized to a value that cannot be returned by reference
// return ref x; // error
Diagnostic(ErrorCode.ERR_RefReturnNonreturnableLocal, "x").WithArguments("x").WithLocation(20, 28));
}
[Fact]
public void RefFor72()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册