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

Adjust calculation of invocation escape when return type is ref to ref struct #65857

Merged
merged 8 commits into from
Dec 21, 2022

Conversation

RikkiGibson
Copy link
Contributor

Implementation of dotnet/csharplang#6781

Closes #65648

@RikkiGibson RikkiGibson marked this pull request as ready for review December 8, 2022 19:58
@RikkiGibson RikkiGibson requested a review from a team as a code owner December 8, 2022 19:58
@cston
Copy link
Member

cston commented Dec 9, 2022

        foreach (var argAndParam in argsAndParamsAll)

Do we need to consider only ref-to-ref-struct parameters here when the method returns a ref-to-ref-struct?


In reply to: 1343737470


Refers to: src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs:1889 in a8ea1ae. [](commit_id = a8ea1ae, deletion_comment = False)

{
// SPEC:
// If `M()` does return ref-to-ref-struct, the *safe-to-escape* is the same as the *safe-to-escape* of all arguments which are ref-to-ref-struct.
// If `M()` does return ref-to-ref-struct, the *ref-safe-to-escape* is the narrowest *ref-safe-to-escape* contributed by all arguments which are ref-to-ref-struct.
Copy link
Member

@cston cston Dec 9, 2022

Choose a reason for hiding this comment

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

is the narrowest ref-safe-to-escape contributed by all arguments which are ref-to-ref-struct

When will the ref-safe-to-escape of the ref-to-ref-struct arguments that contribute be different? When one is [UnscopedRef]? If so, consider testing multiple parameters with a mix of { unannotated, [UnscopedRef] } and also { ref, in, out }.

Copy link
Contributor Author

@RikkiGibson RikkiGibson Dec 9, 2022

Choose a reason for hiding this comment

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

We can take a reference to a variable from any scope, so one case where they might be different is e.g.

RS rs1 = M();
{
    RS rs2 = M();
    ref RS result = ref M(ref rs1, ref rs2);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is tested in ReturnRefToRefStruct_RefEscape_01

private static void Bad3(scoped ref RefStruct s1, int value)
{
s1.RefMethod() = new RefStruct(ref value); // 3
}
Copy link
Member

@cston cston Dec 14, 2022

Choose a reason for hiding this comment

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

}

Consider testing a static method with a ref-to-ref-struct argument if you haven't already (same comment as #65857 (comment)).

static void Bad4(scoped ref RefStruct s4, int value)
{
    GetRef(ref s4) = new RefStruct(ref value); // 4
}

static ref RefStruct GetRef(ref RefStruct s) => ref s;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this in to ReturnRefToRefStruct_ValEscape_03.

}

[Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")]
public void ReturnRefToRefStruct_RefEscape_01()
Copy link
Member

Choose a reason for hiding this comment

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

ReturnRefToRefStruct_RefEscape_01

Is this test affected by the changes in the PR? It doesn't look like it, but I wanted to confirm I wasn't overlooking something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test verifies the following line of the specification

If M() does return ref-to-ref-struct, the ref-safe-to-escape is the narrowest ref-safe-to-escape contributed by all arguments which are ref-to-ref-struct.

The behavior in practice is not new, though. I couldn't think of a scenario which tests the behavior more deeply, e.g. something where we pick the narrowest of multiple input ref escapes, and ignore an even narrower STE. since the narrowest STE is necessarily wider or equal to the narrowest RSTE.


GetReadonlyReference1(ref s1).RefField = ref value; // 4
GetReadonlyReference2(in s1).RefField = ref value; // 5
GetReadonlyReference3(out s1).RefField = ref value; // 6
Copy link
Member

Choose a reason for hiding this comment

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

Are these cases affected by the changes in this PR? It looks like we already report these errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the behavior is not changed, but it still feels relevant to test this case, because we can still end up in a path in escape analysis where the method returns readonly ref to ref struct with various kinds of parameters. Basically, I just don't want anything to fall over in that combination, or to unexpectedly permit any of these assignments (as unlikely as that may be).

@RikkiGibson RikkiGibson requested a review from a team December 19, 2022 19:36
@RikkiGibson
Copy link
Contributor Author

@dotnet/roslyn-compiler for a second review

foreach (var (param, argument, _, isArgumentRefEscape) in argsAndParamsAll)
{
// SPEC:
// If `M()` does return ref-to-ref-struct, the *safe-to-escape* is the same as the *safe-to-escape* of all arguments which are ref-to-ref-struct. It is an error if there are multiple arguments with different *safe-to-escape* because of *method arguments must match*.
Copy link
Member

Choose a reason for hiding this comment

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

It is an error if there are multiple arguments with different safe-to-escape because of method arguments must match.

nit: skip second sentence?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We felt this was more clear to include. It's meant to explain why we are still calculating the narrowest scope here (Math.Max) instead of requiring equal scopes--you shouldn't worry about the case where the scopes actually differ, since an error will be reported elsewhere.

// (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));
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: consider test invocation of a ref R ReturnRef(scoped ref R r, ref R r) method

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 5) with a couple nits to consider

@jcouv jcouv self-assigned this Dec 20, 2022
@RikkiGibson RikkiGibson enabled auto-merge (squash) December 20, 2022 22:54
@RikkiGibson RikkiGibson merged commit 76cd2f1 into dotnet:main Dec 21, 2022
@ghost ghost added this to the Next milestone Dec 21, 2022
@Cosifne Cosifne modified the milestones: Next, 17.5 P3 Jan 4, 2023
@RikkiGibson RikkiGibson deleted the unscopedref-property-escape branch January 7, 2023 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Writes through UnscopedRef allows bypassing lifetime rules
4 participants