Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ref analysis of self-assignment #75618

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2920,11 +2920,6 @@ private bool CheckInvocationArgMixingWithUpdatedRules(
var toArgEscape = GetValEscape(mixableArg.Argument, scopeOfTheContainingExpression);
foreach (var (fromParameter, fromArg, escapeKind, isRefEscape) in escapeValues)
{
if (mixableArg.Parameter is not null && object.ReferenceEquals(mixableArg.Parameter, fromParameter))
{
continue;
}

// This checks to see if the EscapeValue could ever be assigned to this argument based
// on comparing the EscapeLevel of both. If this could never be assigned due to
// this then we don't need to consider it for MAMM analysis.
Expand Down
34 changes: 34 additions & 0 deletions src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26475,6 +26475,40 @@ static ref S F2(S x2)
}
}";

var comp = CreateCompilation(source, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics);
comp.VerifyEmitDiagnostics(
// (12,26): error CS8350: This combination of arguments to 'Program<S>.F1(ref S)' is disallowed because it may expose variables referenced by parameter 'x1' outside of their declaration scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a variation of this test where the signature of F1 is

static ref S F1(ref S x1)

// ref var y2 = ref F1(ref x2);
Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x2)").WithArguments("Program<S>.F1(ref S)", "x1").WithLocation(12, 26),
// (12,33): error CS8166: Cannot return a parameter by reference 'x2' because it is not a ref parameter
// ref var y2 = ref F1(ref x2);
Diagnostic(ErrorCode.ERR_RefReturnParameter, "x2").WithArguments("x2").WithLocation(12, 33),
// (13,20): error CS8157: Cannot return 'y2' by reference because it was initialized to a value that cannot be returned by reference
// return ref y2; // 1
Diagnostic(ErrorCode.ERR_RefReturnNonreturnableLocal, "y2").WithArguments("y2").WithLocation(13, 20)
);
}

