diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index b8a82274d061a..163868061b55d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -327,6 +327,16 @@ public static MethodInvocationInfo FromProperty(BoundPropertyAccess propertyAcce ReceiverIsSubjectToCloning = propertyAccess.InitialBindingReceiverIsSubjectToCloning, HasAnyErrors = propertyAccess.HasAnyErrors, }; + + public static MethodInvocationInfo FromCollectionElementInitializer(BoundCollectionElementInitializer colElement) + => new MethodInvocationInfo + { + MethodInfo = MethodInfo.Create(colElement.AddMethod), + Parameters = colElement.AddMethod.Parameters, + Receiver = colElement.ImplicitReceiverOpt, + ArgsOpt = colElement.Arguments, + ArgsToParamsOpt = colElement.ArgsToParamsOpt, + }; } /// @@ -2176,16 +2186,9 @@ internal SafeContext GetInterpolatedStringHandlerConversionEscapeScope( _visited = previousVisited; #endif - var arguments = ArrayBuilder.GetInstance(); - GetInterpolatedStringHandlerArgumentsForEscape(expression, arguments); + // Narrow the scope for implicit calls which allow the receiver to capture refs from the arguments. + escapeScope = escapeScope.Intersect(GetValEscapeOfInterpolatedStringHandlerCalls(expression, localScopeDepth)); - foreach (var argument in arguments) - { - SafeContext argEscape = GetValEscape(argument, localScopeDepth); - escapeScope = escapeScope.Intersect(argEscape); - } - - arguments.Free(); return escapeScope; } @@ -2326,6 +2329,35 @@ private SafeContext GetInvocationEscapeWithUpdatedRules( return escapeScope; } + private SafeContext GetInvocationEscapeToReceiver( + in MethodInvocationInfo methodInvocationInfo, + SafeContext localScopeDepth) + { + // By default it is safe to escape. + SafeContext escapeScope = SafeContext.CallingMethod; + + var escapeValues = ArrayBuilder.GetInstance(); + GetFilteredInvocationArgumentsForEscapeToReceiver(in methodInvocationInfo, escapeValues); + + foreach (var (_, argument, _, isArgumentRefEscape) in escapeValues) + { + SafeContext argEscape = isArgumentRefEscape + ? GetRefEscape(argument, localScopeDepth) + : GetValEscape(argument, localScopeDepth); + + escapeScope = escapeScope.Intersect(argEscape); + if (localScopeDepth.IsConvertibleTo(escapeScope)) + { + // Can't get any worse. + break; + } + } + + escapeValues.Free(); + + return escapeScope; + } + private static MethodInvocationInfo ReplaceWithExtensionImplementationIfNeeded(ref readonly MethodInvocationInfo methodInvocationInfo) { Symbol? symbol = methodInvocationInfo.MethodInfo.Symbol; @@ -2534,6 +2566,38 @@ private bool CheckInvocationEscapeWithUpdatedRules( return result; } + private bool CheckInvocationEscapeToReceiver( + SyntaxNode syntax, + in MethodInvocationInfo methodInvocationInfo, + bool checkingReceiver, + SafeContext escapeFrom, + SafeContext escapeTo, + BindingDiagnosticBag diagnostics) + { + bool result = true; + + var escapeValues = ArrayBuilder.GetInstance(); + GetFilteredInvocationArgumentsForEscapeToReceiver(in methodInvocationInfo, escapeValues); + + foreach (var (parameter, argument, _, isArgumentRefEscape) in escapeValues) + { + bool valid = isArgumentRefEscape + ? CheckRefEscape(argument.Syntax, argument, escapeFrom, escapeTo, checkingReceiver: false, diagnostics) + : CheckValEscape(argument.Syntax, argument, escapeFrom, escapeTo, checkingReceiver: false, diagnostics); + + if (!valid) + { + ReportInvocationEscapeError(syntax, methodInvocationInfo.MethodInfo.Symbol, parameter, checkingReceiver, diagnostics); + result = false; + break; + } + } + + escapeValues.Free(); + + return result; + } + /// /// Returns the set of arguments to be considered for escape analysis of a method invocation. This /// set potentially includes the receiver of the method call. Each argument is returned (only once) @@ -2772,6 +2836,82 @@ static bool hasRefLikeReturn(Symbol symbol) } } + private void GetFilteredInvocationArgumentsForEscapeToReceiver( + ref readonly MethodInvocationInfo methodInvocationInfo, + ArrayBuilder escapeValues) + { + var localMethodInvocationInfo = ReplaceWithExtensionImplementationIfNeeded(in methodInvocationInfo); + var methodInfo = localMethodInvocationInfo.MethodInfo; + + // If the receiver is not a ref to a ref struct, it cannot capture anything. + ParameterSymbol? extensionReceiver = null; + if (methodInfo.Symbol.RequiresInstanceReceiver()) + { + // We have an instance method receiver. + if (!hasRefToRefStructThis(methodInfo.Method) && !hasRefToRefStructThis(methodInfo.SetMethod)) + { + return; + } + } + else + { + // We have a classic extension method receiver. + Debug.Assert(methodInfo.Method?.IsExtensionMethod != false); + + if (localMethodInvocationInfo.Parameters is [var extReceiver, ..]) + { + extensionReceiver = extReceiver; + if (!isRefToRefStruct(extensionReceiver)) + { + return; + } + } + } + + var unfilteredEscapeValues = ArrayBuilder.GetInstance(); + GetEscapeValues( + // We do not need the receiver in `escapeValues`. + localMethodInvocationInfo with { Receiver = null }, + ignoreArglistRefKinds: true, + mixableArguments: null, + unfilteredEscapeValues); + + foreach (var (parameter, argument, escapeLevel, isArgumentRefEscape) in unfilteredEscapeValues) + { + // Skip if this is the extension method receiver. + if (extensionReceiver is not null && parameter == extensionReceiver) + { + continue; + } + + // We did not pass the instance method receiver to GetEscapeValues so we cannot encounter it here. + Debug.Assert(parameter?.IsThis != true); + + // Skip if the parameter cannot escape from the method to the receiver. + if (escapeLevel != EscapeLevel.CallingMethod) + { + Debug.Assert(escapeLevel == EscapeLevel.ReturnOnly); + continue; + } + + escapeValues.Add(new EscapeValue(parameter, argument, escapeLevel, isArgumentRefEscape)); + } + + unfilteredEscapeValues.Free(); + + static bool hasRefToRefStructThis(MethodSymbol? method) + { + return method?.TryGetThisParameter(out var thisParameter) == true && + isRefToRefStruct(thisParameter); + } + + static bool isRefToRefStruct(ParameterSymbol parameter) + { + return parameter.RefKind == RefKind.Ref && + parameter.Type.IsRefLikeOrAllowsRefLikeType(); + } + } + /// /// Returns the set of to an invocation that impact ref analysis. /// This will filter out everything that could never meaningfully contribute to ref analysis. @@ -4531,11 +4671,7 @@ internal SafeContext GetValEscape(BoundExpression expr, SafeContext localScopeDe case BoundKind.CollectionInitializerExpression: var colExpr = (BoundCollectionInitializerExpression)expr; - return GetValEscape(colExpr.Initializers, localScopeDepth); - - case BoundKind.CollectionElementInitializer: - var colElement = (BoundCollectionElementInitializer)expr; - return GetValEscape(colElement.Arguments, localScopeDepth); + return GetValEscapeOfCollectionInitializer(colExpr, localScopeDepth); case BoundKind.ObjectInitializerMember: // this node generally makes no sense outside of the context of containing initializer @@ -4647,6 +4783,20 @@ private SafeContext GetTupleValEscape(ImmutableArray elements, return narrowestScope; } + private SafeContext GetValEscapeOfCollectionInitializer(BoundCollectionInitializerExpression colExpr, SafeContext localScopeDepth) + { + var result = SafeContext.CallingMethod; + foreach (var expr in colExpr.Initializers) + { + result = result.Intersect(expr is BoundCollectionElementInitializer colElement + ? GetInvocationEscapeToReceiver( + MethodInvocationInfo.FromCollectionElementInitializer(colElement), + localScopeDepth) + : GetValEscape(expr, localScopeDepth)); + } + return result; + } + /// /// The escape value of an object initializer is calculated by looking at all of the /// expressions that can be stored into the implicit receiver. That means arguments @@ -5288,17 +5438,9 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext var initExpr = (BoundObjectInitializerExpression)expr; return CheckValEscapeOfObjectInitializer(initExpr, escapeFrom, escapeTo, diagnostics); - // this would be correct implementation for CollectionInitializerExpression - // however it is unclear if it is reachable since the initialized type must implement IEnumerable case BoundKind.CollectionInitializerExpression: var colExpr = (BoundCollectionInitializerExpression)expr; - return CheckValEscape(colExpr.Initializers, escapeFrom, escapeTo, diagnostics); - - // this would be correct implementation for CollectionElementInitializer - // however it is unclear if it is reachable since the initialized type must implement IEnumerable - case BoundKind.CollectionElementInitializer: - var colElement = (BoundCollectionElementInitializer)expr; - return CheckValEscape(colElement.Arguments, escapeFrom, escapeTo, diagnostics); + return CheckValEscapeOfCollectionInitializer(colExpr, escapeFrom, escapeTo, diagnostics); case BoundKind.PointerElementAccess: var accessedExpression = ((BoundPointerElementAccess)expr).Expression; @@ -5578,13 +5720,25 @@ private bool CheckTupleValEscape(ImmutableArray elements, SafeC #nullable enable - private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckValEscapeOfCollectionInitializer(BoundCollectionInitializerExpression colExpr, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { - foreach (var expr in initExpr.Initializers) + foreach (var expr in colExpr.Initializers) { - if (!GetValEscapeOfObjectMemberInitializer(expr, escapeFrom).IsConvertibleTo(escapeTo)) + if (expr is BoundCollectionElementInitializer colElement) + { + if (!CheckInvocationEscapeToReceiver( + colElement.Syntax, + MethodInvocationInfo.FromCollectionElementInitializer(colElement), + checkingReceiver: false, + escapeFrom, + escapeTo, + diagnostics)) + { + return false; + } + } + else if (!CheckValEscape(expr.Syntax, expr, escapeFrom, escapeTo, checkingReceiver: false, diagnostics)) { - Error(diagnostics, _inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, initExpr.Syntax, expr.Syntax); return false; } } @@ -5592,14 +5746,13 @@ private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression return true; } -#nullable disable - - private bool CheckValEscape(ImmutableArray expressions, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { - foreach (var expression in expressions) + foreach (var expr in initExpr.Initializers) { - if (!CheckValEscape(expression.Syntax, expression, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics)) + if (!GetValEscapeOfObjectMemberInitializer(expr, escapeFrom).IsConvertibleTo(escapeTo)) { + Error(diagnostics, _inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, initExpr.Syntax, expr.Syntax); return false; } } @@ -5607,6 +5760,8 @@ private bool CheckValEscape(ImmutableArray expressions, SafeCon return true; } +#nullable disable + private bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expression, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { var data = expression.GetInterpolatedStringHandlerData(); @@ -5623,73 +5778,105 @@ private bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expr var previousVisited = _visited; _visited = null; #endif - CheckValEscape(expression.Syntax, data.Construction, escapeFrom, escapeTo, checkingReceiver: false, diagnostics); + bool result = CheckValEscape(expression.Syntax, data.Construction, escapeFrom, escapeTo, checkingReceiver: false, diagnostics); #if DEBUG _visited = previousVisited; #endif - var arguments = ArrayBuilder.GetInstance(); - GetInterpolatedStringHandlerArgumentsForEscape(expression, arguments); + // Narrow the scope for implicit calls which allow the receiver to capture refs from the arguments. + result = result && CheckValEscapeOfInterpolatedStringHandlerCalls(expression, escapeFrom, escapeTo, diagnostics); - bool result = true; - foreach (var argument in arguments) + return result; + } + + private SafeContext GetValEscapeOfInterpolatedStringHandlerCalls(BoundExpression expression, SafeContext localScopeDepth) + { + SafeContext scope = SafeContext.CallingMethod; + + while (true) { - if (!CheckValEscape(argument.Syntax, argument, escapeFrom, escapeTo, checkingReceiver: false, diagnostics)) + switch (expression) { - result = false; - break; + case BoundBinaryOperator binary: + scope = scope.Intersect(GetValEscapeOfInterpolatedStringHandlerCalls(binary.Right, localScopeDepth)); + expression = binary.Left; + break; + + case BoundInterpolatedString interpolatedString: + return scope.Intersect(getPartsScope(interpolatedString, localScopeDepth)); + + default: + throw ExceptionUtilities.UnexpectedValue(expression.Kind); } } - arguments.Free(); - return result; + SafeContext getPartsScope(BoundInterpolatedString interpolatedString, SafeContext localScopeDepth) + { + SafeContext scope = SafeContext.CallingMethod; + + foreach (var part in interpolatedString.Parts) + { + if (part is not BoundCall call) + { + // Dynamic calls cannot have ref struct parameters. + continue; + } + + scope = scope.Intersect(GetInvocationEscapeToReceiver( + MethodInvocationInfo.FromCall(call), + localScopeDepth)); + } + + return scope; + } } - private void GetInterpolatedStringHandlerArgumentsForEscape(BoundExpression expression, ArrayBuilder arguments) + private bool CheckValEscapeOfInterpolatedStringHandlerCalls(BoundExpression expression, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { while (true) { switch (expression) { case BoundBinaryOperator binary: - GetInterpolatedStringHandlerArgumentsForEscape(binary.Right, arguments); + if (!CheckValEscapeOfInterpolatedStringHandlerCalls(binary.Right, escapeFrom, escapeTo, diagnostics)) + { + return false; + } + expression = binary.Left; break; case BoundInterpolatedString interpolatedString: - getParts(interpolatedString); - return; + return checkParts(interpolatedString, escapeFrom, escapeTo, diagnostics); default: throw ExceptionUtilities.UnexpectedValue(expression.Kind); } } - void getParts(BoundInterpolatedString interpolatedString) + bool checkParts(BoundInterpolatedString interpolatedString, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { foreach (var part in interpolatedString.Parts) { - if (part is not BoundCall { Method.Name: BoundInterpolatedString.AppendFormattedMethod } call) + if (part is not BoundCall call) { - // Dynamic calls cannot have ref struct parameters, and AppendLiteral calls will always have literal - // string arguments and do not require us to be concerned with escape + // Dynamic calls cannot have ref struct parameters. continue; } - // The interpolation component is always the first argument to the method, and it was not passed by name - // so there can be no reordering. - - // SPEC: For a given argument `a` that is passed to parameter `p`: - // SPEC: 1. ... - // SPEC: 2. If `p` is `scoped` then `a` does not contribute *safe-to-escape* when considering arguments. - if (_useUpdatedEscapeRules && - call.Method.Parameters[0].EffectiveScope == ScopedKind.ScopedValue) + if (!CheckInvocationEscapeToReceiver( + call.Syntax, + MethodInvocationInfo.FromCall(call), + checkingReceiver: false, + escapeFrom, + escapeTo, + diagnostics)) { - continue; + return false; } - - arguments.Add(call.Arguments[0]); } + + return true; } } } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 8016f782e7764..805ade41291fe 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -23377,6 +23377,9 @@ MyStruct localFunc() // (6,12): error CS8352: Cannot use variable 'new MyStruct() { i = ref i }' in this context because it may expose referenced variables outside of their declaration scope // return new MyStruct() { i = ref i }.M($""); Diagnostic(ErrorCode.ERR_EscapeVariable, "new MyStruct() { i = ref i }").WithArguments("new MyStruct() { i = ref i }").WithLocation(6, 12), + // (6,12): error CS8347: Cannot use a result of 'E.extension(scoped MyStruct).M(InterpolationHandler)' in this context because it may expose variables referenced by parameter 'h' outside of their declaration scope + // return new MyStruct() { i = ref i }.M($""); + Diagnostic(ErrorCode.ERR_EscapeCall, @"new MyStruct() { i = ref i }.M($"""")").WithArguments("E.extension(scoped MyStruct).M(InterpolationHandler)", "h").WithLocation(6, 12), // (6,43): error CS8347: Cannot use a result of 'InterpolationHandler.InterpolationHandler(int, int, MyStruct)' in this context because it may expose variables referenced by parameter 's2' outside of their declaration scope // return new MyStruct() { i = ref i }.M($""); Diagnostic(ErrorCode.ERR_EscapeCall, @"$""""").WithArguments("InterpolationHandler.InterpolationHandler(int, int, MyStruct)", "s2").WithLocation(6, 43) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index a84140d4ecea2..254772b5d70b2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -14810,6 +14810,9 @@ public static CustomHandler M() var expectedDiagnostics = new[] { + // (17,18): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return $"{s}"; + Diagnostic(ErrorCode.ERR_EscapeCall, "{s}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "s").WithLocation(17, 18), // (17,19): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope // return $"{s}"; Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(17, 19) @@ -14969,6 +14972,9 @@ public ref struct S1 // (17,9): error CS8350: This combination of arguments to 'CustomHandler.M2(ref S1, ref CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope // M2(ref s1, $"{s}"); Diagnostic(ErrorCode.ERR_CallArgMixing, @"M2(ref s1, " + expression + @")").WithArguments("CustomHandler.M2(ref S1, ref CustomHandler)", "handler").WithLocation(17, 9), + // (17,22): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // M2(ref s1, $"{s}"); + Diagnostic(ErrorCode.ERR_EscapeCall, "{s}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "s").WithLocation(17, 22), // (17,23): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope // M2(ref s1, $"{s}"); Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(17, 23)); @@ -14983,10 +14989,7 @@ public ref struct S1 Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "s1").WithLocation(17, 16), // (17,20): error CS8347: Cannot use a result of 'CustomHandler.CustomHandler(int, int, ref S1)' in this context because it may expose variables referenced by parameter 's1' outside of their declaration scope // M2(ref s1, $"{s}"); - Diagnostic(ErrorCode.ERR_EscapeCall, expression).WithArguments("CustomHandler.CustomHandler(int, int, ref S1)", "s1").WithLocation(17, 20), - // (17,23): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope - // M2(ref s1, $"{s}"); - Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(17, 23)); + Diagnostic(ErrorCode.ERR_EscapeCall, expression).WithArguments("CustomHandler.CustomHandler(int, int, ref S1)", "s1").WithLocation(17, 20)); } [Theory] @@ -15170,6 +15173,9 @@ public ref struct S1 // (15,9): error CS8350: This combination of arguments to 'CustomHandler.M2(ref S1, CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope // M2(ref s1, $"{s2}"); Diagnostic(ErrorCode.ERR_CallArgMixing, @"M2(ref s1, $""{s2}"")").WithArguments("CustomHandler.M2(ref S1, CustomHandler)", "handler").WithLocation(15, 9), + // (15,22): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // M2(ref s1, $"{s2}"); + Diagnostic(ErrorCode.ERR_EscapeCall, "{s2}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "s").WithLocation(15, 22), // (15,23): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope // M2(ref s1, $"{s2}"); Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(15, 23) @@ -15188,10 +15194,7 @@ public ref struct S1 Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "s1").WithLocation(15, 16), // (15,20): error CS8347: Cannot use a result of 'CustomHandler.CustomHandler(int, int, ref S1)' in this context because it may expose variables referenced by parameter 's1' outside of their declaration scope // M2(ref s1, $"{s2}"); - Diagnostic(ErrorCode.ERR_EscapeCall, @"$""{s2}""").WithArguments("CustomHandler.CustomHandler(int, int, ref S1)", "s1").WithLocation(15, 20), - // (15,23): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope - // M2(ref s1, $"{s2}"); - Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(15, 23) + Diagnostic(ErrorCode.ERR_EscapeCall, @"$""{s2}""").WithArguments("CustomHandler.CustomHandler(int, int, ref S1)", "s1").WithLocation(15, 20) ); } @@ -15228,6 +15231,9 @@ static void M(ref S s, [InterpolatedStringHandlerArgument(""s"")] CustomHandler // (5,97): error CS8352: Cannot use variable 'out CustomHandler this' in this context because it may expose referenced variables outside of their declaration scope // public CustomHandler(int literalLength, int formattedCount, ref S s) : this() { s.Handler = this; } Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler this").WithLocation(5, 97), + // (17,9): error CS8350: This combination of arguments to 'Program.M(ref S, CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope + // M(ref s, $"{1}"); + Diagnostic(ErrorCode.ERR_CallArgMixing, @"M(ref s, $""{1}"")").WithArguments("Program.M(ref S, CustomHandler)", "handler").WithLocation(17, 9), // (17,15): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference // M(ref s, $"{1}"); Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "s").WithLocation(17, 15), @@ -15404,10 +15410,8 @@ static CustomHandler F3() } [WorkItem(63306, "https://github.com/dotnet/roslyn/issues/63306")] - [Theory] - [InlineData(LanguageVersion.CSharp10)] - [InlineData(LanguageVersion.CSharp11)] - public void RefEscape_13A(LanguageVersion languageVersion) + [Fact] + public void RefEscape_13A() { var code = @"using System.Runtime.CompilerServices; @@ -15431,18 +15435,39 @@ static CustomHandler F2() static CustomHandler F3() { int i = 3; - CustomHandler h3 = $""{i}""; // 3 - return h3; + CustomHandler h3 = $""{i}""; + return h3; // 3 } static CustomHandler F4() { - CustomHandler h4 = $""{4}""; // 4 - return h4; + CustomHandler h4 = $""{4}""; + return h4; // 4 } } "; - // https://github.com/dotnet/roslyn/issues/63306: Should report an error in each case. - var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute }, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), targetFramework: TargetFramework.Net50); + var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute }, parseOptions: TestOptions.Regular10, targetFramework: TargetFramework.Net50); + comp.VerifyDiagnostics( + // (13,18): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return $"{i}"; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "{i}").WithArguments("CustomHandler.AppendFormatted(in int)", "i").WithLocation(13, 18), + // (13,19): error CS8168: Cannot return local 'i' by reference because it is not a ref local + // return $"{i}"; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(13, 19), + // (17,18): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return $"{2}"; // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "{2}").WithArguments("CustomHandler.AppendFormatted(in int)", "i").WithLocation(17, 18), + // (17,19): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return $"{2}"; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "2").WithLocation(17, 19), + // (23,16): error CS8352: Cannot use variable 'h3' in this context because it may expose referenced variables outside of their declaration scope + // return h3; // 3 + Diagnostic(ErrorCode.ERR_EscapeVariable, "h3").WithArguments("h3").WithLocation(23, 16), + // (28,16): error CS8352: Cannot use variable 'h4' in this context because it may expose referenced variables outside of their declaration scope + // return h4; // 4 + Diagnostic(ErrorCode.ERR_EscapeVariable, "h4").WithArguments("h4").WithLocation(28, 16)); + + // The `in int` parameter cannot be captured in C# 11. + comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute }, parseOptions: TestOptions.Regular11, targetFramework: TargetFramework.Net50); comp.VerifyDiagnostics(); } @@ -15587,17 +15612,14 @@ static R F() // (11,24): error CS0631: ref and out are not valid in this context // public object this[ref R r, [InterpolatedStringHandlerArgument("r")] CustomHandler handler] => null; Diagnostic(ErrorCode.ERR_IllegalRefParam, "ref").WithLocation(11, 24), - // (18,19): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // (18,13): error CS8350: This combination of arguments to 'R.this[ref R, CustomHandler]' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope // _ = r[ref r, $"{1}"]; - Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "r").WithLocation(18, 19), + Diagnostic(ErrorCode.ERR_CallArgMixing, @"r[ref r, $""{1}""]").WithArguments("R.this[ref R, CustomHandler]", "handler").WithLocation(18, 13), // (18,19): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference // _ = r[ref r, $"{1}"]; Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "r").WithLocation(18, 19), // (18,22): error CS8347: Cannot use a result of 'CustomHandler.CustomHandler(int, int, ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope // _ = r[ref r, $"{1}"]; - Diagnostic(ErrorCode.ERR_EscapeCall, @"$""{1}""").WithArguments("CustomHandler.CustomHandler(int, int, ref R)", "r").WithLocation(18, 22), - // (18,22): error CS8347: Cannot use a result of 'CustomHandler.CustomHandler(int, int, ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope - // _ = r[ref r, $"{1}"]; Diagnostic(ErrorCode.ERR_EscapeCall, @"$""{1}""").WithArguments("CustomHandler.CustomHandler(int, int, ref R)", "r").WithLocation(18, 22)); } @@ -15632,6 +15654,9 @@ static R F() // (5,97): error CS8352: Cannot use variable 'out CustomHandler this' in this context because it may expose referenced variables outside of their declaration scope // public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler this").WithLocation(5, 97), + // (18,15): error CS8350: This combination of arguments to 'R.R(ref R, CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope + // R y = new R(ref x, $"{1}"); + Diagnostic(ErrorCode.ERR_CallArgMixing, @"new R(ref x, $""{1}"")").WithArguments("R.R(ref R, CustomHandler)", "handler").WithLocation(18, 15), // (18,25): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference // R y = new R(ref x, $"{1}"); Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(18, 25), @@ -15705,9 +15730,17 @@ static CustomHandler F2() } } """; - // https://github.com/dotnet/roslyn/issues/63306: Should report an error that a reference to y will escape F1() and F2(). var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (14,18): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return $"{1}"; + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "{1}").WithLocation(14, 18), + // (14,18): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(int, in int)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // return $"{1}"; + Diagnostic(ErrorCode.ERR_EscapeCall, "{1}").WithArguments("CustomHandler.AppendFormatted(int, in int)", "y").WithLocation(14, 18), + // (19,16): error CS8352: Cannot use variable 'h2' in this context because it may expose referenced variables outside of their declaration scope + // return h2; + Diagnostic(ErrorCode.ERR_EscapeVariable, "h2").WithArguments("h2").WithLocation(19, 16)); } [WorkItem(67070, "https://github.com/dotnet/roslyn/issues/67070")] @@ -15951,11 +15984,67 @@ public void AppendFormatted(Span span) } """; var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics(); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public void RefEscape_NestedCalls_06b() + { + string source = """ + using System; + using System.Runtime.CompilerServices; + static class Program + { + static R M() + { + R r = default; + Span span = stackalloc byte[42]; + return r + .F($"{span}") + .F($"{span}"); + } + } + ref struct R + { + public R F([InterpolatedStringHandlerArgument("")] CustomHandler handler) + => this; + } + [InterpolatedStringHandler] + ref struct CustomHandler + { + public CustomHandler(int literalLength, int formattedCount, R r) + { + } + public void AppendLiteral(string value) + { + } + public void AppendFormatted(T value) + { + } + public void AppendFormatted(Span span) + { + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); comp.VerifyDiagnostics( + // (9,16): error CS8350: This combination of arguments to 'R.F(CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope + // return r + Diagnostic(ErrorCode.ERR_CallArgMixing, @"r + .F($""{span}"")").WithArguments("R.F(CustomHandler)", "handler").WithLocation(9, 16), // (9,16): error CS8347: Cannot use a result of 'R.F(CustomHandler)' in this context because it may expose variables referenced by parameter 'handler' outside of their declaration scope // return r Diagnostic(ErrorCode.ERR_EscapeCall, @"r .F($""{span}"")").WithArguments("R.F(CustomHandler)", "handler").WithLocation(9, 16), + // (10,18): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // .F($"{span}") + Diagnostic(ErrorCode.ERR_EscapeCall, "{span}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "span").WithLocation(10, 18), + // (10,18): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // .F($"{span}") + Diagnostic(ErrorCode.ERR_EscapeCall, "{span}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "span").WithLocation(10, 18), + // (10,19): error CS8352: Cannot use variable 'span' in this context because it may expose referenced variables outside of their declaration scope + // .F($"{span}") + Diagnostic(ErrorCode.ERR_EscapeVariable, "span").WithArguments("span").WithLocation(10, 19), // (10,19): error CS8352: Cannot use variable 'span' in this context because it may expose referenced variables outside of their declaration scope // .F($"{span}") Diagnostic(ErrorCode.ERR_EscapeVariable, "span").WithArguments("span").WithLocation(10, 19)); @@ -16084,6 +16173,9 @@ static void F(ref R r) // (5,97): error CS8352: Cannot use variable 'out CustomHandler this' in this context because it may expose referenced variables outside of their declaration scope // public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler this").WithLocation(5, 97), + // (26,27): error CS8350: This combination of arguments to 'Enumerable.Create(ref R, CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope + // foreach (var i in Enumerable.Create(ref r, $"{1}")) + Diagnostic(ErrorCode.ERR_CallArgMixing, @"Enumerable.Create(ref r, $""{1}"")").WithArguments("Enumerable.Create(ref R, CustomHandler)", "handler").WithLocation(26, 27), // (26,49): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference // foreach (var i in Enumerable.Create(ref r, $"{1}")) Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "r").WithLocation(26, 49), diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs index 81c060e72a02b..1e01cc6afd4e9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs @@ -11015,6 +11015,9 @@ public static CustomHandler M() var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute }, targetFramework: TargetFramework.Net50); comp.VerifyDiagnostics( + // (17,20): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return $"""{s}"""; + Diagnostic(ErrorCode.ERR_EscapeCall, "{s}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "s").WithLocation(17, 20), // (17,19): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope // return $"{s}"; Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(17, 21)); @@ -11130,6 +11133,9 @@ public ref struct S1 // (17,9): error CS8350: This combination of arguments to 'CustomHandler.M2(ref S1, ref CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope // M2(ref s1, $"""{s}""" + $""" Diagnostic(ErrorCode.ERR_CallArgMixing, @"M2(ref s1, " + expression + @")").WithArguments("CustomHandler.M2(ref S1, ref CustomHandler)", "handler").WithLocation(17, 9), + // (17,24): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // M2(ref s1, $"""{s}"""); + Diagnostic(ErrorCode.ERR_EscapeCall, "{s}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "s").WithLocation(17, 24), // (17,25): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope // M2(ref s1, $"""{s}""" + $""" Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(17, 25)); @@ -11316,6 +11322,9 @@ public ref struct S1 // (15,9): error CS8350: This combination of arguments to 'CustomHandler.M2(ref S1, CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope // M2(ref s1, $"""{s2}"""); Diagnostic(ErrorCode.ERR_CallArgMixing, @"M2(ref s1, $""""""{s2}"""""")").WithArguments("CustomHandler.M2(ref S1, CustomHandler)", "handler").WithLocation(15, 9), + // (15,24): error CS8347: Cannot use a result of 'CustomHandler.AppendFormatted(Span)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // M2(ref s1, $"""{s2}"""); + Diagnostic(ErrorCode.ERR_EscapeCall, "{s2}").WithArguments("CustomHandler.AppendFormatted(System.Span)", "s").WithLocation(15, 24), // (15,25): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope // M2(ref s1, $"""{s2}"""); Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(15, 25)); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 4ab1f964b7031..40a4d96414bfd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -4377,6 +4377,587 @@ static void Main() CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(); } + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/75802")] + public void CollectionInitializer_UnscopedRef_Return(bool? extensionMember) + { + var addSource = extensionMember switch + { + true => """ + static class E + { + extension(ref R r) + { + public void Add([UnscopedRef] in int x) { } + } + } + """, + false => """ + static class E + { + public static void Add(this ref R r, [UnscopedRef] in int x) { } + } + """, + null => """ + ref partial struct R : IEnumerable + { + public void Add([UnscopedRef] in int x) { } + } + """, + }; + var source = $$""" + using System.Collections; + using System.Diagnostics.CodeAnalysis; + class C + { + R M1() + { + var local = 1; + return new R() { local }; // 1 + } + unsafe R M2() + { + var local = 1; + return new R() { local }; // 2 + } + R M3() + { + return new R() { 1 }; // 3 + } + unsafe R M4() + { + return new R() { 1 }; // 4 + } + } + ref partial struct R : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + {{addSource}} + """; + var method = extensionMember switch + { + true => "E.extension(ref R).Add(in int)", + false => "E.Add(ref R, in int)", + null => "R.Add(in int)", + }; + CreateCompilation([source, UnscopedRefAttributeDefinition], options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics( + // (8,26): error CS8168: Cannot return local 'local' by reference because it is not a ref local + // return new R() { local }; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(8, 26), + // (8,26): error CS8347: Cannot use a result of 'R.Add(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R() { local }; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "local").WithArguments(method, "x").WithLocation(8, 26), + // (13,26): warning CS9091: This returns local 'local' by reference but it is not a ref local + // return new R() { local }; // 2 + Diagnostic(ErrorCode.WRN_RefReturnLocal, "local").WithArguments("local").WithLocation(13, 26), + // (17,26): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R() { 1 }; // 3 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(17, 26), + // (17,26): error CS8347: Cannot use a result of 'R.Add(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R() { 1 }; // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "1").WithArguments(method, "x").WithLocation(17, 26), + // (21,26): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R() { 1 }; // 4 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(21, 26), + // (21,26): error CS8347: Cannot use a result of 'R.Add(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R() { 1 }; // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "1").WithArguments(method, "x").WithLocation(21, 26)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75802")] + public void CollectionInitializer_UnscopedRef_Assignment() + { + var source = """ + using System.Collections; + using System.Diagnostics.CodeAnalysis; + class C + { + R M1() + { + var local = 1; + var r = new R() { local }; + return r; // 1 + } + void M2() + { + var local = 1; + scoped R r; + r = new R() { local }; + } + void M3() + { + var local = 1; + R r; + r = new R() { local }; // 2 + } + unsafe R M4() + { + var local = 1; + var r = new R() { local }; + return r; // 3 + } + unsafe void M5() + { + var local = 1; + scoped R r; + r = new R() { local }; + } + unsafe void M6() + { + var local = 1; + R r; + r = new R() { local }; // 4 + } + } + ref struct R : IEnumerable + { + public void Add([UnscopedRef] in int x) { } + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + """; + CreateCompilation([source, UnscopedRefAttributeDefinition], options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics( + // (9,16): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope + // return r; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(9, 16), + // (21,23): error CS8168: Cannot return local 'local' by reference because it is not a ref local + // r = new R() { local }; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(21, 23), + // (21,23): error CS8347: Cannot use a result of 'R.Add(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // r = new R() { local }; // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "local").WithArguments("R.Add(in int)", "x").WithLocation(21, 23), + // (27,16): warning CS9080: Use of variable 'r' in this context may expose referenced variables outside of their declaration scope + // return r; // 3 + Diagnostic(ErrorCode.WRN_EscapeVariable, "r").WithArguments("r").WithLocation(27, 16), + // (39,23): warning CS9091: This returns local 'local' by reference but it is not a ref local + // r = new R() { local }; // 4 + Diagnostic(ErrorCode.WRN_RefReturnLocal, "local").WithArguments("local").WithLocation(39, 23)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75802")] + public void CollectionInitializer_UnscopedRef_OtherScopes() + { + var source = """ + using System.Collections; + using System.Diagnostics.CodeAnalysis; + class C + { + R M1(ref int param) + { + var r = new R() { param }; + return r; + } + void M2(ref int param) + { + var r = new R() { param }; + var local = 1; + r.Add(local); // 1 + } + R M3(scoped ref int param) + { + var r = new R() { param }; + return r; // 2 + } + R M4(ref int param) + { + var r = new R(param) { param }; + return r; + } + R M5(ref int x, scoped ref int y) + { + var r = new R(x) { y }; + return r; // 3 + } + R M6(ref int x, scoped ref int y) + { + return new R() { { x, y } }; // 4 + } + } + ref struct R : IEnumerable + { + public R() { } + public R(in int p) : this() { } + public void Add([UnscopedRef] in int x) { } + public void Add(in int x, [UnscopedRef] in int y) { } + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + """; + CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics( + // (14,9): error CS8350: This combination of arguments to 'R.Add(in int)' is disallowed because it may expose variables referenced by parameter 'x' outside of their declaration scope + // r.Add(local); // 1 + Diagnostic(ErrorCode.ERR_CallArgMixing, "r.Add(local)").WithArguments("R.Add(in int)", "x").WithLocation(14, 9), + // (14,15): error CS8168: Cannot return local 'local' by reference because it is not a ref local + // r.Add(local); // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(14, 15), + // (19,16): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope + // return r; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(19, 16), + // (29,16): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope + // return r; // 3 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(29, 16), + // (33,26): error CS8347: Cannot use a result of 'R.Add(in int, in int)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // return new R() { { x, y } }; // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "{ x, y }").WithArguments("R.Add(in int, in int)", "y").WithLocation(33, 26), + // (33,31): error CS9075: Cannot return a parameter by reference 'y' because it is scoped to the current method + // return new R() { { x, y } }; // 4 + Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "y").WithArguments("y").WithLocation(33, 31)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75802")] + public void CollectionInitializer_ScopedRef() + { + var source = """ + using System.Collections; + class C + { + R M1() + { + var local = 1; + return new R() { local }; + } + R M2() + { + var local = 1; + var r = new R() { local }; + return r; + } + void M3() + { + var local = 1; + scoped R r; + r = new R() { local }; + } + void M4() + { + var local = 1; + R r; + r = new R() { local }; + } + } + ref struct R : IEnumerable + { + public void Add(in int x) { } + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75802")] + public void CollectionInitializer_NonRefStruct() + { + var source = """ + using System.Collections; + using System.Diagnostics.CodeAnalysis; + class C + { + R M1() + { + var local = 1; + return new R() { local }; + } + R M2() + { + var local = 1; + var r = new R() { local }; + return r; + } + void M3() + { + var local = 1; + R r; + r = new R() { local }; + } + } + struct R : IEnumerable + { + public void Add([UnscopedRef] in int x) { } + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + """; + CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/75802")] + [WorkItem("https://github.com/dotnet/roslyn/issues/76374")] + [InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]")] + [InlineData("")] + public void CollectionExpression_AddMethod(string attr) + { + var source = $$""" + using System.Collections; + class C + { + R M() + { + var local = 1; + return [local]; + } + } + ref struct R : IEnumerable + { + public void Add({{attr}} in int x) { } + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + """; + CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics( + // (7,16): error CS9203: A collection expression of type 'R' cannot be used in this context because it may be exposed outside of the current scope. + // return [local]; + Diagnostic(ErrorCode.ERR_CollectionExpressionEscape, "[local]").WithArguments("R").WithLocation(7, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75802")] + public void CollectionExpression_Builder() + { + var source = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + class C + { + R M() + { + var local = 1; + return [local]; + } + } + [CollectionBuilder(typeof(Builder), nameof(Builder.Create))] + ref struct R : IEnumerable + { + public IEnumerator GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + static class Builder + { + public static R Create(ReadOnlySpan x) => throw null; + } + """; + CreateCompilationWithSpan([source, CollectionBuilderAttributeDefinition]).VerifyDiagnostics( + // (10,16): error CS9203: A collection expression of type 'R' cannot be used in this context because it may be exposed outside of the current scope. + // return [local]; + Diagnostic(ErrorCode.ERR_CollectionExpressionEscape, "[local]").WithArguments("R").WithLocation(10, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")] + public void InterpolatedString_UnscopedRef_Return() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + using System.Runtime.CompilerServices; + class C + { + R M1() + { + var local = 1; + return $"{local}"; // 1 + } + unsafe R M2() + { + var local = 1; + return $"{local}"; // 2 + } + R M3() + { + return $"{1}"; // 3 + } + R M4() + { + return $"{1}" + $"{2}"; // 4 + } + R M5(ref int param) + { + int local = 1; + return $"{param}{local}"; // 5 + } + R M6() + { + return $"abc"; // 6 + } + } + [InterpolatedStringHandlerAttribute] + ref struct R + { + public R(int literalLength, int formattedCount) { } + public void AppendFormatted([UnscopedRef] in int x) { } + public void AppendLiteral([UnscopedRef] in string x) { } + } + """; + CreateCompilation([source, UnscopedRefAttributeDefinition, InterpolatedStringHandlerAttribute], options: TestOptions.UnsafeDebugDll).VerifyDiagnostics( + // (8,18): error CS8347: Cannot use a result of 'R.AppendFormatted(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return $"{local}"; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "{local}").WithArguments("R.AppendFormatted(in int)", "x").WithLocation(8, 18), + // (8,19): error CS8168: Cannot return local 'local' by reference because it is not a ref local + // return $"{local}"; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(8, 19), + // (13,19): warning CS9091: This returns local 'local' by reference but it is not a ref local + // return $"{local}"; // 2 + Diagnostic(ErrorCode.WRN_RefReturnLocal, "local").WithArguments("local").WithLocation(13, 19), + // (17,18): error CS8347: Cannot use a result of 'R.AppendFormatted(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return $"{1}"; // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "{1}").WithArguments("R.AppendFormatted(in int)", "x").WithLocation(17, 18), + // (17,19): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return $"{1}"; // 3 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(17, 19), + // (21,27): error CS8347: Cannot use a result of 'R.AppendFormatted(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return $"{1}" + $"{2}"; // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "{2}").WithArguments("R.AppendFormatted(in int)", "x").WithLocation(21, 27), + // (21,28): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return $"{1}" + $"{2}"; // 4 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "2").WithLocation(21, 28), + // (26,25): error CS8347: Cannot use a result of 'R.AppendFormatted(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return $"{param}{local}"; // 5 + Diagnostic(ErrorCode.ERR_EscapeCall, "{local}").WithArguments("R.AppendFormatted(in int)", "x").WithLocation(26, 25), + // (26,26): error CS8168: Cannot return local 'local' by reference because it is not a ref local + // return $"{param}{local}"; // 5 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(26, 26), + // (30,18): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return $"abc"; // 6 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "abc").WithLocation(30, 18), + // (30,18): error CS8347: Cannot use a result of 'R.AppendLiteral(in string)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return $"abc"; // 6 + Diagnostic(ErrorCode.ERR_EscapeCall, "abc").WithArguments("R.AppendLiteral(in string)", "x").WithLocation(30, 18)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")] + public void InterpolatedString_UnscopedRef_Assignment() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + using System.Runtime.CompilerServices; + class C + { + R M1() + { + var local = 1; + R r = $"{local}"; + return r; // 1 + } + void M2() + { + var local = 1; + scoped R r; + r = $"{local}"; + } + void M3() + { + var local = 1; + R r; + r = $"{local}"; // 2 + } + R M4() + { + return $"abc"; // 3 + } + } + [InterpolatedStringHandlerAttribute] + ref struct R + { + public R(int literalLength, int formattedCount) { } + public void AppendFormatted([UnscopedRef] in int x) { } + public void AppendLiteral([UnscopedRef] in string x) { } + } + """; + CreateCompilation([source, UnscopedRefAttributeDefinition, InterpolatedStringHandlerAttribute]).VerifyDiagnostics( + // (9,16): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope + // return r; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(9, 16), + // (21,15): error CS8347: Cannot use a result of 'R.AppendFormatted(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // r = $"{local}"; // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "{local}").WithArguments("R.AppendFormatted(in int)", "x").WithLocation(21, 15), + // (21,16): error CS8168: Cannot return local 'local' by reference because it is not a ref local + // r = $"{local}"; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(21, 16), + // (25,18): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return $"abc"; // 3 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "abc").WithLocation(25, 18), + // (25,18): error CS8347: Cannot use a result of 'R.AppendLiteral(in string)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return $"abc"; // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "abc").WithArguments("R.AppendLiteral(in string)", "x").WithLocation(25, 18)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")] + public void InterpolatedString_ScopedRef_Return() + { + var source = """ + using System.Runtime.CompilerServices; + class C + { + R M1() + { + var local = 1; + return $"{local}"; + } + R M2() + { + return $"{1}"; + } + R M3() + { + return $"{1}" + $"{2}"; + } + R M4(ref int param) + { + int local = 1; + return $"{param}{local}"; + } + R M5() + { + return $"abc"; + } + } + [InterpolatedStringHandlerAttribute] + ref struct R + { + public R(int literalLength, int formattedCount) { } + public void AppendFormatted(in int x) { } + public void AppendLiteral(in string x) { } + } + """; + CreateCompilation([source, InterpolatedStringHandlerAttribute]).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63306")] + public void InterpolatedString_ScopedRef_Assignment() + { + var source = """ + using System.Runtime.CompilerServices; + class C + { + R M1() + { + var local = 1; + R r = $"{local}"; + return r; + } + void M2() + { + var local = 1; + scoped R r; + r = $"{local}"; + } + void M3() + { + var local = 1; + R r; + r = $"{local}"; + } + R M4() + { + return $"abc"; + } + } + [InterpolatedStringHandlerAttribute] + ref struct R + { + public R(int literalLength, int formattedCount) { } + public void AppendFormatted(in int x) { } + public void AppendLiteral(in string x) { } + } + """; + CreateCompilation([source, InterpolatedStringHandlerAttribute]).VerifyDiagnostics(); + } + [Fact] public void RefLikeEscapeMixingDelegate() {