Skip to content

Commit cab096c

Browse files
committed
Check ref safety of arg mixing in interpolated strings
1 parent bbff74e commit cab096c

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,12 @@ internal SafeContext GetInterpolatedStringHandlerConversionEscapeScope(
19401940
escapeScope = escapeScope.Intersect(argEscape);
19411941
}
19421942

1943+
if (!scopeOfTheContainingExpression.IsConvertibleTo(escapeScope) &&
1944+
!CheckInterpolatedStringHandlerInvocationArgMixing(expression, escapeFrom: scopeOfTheContainingExpression, escapeTo: escapeScope, BindingDiagnosticBag.Discarded))
1945+
{
1946+
escapeScope = scopeOfTheContainingExpression;
1947+
}
1948+
19431949
arguments.Free();
19441950
return escapeScope;
19451951
}
@@ -4474,6 +4480,11 @@ internal SafeContext GetValEscape(BoundExpression expr, SafeContext scopeOfTheCo
44744480
return scopeOfTheContainingExpression;
44754481

44764482
case BoundKind.InterpolatedStringHandlerPlaceholder:
4483+
if (_placeholderScopes?.TryGetValue((BoundInterpolatedStringHandlerPlaceholder)expr, out var scope) == true)
4484+
{
4485+
return scope;
4486+
}
4487+
44774488
// The handler placeholder cannot escape out of the current expression, as it's a compiler-synthesized
44784489
// location.
44794490
return scopeOfTheContainingExpression;
@@ -4767,6 +4778,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext
47674778
case BoundKind.DeconstructValuePlaceholder:
47684779
case BoundKind.AwaitableValuePlaceholder:
47694780
case BoundKind.InterpolatedStringArgumentPlaceholder:
4781+
case BoundKind.InterpolatedStringHandlerPlaceholder:
47704782
if (!GetPlaceholderScope((BoundValuePlaceholderBase)expr).IsConvertibleTo(escapeTo))
47714783
{
47724784
Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax);
@@ -5610,6 +5622,11 @@ private bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expr
56105622
}
56115623
}
56125624

5625+
if (result)
5626+
{
5627+
result = CheckInterpolatedStringHandlerInvocationArgMixing(expression, escapeFrom, escapeTo, diagnostics);
5628+
}
5629+
56135630
arguments.Free();
56145631
return result;
56155632
}
@@ -5661,5 +5678,54 @@ void getParts(BoundInterpolatedString interpolatedString)
56615678
}
56625679
}
56635680
}
5681+
5682+
private bool CheckInterpolatedStringHandlerInvocationArgMixing(BoundExpression expression, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
5683+
{
5684+
bool result = true;
5685+
5686+
while (true)
5687+
{
5688+
switch (expression)
5689+
{
5690+
case BoundBinaryOperator binary:
5691+
result &= CheckInterpolatedStringHandlerInvocationArgMixing(binary.Right, escapeFrom, escapeTo, diagnostics);
5692+
expression = binary.Left;
5693+
break;
5694+
5695+
case BoundInterpolatedString interpolatedString:
5696+
result &= CheckInterpolatedStringHandlerInvocationArgMixingParts(interpolatedString, escapeFrom, escapeTo, diagnostics);
5697+
return result;
5698+
5699+
default:
5700+
throw ExceptionUtilities.UnexpectedValue(expression.Kind);
5701+
}
5702+
}
5703+
}
5704+
5705+
private bool CheckInterpolatedStringHandlerInvocationArgMixingParts(BoundInterpolatedString interpolatedString, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
5706+
{
5707+
bool result = true;
5708+
5709+
foreach (var part in interpolatedString.Parts)
5710+
{
5711+
if (part is BoundCall { Method.Name: BoundInterpolatedString.AppendFormattedMethod } call)
5712+
{
5713+
using var _ = new PlaceholderRegion(this, [((BoundInterpolatedStringHandlerPlaceholder)call.ReceiverOpt, escapeTo)]);
5714+
result &= CheckInvocationArgMixing(
5715+
call.Syntax,
5716+
MethodInfo.Create(call.Method),
5717+
receiverOpt: call.ReceiverOpt,
5718+
receiverIsSubjectToCloning: call.InitialBindingReceiverIsSubjectToCloning,
5719+
parameters: call.Method.Parameters,
5720+
argsOpt: call.Arguments,
5721+
argRefKindsOpt: call.ArgumentRefKindsOpt,
5722+
argsToParamsOpt: call.ArgsToParamsOpt,
5723+
scopeOfTheContainingExpression: escapeFrom,
5724+
diagnostics);
5725+
}
5726+
}
5727+
5728+
return result;
5729+
}
56645730
}
56655731
}

