Skip to content

Commit e628ec6

Browse files
authored
Fix ref safe context of assignments to be their RHS (#80633)
Fixes #79054. Closes #73549.
1 parent 4f0be31 commit e628ec6

File tree

6 files changed

+245
-44
lines changed

6 files changed

+245
-44
lines changed

src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3895,7 +3895,8 @@ internal SafeContext GetRefEscape(BoundExpression expr, SafeContext localScopeDe
38953895
break;
38963896
}
38973897

3898-
return GetRefEscape(assignment.Left, localScopeDepth);
3898+
// The result of a ref assignment is its right-hand side.
3899+
return GetRefEscape(assignment.Right, localScopeDepth);
38993900

39003901
case BoundKind.Conversion:
39013902
Debug.Assert(expr is BoundConversion conversion &&
@@ -4229,9 +4230,10 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, SafeContext
42294230
break;
42304231
}
42314232

4233+
// The result of a ref assignment is its right-hand side.
42324234
return CheckRefEscape(
42334235
node,
4234-
assignment.Left,
4236+
assignment.Right,
42354237
escapeFrom,
42364238
escapeTo,
42374239
checkingReceiver: false,
@@ -4616,14 +4618,13 @@ internal SafeContext GetValEscape(BoundExpression expr, SafeContext localScopeDe
46164618
return GetValEscape(conversion.Operand, localScopeDepth);
46174619

46184620
case BoundKind.AssignmentOperator:
4619-
// https://github.com/dotnet/roslyn/issues/73549:
4620-
// We do not have a test that demonstrates that the statement below makes a difference.
4621-
// If 'localScopeDepth' is always returned, not a single test fails.
4622-
// Same for the 'case BoundKind.NullCoalescingAssignmentOperator:' below.
4621+
// The result of an assignment is its right-hand side.
46234622
return GetValEscape(((BoundAssignmentOperator)expr).Right, localScopeDepth);
46244623

46254624
case BoundKind.NullCoalescingAssignmentOperator:
4626-
return GetValEscape(((BoundNullCoalescingAssignmentOperator)expr).RightOperand, localScopeDepth);
4625+
var nullCoalescingAssignment = (BoundNullCoalescingAssignmentOperator)expr;
4626+
return GetValEscape(nullCoalescingAssignment.LeftOperand, localScopeDepth)
4627+
.Intersect(GetValEscape(nullCoalescingAssignment.RightOperand, localScopeDepth));
46274628

46284629
case BoundKind.IncrementOperator:
46294630
var increment = (BoundIncrementOperator)expr;
@@ -5377,11 +5378,13 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext
53775378

53785379
case BoundKind.AssignmentOperator:
53795380
var assignment = (BoundAssignmentOperator)expr;
5380-
return CheckValEscape(node, assignment.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
5381+
// The result of an assignment is its right-hand side.
5382+
return CheckValEscape(node, assignment.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
53815383

53825384
case BoundKind.NullCoalescingAssignmentOperator:
53835385
var nullCoalescingAssignment = (BoundNullCoalescingAssignmentOperator)expr;
5384-
return CheckValEscape(node, nullCoalescingAssignment.LeftOperand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
5386+
return CheckValEscape(node, nullCoalescingAssignment.LeftOperand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) &&
5387+
CheckValEscape(node, nullCoalescingAssignment.RightOperand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
53855388

53865389
case BoundKind.IncrementOperator:
53875390
var increment = (BoundIncrementOperator)expr;

src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28614,6 +28614,94 @@ static T F<T>() where T : allows ref struct
2861428614
comp.VerifyEmitDiagnostics();
2861528615
}
2861628616

28617+
[Fact]
28618+
public void NullCoalescingAssignment_06()
28619+
{
28620+
var comp = CreateCompilation("""
28621+
static class C
28622+
{
28623+
static T M1<T>(T c1, scoped T c2) where T : allows ref struct
28624+
{
28625+
return X(c1 ??= c2);
28626+
}
28627+
static T M2<T>(T c1, scoped T c2) where T : allows ref struct
28628+
{
28629+
return X(c1 ??= c1);
28630+
}
28631+
static T M3<T>(T c1, scoped T c2) where T : allows ref struct
28632+
{
28633+
return X(c2 ??= c2);
28634+
}
28635+
static T M4<T>(T c1, scoped T c2) where T : allows ref struct
28636+
{
28637+
return X(c2 ??= c1);
28638+
}
28639+
static T X<T>(T c) where T : allows ref struct => c;
28640+
}
28641+
""", targetFramework: s_targetFrameworkSupportingByRefLikeGenerics);
28642+
28643+
comp.VerifyDiagnostics(
28644+
// (5,18): error CS8352: Cannot use variable 'scoped T c2' in this context because it may expose referenced variables outside of their declaration scope
28645+
// return X(c1 ??= c2);
28646+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c1 ??= c2").WithArguments("scoped T c2").WithLocation(5, 18),
28647+
// (5,16): error CS8347: Cannot use a result of 'C.X<T>(T)' in this context because it may expose variables referenced by parameter 'c' outside of their declaration scope
28648+
// return X(c1 ??= c2);
28649+
Diagnostic(ErrorCode.ERR_EscapeCall, "X(c1 ??= c2)").WithArguments("C.X<T>(T)", "c").WithLocation(5, 16),
28650+
// (13,18): error CS8352: Cannot use variable 'scoped T c2' in this context because it may expose referenced variables outside of their declaration scope
28651+
// return X(c2 ??= c2);
28652+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c2 ??= c2").WithArguments("scoped T c2").WithLocation(13, 18),
28653+
// (13,16): error CS8347: Cannot use a result of 'C.X<T>(T)' in this context because it may expose variables referenced by parameter 'c' outside of their declaration scope
28654+
// return X(c2 ??= c2);
28655+
Diagnostic(ErrorCode.ERR_EscapeCall, "X(c2 ??= c2)").WithArguments("C.X<T>(T)", "c").WithLocation(13, 16),
28656+
// (17,18): error CS8352: Cannot use variable 'scoped T c2' in this context because it may expose referenced variables outside of their declaration scope
28657+
// return X(c2 ??= c1);
28658+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c2 ??= c1").WithArguments("scoped T c2").WithLocation(17, 18),
28659+
// (17,16): error CS8347: Cannot use a result of 'C.X<T>(T)' in this context because it may expose variables referenced by parameter 'c' outside of their declaration scope
28660+
// return X(c2 ??= c1);
28661+
Diagnostic(ErrorCode.ERR_EscapeCall, "X(c2 ??= c1)").WithArguments("C.X<T>(T)", "c").WithLocation(17, 16));
28662+
}
28663+
28664+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79054")]
28665+
public void NullCoalescingAssignment_07()
28666+
{
28667+
var comp = CreateCompilation("""
28668+
static class C
28669+
{
28670+
static T M1<T>(T c1, scoped T c2) where T : allows ref struct
28671+
{
28672+
var c3 = (c1 ??= c2);
28673+
return c3; // 1
28674+
}
28675+
static T M2<T>(T c1, scoped T c2) where T : allows ref struct
28676+
{
28677+
var c3 = (c1 ??= c1);
28678+
return c3; // 2
28679+
}
28680+
static T M3<T>(T c1, scoped T c2) where T : allows ref struct
28681+
{
28682+
var c3 = (c2 ??= c2);
28683+
return c3; // 3
28684+
}
28685+
static T M4<T>(T c1, scoped T c2) where T : allows ref struct
28686+
{
28687+
var c3 = (c2 ??= c1);
28688+
return c3; // 4
28689+
}
28690+
}
28691+
""", targetFramework: s_targetFrameworkSupportingByRefLikeGenerics);
28692+
28693+
comp.VerifyDiagnostics(
28694+
// (6,16): error CS8352: Cannot use variable 'c3' in this context because it may expose referenced variables outside of their declaration scope
28695+
// return c3; // 1
28696+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c3").WithArguments("c3").WithLocation(6, 16),
28697+
// (16,16): error CS8352: Cannot use variable 'c3' in this context because it may expose referenced variables outside of their declaration scope
28698+
// return c3; // 3
28699+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c3").WithArguments("c3").WithLocation(16, 16),
28700+
// (21,16): error CS8352: Cannot use variable 'c3' in this context because it may expose referenced variables outside of their declaration scope
28701+
// return c3; // 4
28702+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c3").WithArguments("c3").WithLocation(21, 16));
28703+
}
28704+
2861728705
[Theory]
2861828706
[CombinatorialData]
2861928707
public void ObjectCreation_01(bool addStructConstraint)

src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24692,9 +24692,18 @@ static class Extensions
2469224692
// (8,23): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope
2469324693
// return Y(c += c1);
2469424694
Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(8, 23),
24695+
// (12,16): error CS8347: Cannot use a result of 'C.Y(C)' in this context because it may expose variables referenced by parameter 'left' outside of their declaration scope
24696+
// return Y(c = X(c, c1));
24697+
Diagnostic(ErrorCode.ERR_EscapeCall, "Y(c = X(c, c1))").WithArguments("C.Y(C)", "left").WithLocation(12, 16),
2469524698
// (12,22): error CS8347: Cannot use a result of 'C.X(C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope
2469624699
// return Y(c = X(c, c1));
2469724700
Diagnostic(ErrorCode.ERR_EscapeCall, "X(c, c1)").WithArguments("C.X(C, C)", "right").WithLocation(12, 22),
24701+
// (12,22): error CS8347: Cannot use a result of 'C.X(C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope
24702+
// return Y(c = X(c, c1));
24703+
Diagnostic(ErrorCode.ERR_EscapeCall, "X(c, c1)").WithArguments("C.X(C, C)", "right").WithLocation(12, 22),
24704+
// (12,27): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope
24705+
// return Y(c = X(c, c1));
24706+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(12, 27),
2469824707
// (12,27): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope
2469924708
// return Y(c = X(c, c1));
2470024709
Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(12, 27)
@@ -24893,9 +24902,12 @@ static class Extensions
2489324902
// (12,16): error CS8347: Cannot use a result of 'C.Y(C)' in this context because it may expose variables referenced by parameter 'left' outside of their declaration scope
2489424903
// return Y(c = X(c, c1));
2489524904
Diagnostic(ErrorCode.ERR_EscapeCall, "Y(c = X(c, c1))").WithArguments("C.Y(C)", "left").WithLocation(12, 16),
24896-
// (12,18): error CS8352: Cannot use variable 'scoped C c' in this context because it may expose referenced variables outside of their declaration scope
24905+
// (12,22): error CS8347: Cannot use a result of 'C.X(C, C)' in this context because it may expose variables referenced by parameter 'left' outside of their declaration scope
24906+
// return Y(c = X(c, c1));
24907+
Diagnostic(ErrorCode.ERR_EscapeCall, "X(c, c1)").WithArguments("C.X(C, C)", "left").WithLocation(12, 22),
24908+
// (12,24): error CS8352: Cannot use variable 'scoped C c' in this context because it may expose referenced variables outside of their declaration scope
2489724909
// return Y(c = X(c, c1));
24898-
Diagnostic(ErrorCode.ERR_EscapeVariable, "c = X(c, c1)").WithArguments("scoped C c").WithLocation(12, 18)
24910+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c").WithArguments("scoped C c").WithLocation(12, 24)
2489924911
);
2490024912
}
2490124913

@@ -24942,16 +24954,19 @@ static class Extensions
2494224954
// (12,16): error CS8347: Cannot use a result of 'C.Y(C)' in this context because it may expose variables referenced by parameter 'left' outside of their declaration scope
2494324955
// return Y(c = X(c, c1));
2494424956
Diagnostic(ErrorCode.ERR_EscapeCall, "Y(c = X(c, c1))").WithArguments("C.Y(C)", "left").WithLocation(12, 16),
24945-
// (12,18): error CS8352: Cannot use variable 'scoped C c' in this context because it may expose referenced variables outside of their declaration scope
24957+
// (12,22): error CS8347: Cannot use a result of 'C.X(C, scoped C)' in this context because it may expose variables referenced by parameter 'left' outside of their declaration scope
2494624958
// return Y(c = X(c, c1));
24947-
Diagnostic(ErrorCode.ERR_EscapeVariable, "c = X(c, c1)").WithArguments("scoped C c").WithLocation(12, 18)
24959+
Diagnostic(ErrorCode.ERR_EscapeCall, "X(c, c1)").WithArguments("C.X(C, scoped C)", "left").WithLocation(12, 22),
24960+
// (12,24): error CS8352: Cannot use variable 'scoped C c' in this context because it may expose referenced variables outside of their declaration scope
24961+
// return Y(c = X(c, c1));
24962+
Diagnostic(ErrorCode.ERR_EscapeVariable, "c").WithArguments("scoped C c").WithLocation(12, 24)
2494824963
);
2494924964
}
2495024965

2495124966
/// <summary>
2495224967
/// This is a clone of Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics.RefEscapingTests.UserDefinedBinaryOperator_RefStruct_Compound_ScopedTarget_04
2495324968
/// </summary>
24954-
[Fact]
24969+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79054")]
2495524970
public void CompoundAssignment_089_RefSafety()
2495624971
{
2495724972
var source = """
@@ -24978,14 +24993,7 @@ static class Extensions
2497824993
}
2497924994
}
2498024995
""";
24981-
CreateCompilation(source).VerifyDiagnostics(
24982-
// (12,16): error CS8347: Cannot use a result of 'C.Y(C)' in this context because it may expose variables referenced by parameter 'left' outside of their declaration scope
24983-
// return Y(c = X(c, c1));
24984-
Diagnostic(ErrorCode.ERR_EscapeCall, "Y(c = X(c, c1))").WithArguments("C.Y(C)", "left").WithLocation(12, 16),
24985-
// (12,18): error CS8352: Cannot use variable 'scoped C c' in this context because it may expose referenced variables outside of their declaration scope
24986-
// return Y(c = X(c, c1));
24987-
Diagnostic(ErrorCode.ERR_EscapeVariable, "c = X(c, c1)").WithArguments("scoped C c").WithLocation(12, 18)
24988-
);
24996+
CreateCompilation(source).VerifyDiagnostics();
2498924997
}
2499024998

2499124999
/// <summary>

0 commit comments

Comments
 (0)