[Fact]
public void ReturnRefToByValueParameter_02()
{
var source =
@"
using System.Diagnostics.CodeAnalysis;

class Program<S> where S : allows ref struct
{
static ref S F1(ref S x1)
{
return ref x1;
}
static ref S F2(S x2)
{
ref var y2 = ref F1(ref x2);
return ref y2; // 1
}
}";

var comp = CreateCompilation(source, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics);
comp.VerifyEmitDiagnostics(
// (13,20): error CS8157: Cannot return 'y2' by reference because it was initialized to a value that cannot be returned by reference
Expand Down
132 changes: 132 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10170,5 +10170,137 @@ public void Utf8Addition()
""";
CreateCompilation(code, targetFramework: TargetFramework.Net70).VerifyDiagnostics();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75592")]
public void SelfAssignment_ReturnOnly()
{
var source = """
S s = default;
S.M(ref s);

ref struct S
jjonescz marked this conversation as resolved.
Show resolved Hide resolved
{
int field;
ref int refField;

public static void M(ref S s)
{
s.refField = ref s.field;
}
}
""";
CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(
// (11,9): error CS9079: Cannot ref-assign 's.field' to 'refField' because 's.field' can only escape the current method through a return statement.
// s.refField = ref s.field;
Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "s.refField = ref s.field").WithArguments("refField", "s.field").WithLocation(11, 9));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75592")]
public void SelfAssignment_CallerContext()
{
var source = """
using System.Diagnostics.CodeAnalysis;

S s = default;
S.M(ref s);

ref struct S
{
int field;
ref int refField;

public static void M([UnscopedRef] ref S s)
{
s.refField = ref s.field;
jjonescz marked this conversation as resolved.
Show resolved Hide resolved
}
}
""";
CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(
// (4,1): error CS8350: This combination of arguments to 'S.M(ref S)' is disallowed because it may expose variables referenced by parameter 's' outside of their declaration scope
// S.M(ref s);
Diagnostic(ErrorCode.ERR_CallArgMixing, "S.M(ref s)").WithArguments("S.M(ref S)", "s").WithLocation(4, 1),
// (4,9): error CS8168: Cannot return local 's' by reference because it is not a ref local
// S.M(ref s);
Diagnostic(ErrorCode.ERR_RefReturnLocal, "s").WithArguments("s").WithLocation(4, 9));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75592")]
public void SelfAssignment_CallerContext_StructReceiver()
{
var source = """
using System.Diagnostics.CodeAnalysis;

S s = default;
s.M();

ref struct S
{
int field;
ref int refField;

[UnscopedRef] public void M()
{
this.refField = ref this.field;
}
}
""";
CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(
// (13,9): error CS9079: Cannot ref-assign 'this.field' to 'refField' because 'this.field' can only escape the current method through a return statement.
// this.refField = ref this.field;
Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "this.refField = ref this.field").WithArguments("refField", "this.field").WithLocation(13, 9));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75592")]
public void SelfAssignment_CallerContext_Out()
{
var source = """
using System.Diagnostics.CodeAnalysis;

S s = default;
S.M(out s);

ref struct S
{
int field;
ref int refField;

public static void M([UnscopedRef] out S s)
{
s = default;
s.refField = ref s.field;
}
}
""";
CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(
// (4,1): error CS8350: This combination of arguments to 'S.M(out S)' is disallowed because it may expose variables referenced by parameter 's' outside of their declaration scope
// S.M(out s);
Diagnostic(ErrorCode.ERR_CallArgMixing, "S.M(out s)").WithArguments("S.M(out S)", "s").WithLocation(4, 1),
// (4,9): error CS8168: Cannot return local 's' by reference because it is not a ref local
// S.M(out s);
Diagnostic(ErrorCode.ERR_RefReturnLocal, "s").WithArguments("s").WithLocation(4, 9));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75592")]
public void SelfAssignment_CallerContext_Scoped()
{
var source = """
using System.Diagnostics.CodeAnalysis;

scoped S s = default;
S.M(ref s);

ref struct S
{
int field;
ref int refField;

public static void M([UnscopedRef] ref S s)
{
s.refField = ref s.field;
}
}
""";
CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics();
}
}
}
20 changes: 19 additions & 1 deletion src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9757,6 +9757,12 @@ static ref S<T> F2<T>(S<T> x2)
else
{
comp.VerifyEmitDiagnostics(
// (14,26): error CS8350: This combination of arguments to 'Program.F1<T>(ref S<T>)' is disallowed because it may expose variables referenced by parameter 'x1' outside of their declaration scope
// ref var y2 = ref F1(ref x2);
Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x2)").WithArguments("Program.F1<T>(ref S<T>)", "x1").WithLocation(14, 26),
// (14,33): error CS8166: Cannot return a parameter by reference 'x2' because it is not a ref parameter
// ref var y2 = ref F1(ref x2);
Diagnostic(ErrorCode.ERR_RefReturnParameter, "x2").WithArguments("x2").WithLocation(14, 33),
// (15,20): error CS8157: Cannot return 'y2' by reference because it was initialized to a value that cannot be returned by reference
// return ref y2; // 1
Diagnostic(ErrorCode.ERR_RefReturnNonreturnableLocal, "y2").WithArguments("y2").WithLocation(15, 20));
Expand Down Expand Up @@ -29523,9 +29529,15 @@ private ref struct RefStruct
// (9,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'.
// GetReference2(in s1).RefField = ref value; // 2
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "GetReference2(in s1).RefField = ref value").WithArguments("RefField", "value").WithLocation(9, 9),
// (10,9): error CS8350: This combination of arguments to 'Repro.GetReference3(out Repro.RefStruct)' is disallowed because it may expose variables referenced by parameter 'rs' outside of their declaration scope
// GetReference3(out s1).RefField = ref value; // 3
Diagnostic(ErrorCode.ERR_CallArgMixing, "GetReference3(out s1)").WithArguments("Repro.GetReference3(out Repro.RefStruct)", "rs").WithLocation(10, 9),
// (10,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'.
// GetReference3(out s1).RefField = ref value; // 3
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "GetReference3(out s1).RefField = ref value").WithArguments("RefField", "value").WithLocation(10, 9),
// (10,27): error CS8168: Cannot return local 's1' by reference because it is not a ref local
// GetReference3(out s1).RefField = ref value; // 3
Diagnostic(ErrorCode.ERR_RefReturnLocal, "s1").WithArguments("s1").WithLocation(10, 27),
// (12,9): error CS8332: Cannot assign to a member of method 'GetReadonlyReference1' or use it as the right hand side of a ref assignment because it is a readonly variable
// GetReadonlyReference1(ref s1).RefField = ref value; // 4
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "GetReadonlyReference1(ref s1).RefField").WithArguments("method", "GetReadonlyReference1").WithLocation(12, 9),
Expand All @@ -29534,7 +29546,13 @@ private ref struct RefStruct
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "GetReadonlyReference2(in s1).RefField").WithArguments("method", "GetReadonlyReference2").WithLocation(13, 9),
// (14,9): error CS8332: Cannot assign to a member of method 'GetReadonlyReference3' or use it as the right hand side of a ref assignment because it is a readonly variable
// GetReadonlyReference3(out s1).RefField = ref value; // 6
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "GetReadonlyReference3(out s1).RefField").WithArguments("method", "GetReadonlyReference3").WithLocation(14, 9));
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "GetReadonlyReference3(out s1).RefField").WithArguments("method", "GetReadonlyReference3").WithLocation(14, 9),
// (14,9): error CS8350: This combination of arguments to 'Repro.GetReadonlyReference3(out Repro.RefStruct)' is disallowed because it may expose variables referenced by parameter 'rs' outside of their declaration scope
// GetReadonlyReference3(out s1).RefField = ref value; // 6
Diagnostic(ErrorCode.ERR_CallArgMixing, "GetReadonlyReference3(out s1)").WithArguments("Repro.GetReadonlyReference3(out Repro.RefStruct)", "rs").WithLocation(14, 9),
// (14,35): error CS8168: Cannot return local 's1' by reference because it is not a ref local
// GetReadonlyReference3(out s1).RefField = ref value; // 6
Diagnostic(ErrorCode.ERR_RefReturnLocal, "s1").WithArguments("s1").WithLocation(14, 35));
}

[WorkItem(66128, "https://github.com/dotnet/roslyn/issues/66128")]
Expand Down
Loading