src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10227,6 +10227,123 @@ public void Utf8Addition()
1022710227
CreateCompilation(code, targetFramework: TargetFramework.Net70).VerifyDiagnostics();
1022810228
}
1022910229

10230+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")]
10231+
public void InterpolatedString_UnscopedRef_Return()
10232+
{
10233+
var source = """
10234+
using System.Diagnostics.CodeAnalysis;
10235+
using System.Runtime.CompilerServices;
10236+
10237+
class C
10238+
{
10239+
R M()
10240+
{
10241+
var local = 1;
10242+
return $"{local}";
10243+
}
10244+
}
10245+
10246+
[InterpolatedStringHandlerAttribute]
10247+
ref struct R
10248+
{
10249+
public R(int literalLength, int formattedCount) { }
10250+
10251+
public void AppendFormatted([UnscopedRef] in int x) { }
10252+
}
10253+
""";
10254+
CreateCompilation([source, UnscopedRefAttributeDefinition, InterpolatedStringHandlerAttribute]).VerifyDiagnostics(
10255+
// (9,18): error CS8350: This combination of arguments to 'R.AppendFormatted(in int)' is disallowed because it may expose variables referenced by parameter 'x' outside of their declaration scope
10256+
// return $"{local}";
10257+
Diagnostic(ErrorCode.ERR_CallArgMixing, "{local}").WithArguments("R.AppendFormatted(in int)", "x").WithLocation(9, 18),
10258+
// (9,19): error CS8168: Cannot return local 'local' by reference because it is not a ref local
10259+
// return $"{local}";
10260+
Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(9, 19));
10261+
}
10262+
10263+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")]
10264+
public void InterpolatedString_UnscopedRef_Assignment()
10265+
{
10266+
var source = """
10267+
using System.Diagnostics.CodeAnalysis;
10268+
using System.Runtime.CompilerServices;
10269+
10270+
class C
10271+
{
10272+
R M()
10273+
{
10274+
var local = 1;
10275+
R r = $"{local}";
10276+
return r;
10277+
}
10278+
}
10279+
10280+
[InterpolatedStringHandlerAttribute]
10281+
ref struct R
10282+
{
10283+
public R(int literalLength, int formattedCount) { }
10284+
10285+
public void AppendFormatted([UnscopedRef] in int x) { }
10286+
}
10287+
""";
10288+
CreateCompilation([source, UnscopedRefAttributeDefinition, InterpolatedStringHandlerAttribute]).VerifyDiagnostics(
10289+
// (10,16): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope
10290+
// return r;
10291+
Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(10, 16));
10292+
}
10293+
10294+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")]
10295+
public void InterpolatedString_ScopedRef_Return()
10296+
{
10297+
var source = """
10298+
using System.Runtime.CompilerServices;
10299+
10300+
class C
10301+
{
10302+
R M()
10303+
{
10304+
var local = 1;
10305+
return $"{local}";
10306+
}
10307+
}
10308+
10309+
[InterpolatedStringHandlerAttribute]
10310+
ref struct R
10311+
{
10312+
public R(int literalLength, int formattedCount) { }
10313+
10314+
public void AppendFormatted(in int x) { }
10315+
}
10316+
""";
10317+
CreateCompilation([source, InterpolatedStringHandlerAttribute]).VerifyDiagnostics();
10318+
}
10319+
10320+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")]
10321+
public void InterpolatedString_ScopedRef_Assignment()
10322+
{
10323+
var source = """
10324+
using System.Runtime.CompilerServices;
10325+
10326+
class C
10327+
{
10328+
R M()
10329+
{
10330+
var local = 1;
10331+
R r = $"{local}";
10332+
return r;
10333+
}
10334+
}
10335+
10336+
[InterpolatedStringHandlerAttribute]
10337+
ref struct R
10338+
{
10339+
public R(int literalLength, int formattedCount) { }
10340+
10341+
public void AppendFormatted(in int x) { }
10342+
}
10343+
""";
10344+
CreateCompilation([source, InterpolatedStringHandlerAttribute]).VerifyDiagnostics();
10345+
}
10346+
1023010347
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75592")]
1023110348
public void SelfAssignment_ReturnOnly()
1023210349
{

0 commit comments

Comments
 (0)