diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index aea6580634b3a..5472b1d5161df 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -16,10 +16,10 @@ namespace Microsoft.CodeAnalysis.CSharp { internal partial class RefSafetyAnalysis { - private enum EscapeLevel : uint + private enum EscapeLevel { - CallingMethod = CallingMethodScope, - ReturnOnly = ReturnOnlyScope, + CallingMethod, + ReturnOnly } /// @@ -216,29 +216,6 @@ public void Deconstruct(out ParameterSymbol? parameter, out BoundExpression argu ? p.ToString() : Argument.ToString(); } - - /// - /// For the purpose of escape verification we operate with the depth of local scopes. - /// The depth is a uint, with smaller number representing shallower/wider scopes. - /// 0, 1 and 2 are special scopes - - /// 0 is the "calling method" scope that is outside of the containing method/lambda. - /// If something can escape to scope 0, it can escape to any scope in a given method through a ref parameter or return. - /// 1 is the "return-only" scope that is outside of the containing method/lambda. - /// If something can escape to scope 1, it can escape to any scope in a given method or can be returned, but it can't escape through a ref parameter. - /// 2 is the "current method" scope that is just inside the containing method/lambda. - /// If something can escape to scope 1, it can escape to any scope in a given method, but cannot be returned. - /// n + 1 corresponds to scopes immediately inside a scope of depth n. - /// Since sibling scopes do not intersect and a value cannot escape from one to another without - /// escaping to a wider scope, we can use simple depth numbering without ambiguity. - /// - /// Generally these values are expressed via the following parameters: - /// - escapeFrom: the scope in which an expression is being evaluated. Usually the current local - /// scope - /// - escapeTo: the scope to which the values are being escaped to. - /// - private const uint CallingMethodScope = 0; - private const uint ReturnOnlyScope = 1; - private const uint CurrentMethodScope = 2; } #nullable disable @@ -1084,19 +1061,19 @@ private bool CheckLocalValueKind(SyntaxNode node, BoundLocal local, BindValueKin internal partial class RefSafetyAnalysis { - private bool CheckLocalRefEscape(SyntaxNode node, BoundLocal local, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) + private bool CheckLocalRefEscape(SyntaxNode node, BoundLocal local, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) { LocalSymbol localSymbol = local.LocalSymbol; // if local symbol can escape to the same or wider/shallower scope then escapeTo // then it is all ok, otherwise it is an error. - if (GetLocalScopes(localSymbol).RefEscapeScope <= escapeTo) + if (GetLocalScopes(localSymbol).RefEscapeScope.IsConvertibleTo(escapeTo)) { return true; } var inUnsafeRegion = _inUnsafeRegion; - if (escapeTo is CallingMethodScope or ReturnOnlyScope) + if (escapeTo.IsReturnable) { if (localSymbol.RefKind == RefKind.None) { @@ -1241,46 +1218,46 @@ static void reportReadOnlyParameterError(ParameterSymbol parameterSymbol, Syntax internal partial class RefSafetyAnalysis { - private static EscapeLevel? EscapeLevelFromScope(uint scope) => scope switch + private static EscapeLevel? EscapeLevelFromScope(SafeContext lifetime) => lifetime switch { - ReturnOnlyScope => EscapeLevel.ReturnOnly, - CallingMethodScope => EscapeLevel.CallingMethod, + { IsReturnOnly: true } => EscapeLevel.ReturnOnly, + { IsCallingMethod: true } => EscapeLevel.CallingMethod, _ => null, }; - private static uint GetParameterValEscape(ParameterSymbol parameter) + private static SafeContext GetParameterValEscape(ParameterSymbol parameter) { return parameter switch { - { EffectiveScope: ScopedKind.ScopedValue } => CurrentMethodScope, - { RefKind: RefKind.Out, UseUpdatedEscapeRules: true } => ReturnOnlyScope, - _ => CallingMethodScope + { EffectiveScope: ScopedKind.ScopedValue } => SafeContext.CurrentMethod, + { RefKind: RefKind.Out, UseUpdatedEscapeRules: true } => SafeContext.ReturnOnly, + _ => SafeContext.CallingMethod }; } private static EscapeLevel? GetParameterValEscapeLevel(ParameterSymbol parameter) => EscapeLevelFromScope(GetParameterValEscape(parameter)); - private static uint GetParameterRefEscape(ParameterSymbol parameter) + private static SafeContext GetParameterRefEscape(ParameterSymbol parameter) { return parameter switch { - { RefKind: RefKind.None } => CurrentMethodScope, - { EffectiveScope: ScopedKind.ScopedRef } => CurrentMethodScope, - { HasUnscopedRefAttribute: true, RefKind: RefKind.Out } => ReturnOnlyScope, - { HasUnscopedRefAttribute: true, IsThis: false } => CallingMethodScope, - _ => ReturnOnlyScope + { RefKind: RefKind.None } => SafeContext.CurrentMethod, + { EffectiveScope: ScopedKind.ScopedRef } => SafeContext.CurrentMethod, + { HasUnscopedRefAttribute: true, RefKind: RefKind.Out } => SafeContext.ReturnOnly, + { HasUnscopedRefAttribute: true, IsThis: false } => SafeContext.CallingMethod, + _ => SafeContext.ReturnOnly }; } private static EscapeLevel? GetParameterRefEscapeLevel(ParameterSymbol parameter) => EscapeLevelFromScope(GetParameterRefEscape(parameter)); - private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter, uint escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { if (_useUpdatedEscapeRules) { - if (GetParameterValEscape(parameter) > escapeTo) + if (!GetParameterValEscape(parameter).IsConvertibleTo(escapeTo)) { Error(diagnostics, _inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, parameter); return _inUnsafeRegion; @@ -1294,13 +1271,13 @@ private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter, } } - private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter, ParameterSymbol parameterSymbol, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) + private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter, ParameterSymbol parameterSymbol, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) { var refSafeToEscape = GetParameterRefEscape(parameterSymbol); - if (refSafeToEscape > escapeTo) + if (!refSafeToEscape.IsConvertibleTo(escapeTo)) { var isRefScoped = parameterSymbol.EffectiveScope == ScopedKind.ScopedRef; - Debug.Assert(parameterSymbol.RefKind == RefKind.None || isRefScoped || refSafeToEscape == ReturnOnlyScope); + Debug.Assert(parameterSymbol.RefKind == RefKind.None || isRefScoped || refSafeToEscape.IsReturnOnly); var inUnsafeRegion = _inUnsafeRegion; if (parameter is BoundThisReference) @@ -1314,14 +1291,14 @@ private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter, { (checkingReceiver: true, isRefScoped: true, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnScopedParameter2, parameter.Syntax), (checkingReceiver: true, isRefScoped: true, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnScopedParameter2, parameter.Syntax), - (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: false, ReturnOnlyScope) => (ErrorCode.ERR_RefReturnOnlyParameter2, parameter.Syntax), - (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: true, ReturnOnlyScope) => (ErrorCode.WRN_RefReturnOnlyParameter2, parameter.Syntax), + (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: false, { IsReturnOnly: true }) => (ErrorCode.ERR_RefReturnOnlyParameter2, parameter.Syntax), + (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: true, { IsReturnOnly: true }) => (ErrorCode.WRN_RefReturnOnlyParameter2, parameter.Syntax), (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnParameter2, parameter.Syntax), (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnParameter2, parameter.Syntax), (checkingReceiver: false, isRefScoped: true, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnScopedParameter, node), (checkingReceiver: false, isRefScoped: true, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnScopedParameter, node), - (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: false, ReturnOnlyScope) => (ErrorCode.ERR_RefReturnOnlyParameter, node), - (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: true, ReturnOnlyScope) => (ErrorCode.WRN_RefReturnOnlyParameter, node), + (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: false, { IsReturnOnly: true }) => (ErrorCode.ERR_RefReturnOnlyParameter, node), + (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: true, { IsReturnOnly: true }) => (ErrorCode.WRN_RefReturnOnlyParameter, node), (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnParameter, node), (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnParameter, node) }; @@ -1492,14 +1469,14 @@ private bool CheckSimpleAssignmentValueKind(SyntaxNode node, BoundAssignmentOper internal partial class RefSafetyAnalysis { - private uint GetFieldRefEscape(BoundFieldAccess fieldAccess, uint scopeOfTheContainingExpression) + private SafeContext GetFieldRefEscape(BoundFieldAccess fieldAccess, SafeContext scopeOfTheContainingExpression) { var fieldSymbol = fieldAccess.FieldSymbol; // fields that are static or belong to reference types can ref escape anywhere if (fieldSymbol.IsStatic || fieldSymbol.ContainingType.IsReferenceType) { - return CallingMethodScope; + return SafeContext.CallingMethod; } if (_useUpdatedEscapeRules) @@ -1515,7 +1492,7 @@ private uint GetFieldRefEscape(BoundFieldAccess fieldAccess, uint scopeOfTheCont return GetRefEscape(fieldAccess.ReceiverOpt, scopeOfTheContainingExpression); } - private bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { var fieldSymbol = fieldAccess.FieldSymbol; // fields that are static or belong to reference types can ref escape anywhere @@ -1539,7 +1516,7 @@ private bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess, return CheckRefEscape(node, fieldAccess.ReceiverOpt, escapeFrom, escapeTo, checkingReceiver: true, diagnostics: diagnostics); } - private bool CheckFieldLikeEventRefEscape(SyntaxNode node, BoundEventAccess eventAccess, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckFieldLikeEventRefEscape(SyntaxNode node, BoundEventAccess eventAccess, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { var eventSymbol = eventAccess.EventSymbol; @@ -1937,9 +1914,9 @@ private bool IsBadBaseAccess(SyntaxNode node, BoundExpression receiverOpt, Symbo internal partial class RefSafetyAnalysis { - internal uint GetInterpolatedStringHandlerConversionEscapeScope( + internal SafeContext GetInterpolatedStringHandlerConversionEscapeScope( BoundExpression expression, - uint scopeOfTheContainingExpression) + SafeContext scopeOfTheContainingExpression) { var data = expression.GetInterpolatedStringHandlerData(); #if DEBUG @@ -1949,7 +1926,7 @@ internal uint GetInterpolatedStringHandlerConversionEscapeScope( var previousVisited = _visited; _visited = null; #endif - uint escapeScope = GetValEscape(data.Construction, scopeOfTheContainingExpression); + SafeContext escapeScope = GetValEscape(data.Construction, scopeOfTheContainingExpression); #if DEBUG _visited = previousVisited; #endif @@ -1959,8 +1936,8 @@ internal uint GetInterpolatedStringHandlerConversionEscapeScope( foreach (var argument in arguments) { - uint argEscape = GetValEscape(argument, scopeOfTheContainingExpression); - escapeScope = Math.Max(escapeScope, argEscape); + SafeContext argEscape = GetValEscape(argument, scopeOfTheContainingExpression); + escapeScope = escapeScope.Intersect(argEscape); } arguments.Free(); @@ -1977,7 +1954,7 @@ internal uint GetInterpolatedStringHandlerConversionEscapeScope( /// NOTE: we need scopeOfTheContainingExpression as some expressions such as optional in parameters or ref dynamic behave as /// local variables declared at the scope of the invocation. /// - private uint GetInvocationEscapeScope( + private SafeContext GetInvocationEscapeScope( in MethodInfo methodInfo, BoundExpression? receiver, ThreeState receiverIsSubjectToCloning, @@ -1985,7 +1962,7 @@ private uint GetInvocationEscapeScope( ImmutableArray argsOpt, ImmutableArray argRefKindsOpt, ImmutableArray argsToParamsOpt, - uint scopeOfTheContainingExpression, + SafeContext scopeOfTheContainingExpression, bool isRefEscape ) { @@ -2010,7 +1987,7 @@ bool isRefEscape //• the safe-to-escape of all argument expressions(including the receiver) // - uint escapeScope = CallingMethodScope; + SafeContext escapeScope = SafeContext.CallingMethod; var escapeValues = ArrayBuilder.GetInstance(); GetEscapeValuesForOldRules( in methodInfo, @@ -2037,15 +2014,15 @@ bool isRefEscape // // val escape scope is the narrowest of // - val escape of all byval arguments (refs cannot be wrapped into values, so their ref escape is irrelevant, only use val escapes) - uint argumentEscape = (isRefEscape, argumentIsRefEscape) switch + SafeContext argumentEscape = (isRefEscape, argumentIsRefEscape) switch { (true, true) => GetRefEscape(argument, scopeOfTheContainingExpression), (false, false) => GetValEscape(argument, scopeOfTheContainingExpression), _ => escapeScope }; - escapeScope = Math.Max(escapeScope, argumentEscape); - if (escapeScope >= scopeOfTheContainingExpression) + escapeScope = escapeScope.Intersect(argumentEscape); + if (scopeOfTheContainingExpression.IsConvertibleTo(escapeScope)) { // can't get any worse return escapeScope; @@ -2060,13 +2037,13 @@ bool isRefEscape // check receiver if ref-like if (methodInfo.Method?.RequiresInstanceReceiver == true && receiver?.Type?.IsRefLikeOrAllowsRefLikeType() == true) { - escapeScope = Math.Max(escapeScope, GetValEscape(receiver, scopeOfTheContainingExpression)); + escapeScope = escapeScope.Intersect(GetValEscape(receiver, scopeOfTheContainingExpression)); } return escapeScope; } - private uint GetInvocationEscapeWithUpdatedRules( + private SafeContext GetInvocationEscapeWithUpdatedRules( in MethodInfo methodInfo, BoundExpression? receiver, ThreeState receiverIsSubjectToCloning, @@ -2074,11 +2051,11 @@ private uint GetInvocationEscapeWithUpdatedRules( ImmutableArray argsOpt, ImmutableArray argRefKindsOpt, ImmutableArray argsToParamsOpt, - uint scopeOfTheContainingExpression, + SafeContext scopeOfTheContainingExpression, bool isRefEscape) { //by default it is safe to escape - uint escapeScope = CallingMethodScope; + SafeContext escapeScope = SafeContext.CallingMethod; var argsAndParamsAll = ArrayBuilder.GetInstance(); GetFilteredInvocationArgumentsForEscapeWithUpdatedRules( @@ -2105,12 +2082,12 @@ private uint GetInvocationEscapeWithUpdatedRules( (param is { RefKind: not RefKind.None, Type: { } type } && type.IsRefLikeOrAllowsRefLikeType())) && isArgumentRefEscape == isRefEscape)) { - uint argEscape = isArgumentRefEscape ? + SafeContext argEscape = isArgumentRefEscape ? GetRefEscape(argument, scopeOfTheContainingExpression) : GetValEscape(argument, scopeOfTheContainingExpression); - escapeScope = Math.Max(escapeScope, argEscape); - if (escapeScope >= scopeOfTheContainingExpression) + escapeScope = escapeScope.Intersect(argEscape); + if (scopeOfTheContainingExpression.IsConvertibleTo(escapeScope)) { // can't get any worse break; @@ -2140,8 +2117,8 @@ private bool CheckInvocationEscape( ImmutableArray argRefKindsOpt, ImmutableArray argsToParamsOpt, bool checkingReceiver, - uint escapeFrom, - uint escapeTo, + SafeContext escapeFrom, + SafeContext escapeTo, BindingDiagnosticBag diagnostics, bool isRefEscape ) @@ -2233,8 +2210,8 @@ private bool CheckInvocationEscapeWithUpdatedRules( ImmutableArray argRefKindsOpt, ImmutableArray argsToParamsOpt, bool checkingReceiver, - uint escapeFrom, - uint escapeTo, + SafeContext escapeFrom, + SafeContext escapeTo, BindingDiagnosticBag diagnostics, bool isRefEscape) { @@ -2768,7 +2745,7 @@ private bool ShouldInferDeclarationExpressionValEscape(BoundExpression argument, _ => null }; if (symbol is SourceLocalSymbol local && - GetLocalScopes(local).ValEscapeScope == CallingMethodScope) + GetLocalScopes(local).ValEscapeScope.IsCallingMethod) { localSymbol = local; return true; @@ -2797,7 +2774,7 @@ private bool CheckInvocationArgMixing( ImmutableArray argsOpt, ImmutableArray argRefKindsOpt, ImmutableArray argsToParamsOpt, - uint scopeOfTheContainingExpression, + SafeContext scopeOfTheContainingExpression, BindingDiagnosticBag diagnostics) { if (methodInfo.UseUpdatedEscapeRules) @@ -2818,7 +2795,7 @@ private bool CheckInvocationArgMixing( } // widest possible escape via writeable ref-like receiver or ref/out argument. - uint escapeTo = scopeOfTheContainingExpression; + SafeContext escapeTo = scopeOfTheContainingExpression; // collect all writeable ref-like arguments, including receiver var escapeArguments = ArrayBuilder.GetInstance(); @@ -2850,7 +2827,7 @@ private bool CheckInvocationArgMixing( && !argument.IsDiscardExpression() && argument.Type?.IsRefLikeOrAllowsRefLikeType() == true) { - escapeTo = Math.Min(escapeTo, GetValEscape(argument, scopeOfTheContainingExpression)); + escapeTo = escapeTo.Union(GetValEscape(argument, scopeOfTheContainingExpression)); } } @@ -2858,12 +2835,12 @@ private bool CheckInvocationArgMixing( // track the widest scope that arguments could safely escape to. // use this scope as the inferred STE of declaration expressions. - var inferredDestinationValEscape = CallingMethodScope; + var inferredDestinationValEscape = SafeContext.CallingMethod; foreach (var (parameter, argument, _) in escapeArguments) { // in the old rules, we assume that refs cannot escape into ref struct variables. // e.g. in `dest = M(ref arg)`, we assume `ref arg` will not escape into `dest`, but `arg` might. - inferredDestinationValEscape = Math.Max(inferredDestinationValEscape, GetValEscape(argument, scopeOfTheContainingExpression)); + inferredDestinationValEscape = inferredDestinationValEscape.Intersect(GetValEscape(argument, scopeOfTheContainingExpression)); if (!hasMixingError && !CheckValEscape(argument.Syntax, argument, scopeOfTheContainingExpression, escapeTo, false, diagnostics)) { string parameterName = GetInvocationParameterName(parameter); @@ -2897,7 +2874,7 @@ private bool CheckInvocationArgMixingWithUpdatedRules( ImmutableArray argsOpt, ImmutableArray argRefKindsOpt, ImmutableArray argsToParamsOpt, - uint scopeOfTheContainingExpression, + SafeContext scopeOfTheContainingExpression, BindingDiagnosticBag diagnostics) { var mixableArguments = ArrayBuilder.GetInstance(); @@ -2961,10 +2938,10 @@ void inferDeclarationExpressionValEscape() { // find the widest scope that arguments could safely escape to. // use this scope as the inferred STE of declaration expressions. - var inferredDestinationValEscape = CallingMethodScope; + var inferredDestinationValEscape = SafeContext.CallingMethod; foreach (var (_, fromArg, _, isRefEscape) in escapeValues) { - inferredDestinationValEscape = Math.Max(inferredDestinationValEscape, isRefEscape + inferredDestinationValEscape = inferredDestinationValEscape.Intersect(isRefEscape ? GetRefEscape(fromArg, scopeOfTheContainingExpression) : GetValEscape(fromArg, scopeOfTheContainingExpression)); } @@ -3235,9 +3212,9 @@ private static ErrorCode GetStandardLvalueError(BindValueKind kind) internal partial class RefSafetyAnalysis { - private static ErrorCode GetStandardRValueRefEscapeError(uint escapeTo) + private static ErrorCode GetStandardRValueRefEscapeError(SafeContext escapeTo) { - if (escapeTo is CallingMethodScope or ReturnOnlyScope) + if (escapeTo.IsReturnable) { return ErrorCode.ERR_RefReturnLvalueExpected; } @@ -3319,7 +3296,7 @@ internal partial class RefSafetyAnalysis /// /// Checks whether given expression can escape from the current scope to the . /// - internal void ValidateEscape(BoundExpression expr, uint escapeTo, bool isByRef, BindingDiagnosticBag diagnostics) + internal void ValidateEscape(BoundExpression expr, SafeContext escapeTo, bool isByRef, BindingDiagnosticBag diagnostics) { // The result of escape analysis is affected by the expression's type. // We can't do escape analysis on expressions which lack a type, such as 'target typed new()', until they are converted. @@ -3342,7 +3319,7 @@ internal void ValidateEscape(BoundExpression expr, uint escapeTo, bool isByRef, /// There are few cases where RValues are permitted to be passed by reference which implies that a temporary local proxy is passed instead. /// We reflect such behavior by constraining the escape value to the narrowest scope possible. /// - internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpression) + internal SafeContext GetRefEscape(BoundExpression expr, SafeContext scopeOfTheContainingExpression) { #if DEBUG AssertVisited(expr); @@ -3351,13 +3328,13 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres // cannot infer anything from errors if (expr.HasAnyErrors) { - return CallingMethodScope; + return SafeContext.CallingMethod; } // cannot infer anything from Void (broken code) if (expr.Type?.GetSpecialTypeSafe() == SpecialType.System_Void) { - return CallingMethodScope; + return SafeContext.CallingMethod; } // constants/literals cannot ref-escape current scope @@ -3374,13 +3351,13 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.PointerIndirectionOperator: case BoundKind.PointerElementAccess: // array elements and pointer dereferencing are readwrite variables - return CallingMethodScope; + return SafeContext.CallingMethod; case BoundKind.RefValueOperator: // The undocumented __refvalue(tr, T) expression results in an lvalue of type T. // for compat reasons it is not ref-returnable (since TypedReference is not val-returnable) // it can, however, ref-escape to any other level (since TypedReference can val-escape to any other level) - return CurrentMethodScope; + return SafeContext.CurrentMethod; case BoundKind.DiscardExpression: // same as write-only byval local @@ -3414,8 +3391,8 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres if (conditional.IsRef) { // ref conditional defers to its operands - return Math.Max(GetRefEscape(conditional.Consequence, scopeOfTheContainingExpression), - GetRefEscape(conditional.Alternative, scopeOfTheContainingExpression)); + return GetRefEscape(conditional.Consequence, scopeOfTheContainingExpression) + .Intersect(GetRefEscape(conditional.Alternative, scopeOfTheContainingExpression)); } // otherwise it is an RValue @@ -3437,7 +3414,7 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres // field-like events that are static or belong to reference types can ref escape anywhere if (eventSymbol.IsStatic || eventSymbol.ContainingType.IsReferenceType) { - return CallingMethodScope; + return SafeContext.CallingMethod; } // for other events defer to the receiver. @@ -3527,7 +3504,7 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundArrayAccess: // array elements are readwrite variables - return CallingMethodScope; + return SafeContext.CallingMethod; case BoundCall call: var methodSymbol = call.Method; @@ -3650,7 +3627,7 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres /// The result indicates whether the escape is possible. /// Additionally, the method emits diagnostics (possibly more than one, recursively) that would help identify the cause for the failure. /// - internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeFrom, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) + internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, SafeContext escapeFrom, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) { #if DEBUG AssertVisited(expr); @@ -3658,7 +3635,7 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter()); - if (escapeTo >= escapeFrom) + if (escapeFrom.IsConvertibleTo(escapeTo)) { // escaping to same or narrower scope is ok. return true; @@ -3694,7 +3671,7 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.RefValueOperator: // The undocumented __refvalue(tr, T) expression results in an lvalue of type T. // for compat reasons it is not ref-returnable (since TypedReference is not val-returnable) - if (escapeTo is CallingMethodScope or ReturnOnlyScope) + if (escapeTo.IsReturnable) { break; } @@ -3723,7 +3700,7 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.CapturedReceiverPlaceholder: // Equivalent to a non-ref local with the underlying receiver as an initializer provided at declaration - if (((BoundCapturedReceiverPlaceholder)expr).LocalScopeDepth <= escapeTo) + if (((BoundCapturedReceiverPlaceholder)expr).LocalScopeDepth.IsConvertibleTo(escapeTo)) { return true; } @@ -4019,12 +3996,12 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF return false; } - internal uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheContainingExpression) + internal SafeContext GetBroadestValEscape(BoundTupleExpression expr, SafeContext scopeOfTheContainingExpression) { - uint broadest = scopeOfTheContainingExpression; + SafeContext broadest = scopeOfTheContainingExpression; foreach (var element in expr.Arguments) { - uint valEscape; + SafeContext valEscape; if (element is BoundTupleExpression te) { valEscape = GetBroadestValEscape(te, scopeOfTheContainingExpression); @@ -4034,7 +4011,7 @@ internal uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheCon valEscape = GetValEscape(element, scopeOfTheContainingExpression); } - broadest = Math.Min(broadest, valEscape); + broadest = broadest.Union(valEscape); } return broadest; @@ -4045,7 +4022,7 @@ internal uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheCon /// /// NOTE: unless the type of expression is ref-like, the result is Binder.ExternalScope since ordinary values can always be returned from methods. /// - internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpression) + internal SafeContext GetValEscape(BoundExpression expr, SafeContext scopeOfTheContainingExpression) { #if DEBUG AssertVisited(expr); @@ -4054,19 +4031,19 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres // cannot infer anything from errors if (expr.HasAnyErrors) { - return CallingMethodScope; + return SafeContext.CallingMethod; } // constants/literals cannot refer to local state if (expr.ConstantValueOpt != null) { - return CallingMethodScope; + return SafeContext.CallingMethod; } // to have local-referring values an expression must have a ref-like type if (expr.Type?.IsRefLikeOrAllowsRefLikeType() != true) { - return CallingMethodScope; + return SafeContext.CallingMethod; } // cover case that can refer to local state @@ -4081,7 +4058,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.DefaultExpression: case BoundKind.Utf8String: // always returnable - return CallingMethodScope; + return SafeContext.CallingMethod; case BoundKind.Parameter: return GetParameterValEscape(((BoundParameter)expr).ParameterSymbol); @@ -4089,7 +4066,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.FromEndIndexExpression: // We are going to call a constructor that takes an integer and a bool. Cannot leak any references through them. // always returnable - return CallingMethodScope; + return SafeContext.CallingMethod; case BoundKind.TupleLiteral: case BoundKind.ConvertedTupleLiteral: @@ -4101,10 +4078,10 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres // for compat reasons // NB: it also means can`t assign stackalloc spans to a __refvalue // we are ok with that. - return CallingMethodScope; + return SafeContext.CallingMethod; case BoundKind.DiscardExpression: - return CallingMethodScope; + return SafeContext.CallingMethod; case BoundKind.DeconstructValuePlaceholder: case BoundKind.InterpolatedStringArgumentPlaceholder: @@ -4121,7 +4098,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.StackAllocArrayCreation: case BoundKind.ConvertedStackAllocExpression: - return CurrentMethodScope; + return SafeContext.CurrentMethod; case BoundKind.ConditionalOperator: var conditional = (BoundConditionalOperator)expr; @@ -4136,14 +4113,13 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres } // val conditional gets narrowest of its operands - return Math.Max(consEscape, - GetValEscape(conditional.Alternative, scopeOfTheContainingExpression)); + return consEscape.Intersect(GetValEscape(conditional.Alternative, scopeOfTheContainingExpression)); case BoundKind.NullCoalescingOperator: var coalescingOp = (BoundNullCoalescingOperator)expr; - return Math.Max(GetValEscape(coalescingOp.LeftOperand, scopeOfTheContainingExpression), - GetValEscape(coalescingOp.RightOperand, scopeOfTheContainingExpression)); + return GetValEscape(coalescingOp.LeftOperand, scopeOfTheContainingExpression) + .Intersect(GetValEscape(coalescingOp.RightOperand, scopeOfTheContainingExpression)); case BoundKind.FieldAccess: var fieldAccess = (BoundFieldAccess)expr; @@ -4152,7 +4128,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres if (fieldSymbol.IsStatic || !fieldSymbol.ContainingType.IsRefLikeType) { // Already an error state. - return CallingMethodScope; + return SafeContext.CallingMethod; } // for ref-like fields defer to the receiver. @@ -4301,7 +4277,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres var initializerOpt = objectCreation.InitializerExpressionOpt; if (initializerOpt != null) { - escape = Math.Max(escape, GetValEscape(initializerOpt, scopeOfTheContainingExpression)); + escape = escape.Intersect(GetValEscape(initializerOpt, scopeOfTheContainingExpression)); } return escape; @@ -4311,12 +4287,12 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres { var newT = (BoundNewT)expr; // By default it is safe to escape - var escape = CallingMethodScope; + var escape = SafeContext.CallingMethod; var initializerOpt = newT.InitializerExpressionOpt; if (initializerOpt != null) { - escape = Math.Max(escape, GetValEscape(initializerOpt, scopeOfTheContainingExpression)); + escape = escape.Intersect(GetValEscape(initializerOpt, scopeOfTheContainingExpression)); } return escape; @@ -4325,8 +4301,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.WithExpression: var withExpression = (BoundWithExpression)expr; - return Math.Max(GetValEscape(withExpression.Receiver, scopeOfTheContainingExpression), - GetValEscape(withExpression.InitializerExpression, scopeOfTheContainingExpression)); + return GetValEscape(withExpression.Receiver, scopeOfTheContainingExpression) + .Intersect(GetValEscape(withExpression.InitializerExpression, scopeOfTheContainingExpression)); case BoundKind.UnaryOperator: var unaryOperator = (BoundUnaryOperator)expr; @@ -4358,8 +4334,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres if (conversion.ConversionKind == ConversionKind.CollectionExpression) { return HasLocalScope((BoundCollectionExpression)conversion.Operand) ? - CurrentMethodScope : - CallingMethodScope; + SafeContext.CurrentMethod : + SafeContext.CallingMethod; } if (conversion.Conversion.IsInlineArray) @@ -4429,8 +4405,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres isRefEscape: false); } - return Math.Max(GetValEscape(compound.Left, scopeOfTheContainingExpression), - GetValEscape(compound.Right, scopeOfTheContainingExpression)); + return GetValEscape(compound.Left, scopeOfTheContainingExpression) + .Intersect(GetValEscape(compound.Right, scopeOfTheContainingExpression)); case BoundKind.BinaryOperator: var binary = (BoundBinaryOperator)expr; @@ -4449,14 +4425,14 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres isRefEscape: false); } - return Math.Max(GetValEscape(binary.Left, scopeOfTheContainingExpression), - GetValEscape(binary.Right, scopeOfTheContainingExpression)); + return GetValEscape(binary.Left, scopeOfTheContainingExpression) + .Intersect(GetValEscape(binary.Right, scopeOfTheContainingExpression)); case BoundKind.RangeExpression: var range = (BoundRangeExpression)expr; - return Math.Max((range.LeftOperandOpt is { } left ? GetValEscape(left, scopeOfTheContainingExpression) : CallingMethodScope), - (range.RightOperandOpt is { } right ? GetValEscape(right, scopeOfTheContainingExpression) : CallingMethodScope)); + return (range.LeftOperandOpt is { } left ? GetValEscape(left, scopeOfTheContainingExpression) : SafeContext.CallingMethod) + .Intersect(range.RightOperandOpt is { } right ? GetValEscape(right, scopeOfTheContainingExpression) : SafeContext.CallingMethod); case BoundKind.UserDefinedConditionalLogicalOperator: var uo = (BoundUserDefinedConditionalLogicalOperator)expr; @@ -4515,7 +4491,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.PointerElementAccess: case BoundKind.PointerIndirectionOperator: // Unsafe code will always be allowed to escape. - return CallingMethodScope; + return SafeContext.CallingMethod; case BoundKind.AsOperator: case BoundKind.AwaitExpression: @@ -4589,12 +4565,12 @@ private bool HasLocalScope(BoundCollectionExpression expr) } } - private uint GetTupleValEscape(ImmutableArray elements, uint scopeOfTheContainingExpression) + private SafeContext GetTupleValEscape(ImmutableArray elements, SafeContext scopeOfTheContainingExpression) { - uint narrowestScope = scopeOfTheContainingExpression; + SafeContext narrowestScope = scopeOfTheContainingExpression; foreach (var element in elements) { - narrowestScope = Math.Max(narrowestScope, GetValEscape(element, scopeOfTheContainingExpression)); + narrowestScope = narrowestScope.Intersect(GetValEscape(element, scopeOfTheContainingExpression)); } return narrowestScope; @@ -4606,21 +4582,21 @@ private uint GetTupleValEscape(ImmutableArray elements, uint sc /// passed to an indexer for example only matter if they can escape into the receiver /// as a stored field. /// - private uint GetValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint scopeOfTheContainingExpression) + private SafeContext GetValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, SafeContext scopeOfTheContainingExpression) { - var result = CallingMethodScope; + var result = SafeContext.CallingMethod; foreach (var expr in initExpr.Initializers) { var exprResult = GetValEscapeOfObjectMemberInitializer(expr, scopeOfTheContainingExpression); - result = Math.Max(result, exprResult); + result = result.Intersect(exprResult); } return result; } - private uint GetValEscapeOfObjectMemberInitializer(BoundExpression expr, uint scopeOfTheContainingExpression) + private SafeContext GetValEscapeOfObjectMemberInitializer(BoundExpression expr, SafeContext scopeOfTheContainingExpression) { - uint result; + SafeContext result; if (expr.Kind == BoundKind.AssignmentOperator) { var assignment = (BoundAssignmentOperator)expr; @@ -4643,23 +4619,23 @@ private uint GetValEscapeOfObjectMemberInitializer(BoundExpression expr, uint sc return result; - uint getIndexerEscape( + SafeContext getIndexerEscape( PropertySymbol indexer, BoundObjectInitializerMember expr, - uint rightEscapeScope) + SafeContext rightEscapeScope) { Debug.Assert(expr.AccessorKind != AccessorKind.Unknown); var methodInfo = MethodInfo.Create(indexer, expr.AccessorKind); if (methodInfo.Method is null) { - return CallingMethodScope; + return SafeContext.CallingMethod; } // If the indexer is readonly then none of the arguments can contribute to // the receiver escape if (methodInfo.Method.IsEffectivelyReadOnly) { - return CallingMethodScope; + return SafeContext.CallingMethod; } var escapeValues = ArrayBuilder.GetInstance(); @@ -4676,7 +4652,7 @@ uint getIndexerEscape( mixableArguments: null, escapeValues); - uint receiverEscapeScope = CallingMethodScope; + SafeContext receiverEscapeScope = SafeContext.CallingMethod; foreach (var escapeValue in escapeValues) { // This is a call to an indexer so the ref escape scope can only impact the escape value if it @@ -4686,25 +4662,25 @@ uint getIndexerEscape( continue; } - uint escapeScope = escapeValue.IsRefEscape + SafeContext escapeScope = escapeValue.IsRefEscape ? GetRefEscape(escapeValue.Argument, scopeOfTheContainingExpression) : GetValEscape(escapeValue.Argument, scopeOfTheContainingExpression); - receiverEscapeScope = Math.Max(escapeScope, receiverEscapeScope); + receiverEscapeScope = escapeScope.Intersect(receiverEscapeScope); } escapeValues.Free(); - return Math.Max(receiverEscapeScope, rightEscapeScope); + return receiverEscapeScope.Intersect(rightEscapeScope); } - uint getPropertyEscape( + SafeContext getPropertyEscape( PropertySymbol property, - uint rightEscapeScope) + SafeContext rightEscapeScope) { var accessorKind = property.RefKind == RefKind.None ? AccessorKind.Set : AccessorKind.Get; var methodInfo = MethodInfo.Create(property, accessorKind); if (methodInfo.Method is null || methodInfo.Method.IsEffectivelyReadOnly) { - return CallingMethodScope; + return SafeContext.CallingMethod; } return rightEscapeScope; @@ -4713,12 +4689,12 @@ uint getPropertyEscape( #nullable disable - private uint GetValEscape(ImmutableArray expressions, uint scopeOfTheContainingExpression) + private SafeContext GetValEscape(ImmutableArray expressions, SafeContext scopeOfTheContainingExpression) { - var result = CallingMethodScope; + var result = SafeContext.CallingMethod; foreach (var expression in expressions) { - result = Math.Max(result, GetValEscape(expression, scopeOfTheContainingExpression)); + result = result.Intersect(GetValEscape(expression, scopeOfTheContainingExpression)); } return result; @@ -4729,7 +4705,7 @@ private uint GetValEscape(ImmutableArray expressions, uint scop /// The result indicates whether the escape is possible. /// Additionally, the method emits diagnostics (possibly more than one, recursively) that would help identify the cause for the failure. /// - internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeFrom, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) + internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext escapeFrom, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) { #if DEBUG AssertVisited(expr); @@ -4737,7 +4713,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter()); - if (escapeTo >= escapeFrom) + if (escapeFrom.IsConvertibleTo(escapeTo)) { // escaping to same or narrower scope is ok. return true; @@ -4796,7 +4772,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.DeconstructValuePlaceholder: case BoundKind.AwaitableValuePlaceholder: case BoundKind.InterpolatedStringArgumentPlaceholder: - if (GetPlaceholderScope((BoundValuePlaceholderBase)expr) > escapeTo) + if (!GetPlaceholderScope((BoundValuePlaceholderBase)expr).IsConvertibleTo(escapeTo)) { Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax); return inUnsafeRegion; @@ -4805,7 +4781,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.Local: var localSymbol = ((BoundLocal)expr).LocalSymbol; - if (GetLocalScopes(localSymbol).ValEscapeScope > escapeTo) + if (!GetLocalScopes(localSymbol).ValEscapeScope.IsConvertibleTo(escapeTo)) { Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, localSymbol); return inUnsafeRegion; @@ -4819,7 +4795,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.StackAllocArrayCreation: case BoundKind.ConvertedStackAllocExpression: - if (escapeTo < CurrentMethodScope) + if (!SafeContext.CurrentMethod.IsConvertibleTo(escapeTo)) { Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeStackAlloc : ErrorCode.ERR_EscapeStackAlloc, node, expr.Type); return inUnsafeRegion; @@ -5128,7 +5104,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF if (conversion.ConversionKind == ConversionKind.CollectionExpression) { - if (HasLocalScope((BoundCollectionExpression)conversion.Operand) && escapeTo < CurrentMethodScope) + if (HasLocalScope((BoundCollectionExpression)conversion.Operand) && !SafeContext.CurrentMethod.IsConvertibleTo(escapeTo)) { Error(diagnostics, ErrorCode.ERR_CollectionExpressionEscape, node, expr.Type); return false; @@ -5561,7 +5537,7 @@ private SignatureOnlyMethodSymbol GetInlineArrayConversionEquivalentSignatureMet return equivalentSignatureMethod; } - private bool CheckTupleValEscape(ImmutableArray elements, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckTupleValEscape(ImmutableArray elements, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { foreach (var element in elements) { @@ -5576,11 +5552,11 @@ private bool CheckTupleValEscape(ImmutableArray elements, uint #nullable enable - private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { foreach (var expr in initExpr.Initializers) { - if (GetValEscapeOfObjectMemberInitializer(expr, escapeFrom) > escapeTo) + if (!GetValEscapeOfObjectMemberInitializer(expr, escapeFrom).IsConvertibleTo(escapeTo)) { Error(diagnostics, _inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, initExpr.Syntax, expr.Syntax); return false; @@ -5592,7 +5568,7 @@ private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression #nullable disable - private bool CheckValEscape(ImmutableArray expressions, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckValEscape(ImmutableArray expressions, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { foreach (var expression in expressions) { @@ -5605,7 +5581,7 @@ private bool CheckValEscape(ImmutableArray expressions, uint es return true; } - private bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expression, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics) + private bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expression, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics) { var data = expression.GetInterpolatedStringHandlerData(); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 13230a166b2c1..7f62ca8b1feae 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -4377,13 +4377,13 @@ private void ValidateRefConditionalOperator(SyntaxNode node, BoundExpression tru var currentScope = _localScopeDepth; // val-escape must agree on both branches. - uint whenTrueEscape = GetValEscape(trueExpr, currentScope); - uint whenFalseEscape = GetValEscape(falseExpr, currentScope); + SafeContext whenTrueEscape = GetValEscape(trueExpr, currentScope); + SafeContext whenFalseEscape = GetValEscape(falseExpr, currentScope); if (whenTrueEscape != whenFalseEscape) { // ask the one with narrower escape, for the wider - hopefully the errors will make the violation easier to fix. - if (whenTrueEscape < whenFalseEscape) + if (!whenFalseEscape.IsConvertibleTo(whenTrueEscape)) CheckValEscape(falseExpr.Syntax, falseExpr, currentScope, whenTrueEscape, checkingReceiver: false, diagnostics: diagnostics); else CheckValEscape(trueExpr.Syntax, trueExpr, currentScope, whenFalseEscape, checkingReceiver: false, diagnostics: diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 0b4c03c795406..f31593f42dc8a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1549,12 +1549,12 @@ private void ValidateAssignment( var leftEscape = GetRefEscape(op1, _localScopeDepth); var rightEscape = GetRefEscape(op2, _localScopeDepth); - if (leftEscape < rightEscape) + if (!rightEscape.IsConvertibleTo(leftEscape)) { var errorCode = (rightEscape, _inUnsafeRegion) switch { - (ReturnOnlyScope, false) => ErrorCode.ERR_RefAssignReturnOnly, - (ReturnOnlyScope, true) => ErrorCode.WRN_RefAssignReturnOnly, + ({ IsReturnOnly: true }, false) => ErrorCode.ERR_RefAssignReturnOnly, + ({ IsReturnOnly: true }, true) => ErrorCode.WRN_RefAssignReturnOnly, (_, false) => ErrorCode.ERR_RefAssignNarrower, (_, true) => ErrorCode.WRN_RefAssignNarrower }; @@ -1570,12 +1570,13 @@ private void ValidateAssignment( leftEscape = GetValEscape(op1, _localScopeDepth); rightEscape = GetValEscape(op2, _localScopeDepth); - Debug.Assert(leftEscape == rightEscape || op1.Type.IsRefLikeOrAllowsRefLikeType()); + Debug.Assert(leftEscape.Equals(rightEscape) || op1.Type.IsRefLikeOrAllowsRefLikeType()); - // We only check if the safe-to-escape of e2 is wider than the safe-to-escape of e1 here, - // we don't check for equality. The case where the safe-to-escape of e2 is narrower than - // e1 is handled in the if (op1.Type.IsRefLikeType) { ... } block later. - if (leftEscape > rightEscape) + // We only check if the left SafeContext is convertible to the right here + // in order to give a more useful diagnostic. + // Later on we check if right SafeContext is convertible to left, + // which effectively means these SafeContexts must be equal. + if (!leftEscape.IsConvertibleTo(rightEscape)) { Debug.Assert(op1.Kind != BoundKind.Parameter); // If the assert fails, add a corresponding test. diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs index d17a26686d61d..9e7ff5ea2f103 100644 --- a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs +++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs @@ -59,10 +59,10 @@ private static bool InUnsafeMethod(Symbol symbol) private readonly bool _useUpdatedEscapeRules; private readonly BindingDiagnosticBag _diagnostics; private bool _inUnsafeRegion; - private uint _localScopeDepth; - private Dictionary? _localEscapeScopes; - private Dictionary? _placeholderScopes; - private uint _patternInputValEscape; + private SafeContext _localScopeDepth; + private Dictionary? _localEscapeScopes; + private Dictionary? _placeholderScopes; + private SafeContext _patternInputValEscape; #if DEBUG private const int MaxTrackVisited = 100; // Avoid tracking if too many expressions. private HashSet? _visited = new HashSet(); @@ -83,7 +83,7 @@ private RefSafetyAnalysis( // _localScopeDepth is incremented at each block in the method, including the // outermost. To ensure that locals in the outermost block are considered at // the same depth as parameters, _localScopeDepth is initialized to one less. - _localScopeDepth = CurrentMethodScope - 1; + _localScopeDepth = SafeContext.CurrentMethod.Wider(); } private ref struct LocalScope @@ -95,10 +95,10 @@ public LocalScope(RefSafetyAnalysis analysis, ImmutableArray locals { _analysis = analysis; _locals = locals; - _analysis._localScopeDepth++; + _analysis._localScopeDepth = _analysis._localScopeDepth.Narrower(); foreach (var local in locals) { - _analysis.AddLocalScopes(local, refEscapeScope: _analysis._localScopeDepth, valEscapeScope: CallingMethodScope); + _analysis.AddLocalScopes(local, refEscapeScope: _analysis._localScopeDepth, valEscapeScope: SafeContext.CallingMethod); } } @@ -108,7 +108,7 @@ public void Dispose() { _analysis.RemoveLocalScopes(local); } - _analysis._localScopeDepth--; + _analysis._localScopeDepth = _analysis._localScopeDepth.Wider(); } } @@ -133,9 +133,9 @@ public void Dispose() private ref struct PatternInput { private readonly RefSafetyAnalysis _analysis; - private readonly uint _previousInputValEscape; + private readonly SafeContext _previousInputValEscape; - public PatternInput(RefSafetyAnalysis analysis, uint patternInputValEscape) + public PatternInput(RefSafetyAnalysis analysis, SafeContext patternInputValEscape) { _analysis = analysis; _previousInputValEscape = analysis._patternInputValEscape; @@ -151,9 +151,9 @@ public void Dispose() private ref struct PlaceholderRegion { private readonly RefSafetyAnalysis _analysis; - private readonly ArrayBuilder<(BoundValuePlaceholderBase, uint)> _placeholders; + private readonly ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> _placeholders; - public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders) + public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders) { _analysis = analysis; _placeholders = placeholders; @@ -173,30 +173,30 @@ public void Dispose() } } - private (uint RefEscapeScope, uint ValEscapeScope) GetLocalScopes(LocalSymbol local) + private (SafeContext RefEscapeScope, SafeContext ValEscapeScope) GetLocalScopes(LocalSymbol local) { Debug.Assert(_localEscapeScopes?.ContainsKey(local) == true || _symbol != local.ContainingSymbol); return _localEscapeScopes?.TryGetValue(local, out var scopes) == true ? scopes - : (CurrentMethodScope, CallingMethodScope); + : (SafeContext.CurrentMethod, SafeContext.CallingMethod); } - private void SetLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope) + private void SetLocalScopes(LocalSymbol local, SafeContext refEscapeScope, SafeContext valEscapeScope) { Debug.Assert(_localEscapeScopes?.ContainsKey(local) == true); AddOrSetLocalScopes(local, refEscapeScope, valEscapeScope); } - private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, uint valEscapeScope) + private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, SafeContext valEscapeScope) { Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) != true); // Consider not adding the placeholder to the dictionary if the escape scope is // CallingMethod, and simply fallback to that value in GetPlaceholderScope(). - _placeholderScopes ??= new Dictionary(); + _placeholderScopes ??= new Dictionary(); _placeholderScopes[placeholder] = valEscapeScope; } @@ -211,13 +211,13 @@ private void RemovePlaceholderScope(BoundValuePlaceholderBase placeholder) } #pragma warning restore IDE0060 - private uint GetPlaceholderScope(BoundValuePlaceholderBase placeholder) + private SafeContext GetPlaceholderScope(BoundValuePlaceholderBase placeholder) { Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) == true); return _placeholderScopes?.TryGetValue(placeholder, out var scope) == true ? scope - : CallingMethodScope; + : SafeContext.CallingMethod; } #if DEBUG @@ -343,10 +343,10 @@ private void AssertVisited(BoundExpression expr) this.Visit(node.DeclarationsOpt); this.Visit(node.ExpressionOpt); - var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance(); if (node.AwaitOpt is { } awaitableInfo) { - uint valEscapeScope = node.ExpressionOpt is { } expr + SafeContext valEscapeScope = node.ExpressionOpt is { } expr ? GetValEscape(expr, _localScopeDepth) : _localScopeDepth; GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, valEscapeScope); @@ -360,7 +360,7 @@ private void AssertVisited(BoundExpression expr) public override BoundNode? VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node) { - var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance(); if (node.AwaitOpt is { } awaitableInfo) { GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, _localScopeDepth); @@ -432,7 +432,7 @@ private void AssertVisited(BoundExpression expr) return base.VisitLocal(node); } - private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope) + private void AddLocalScopes(LocalSymbol local, SafeContext refEscapeScope, SafeContext valEscapeScope) { // From https://github.com/dotnet/csharplang/blob/main/csharp-11.0/proposals/low-level-struct-improvements.md: // @@ -448,10 +448,10 @@ private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEsca { refEscapeScope = scopedModifier == ScopedKind.ScopedRef ? _localScopeDepth : - CurrentMethodScope; + SafeContext.CurrentMethod; valEscapeScope = scopedModifier == ScopedKind.ScopedValue ? _localScopeDepth : - CallingMethodScope; + SafeContext.CallingMethod; } Debug.Assert(_localEscapeScopes?.ContainsKey(local) != true); @@ -459,9 +459,9 @@ private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEsca AddOrSetLocalScopes(local, refEscapeScope, valEscapeScope); } - private void AddOrSetLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope) + private void AddOrSetLocalScopes(LocalSymbol local, SafeContext refEscapeScope, SafeContext valEscapeScope) { - _localEscapeScopes ??= new Dictionary(); + _localEscapeScopes ??= new Dictionary(); _localEscapeScopes[local] = (refEscapeScope, valEscapeScope); } @@ -482,15 +482,15 @@ private void RemoveLocalScopes(LocalSymbol local) if (node.InitializerOpt is { } initializer) { var localSymbol = (SourceLocalSymbol)node.LocalSymbol; - (uint refEscapeScope, uint valEscapeScope) = GetLocalScopes(localSymbol); + (SafeContext refEscapeScope, SafeContext valEscapeScope) = GetLocalScopes(localSymbol); if (_useUpdatedEscapeRules && localSymbol.Scope != ScopedKind.None) { - // If the local has a scoped modifier, then the lifetime is not inferred from + // If the local has a scoped modifier, then the SafeContext is not inferred from // the initializer. Validate the escape values for the initializer instead. Debug.Assert(localSymbol.RefKind == RefKind.None || - refEscapeScope >= GetRefEscape(initializer, _localScopeDepth)); + GetRefEscape(initializer, _localScopeDepth).IsConvertibleTo(refEscapeScope)); if (node.DeclaredTypeOpt?.Type.IsRefLikeOrAllowsRefLikeType() == true) { @@ -520,7 +520,7 @@ private void RemoveLocalScopes(LocalSymbol local) base.VisitReturnStatement(node); if (node.ExpressionOpt is { Type: { } } expr) { - ValidateEscape(expr, ReturnOnlyScope, node.RefKind != RefKind.None, _diagnostics); + ValidateEscape(expr, SafeContext.ReturnOnly, node.RefKind != RefKind.None, _diagnostics); } return null; } @@ -530,7 +530,7 @@ private void RemoveLocalScopes(LocalSymbol local) base.VisitYieldReturnStatement(node); if (node.Expression is { Type: { } } expr) { - ValidateEscape(expr, ReturnOnlyScope, isByRef: false, _diagnostics); + ValidateEscape(expr, SafeContext.ReturnOnly, isByRef: false, _diagnostics); } return null; } @@ -567,12 +567,12 @@ private void RemoveLocalScopes(LocalSymbol local) using var _ = new PatternInput(this, getDeclarationValEscape(node.DeclaredType, _patternInputValEscape)); return base.VisitDeclarationPattern(node); - static uint getDeclarationValEscape(BoundTypeExpression typeExpression, uint valEscape) + static SafeContext getDeclarationValEscape(BoundTypeExpression typeExpression, SafeContext valEscape) { // https://github.com/dotnet/roslyn/issues/73551: // We do not have a test that demonstrates the statement below makes a difference - // for ref like types. If 'CallingMethodScope' is always returned, not a single test fails. - return typeExpression.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : CallingMethodScope; + // for ref like types. If 'SafeContext.CallingMethod' is always returned, not a single test fails. + return typeExpression.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : SafeContext.CallingMethod; } } @@ -593,11 +593,11 @@ static uint getDeclarationValEscape(BoundTypeExpression typeExpression, uint val using var _ = new PatternInput(this, getPositionalValEscape(node.Symbol, _patternInputValEscape)); return base.VisitPositionalSubpattern(node); - static uint getPositionalValEscape(Symbol? symbol, uint valEscape) + static SafeContext getPositionalValEscape(Symbol? symbol, SafeContext valEscape) { return symbol is null ? valEscape - : symbol.GetTypeOrReturnType().IsRefLikeOrAllowsRefLikeType() ? valEscape : CallingMethodScope; + : symbol.GetTypeOrReturnType().IsRefLikeOrAllowsRefLikeType() ? valEscape : SafeContext.CallingMethod; } } @@ -606,11 +606,11 @@ static uint getPositionalValEscape(Symbol? symbol, uint valEscape) using var _ = new PatternInput(this, getMemberValEscape(node.Member, _patternInputValEscape)); return base.VisitPropertySubpattern(node); - static uint getMemberValEscape(BoundPropertySubpatternMember? member, uint valEscape) + static SafeContext getMemberValEscape(BoundPropertySubpatternMember? member, SafeContext valEscape) { if (member is null) return valEscape; valEscape = getMemberValEscape(member.Receiver, valEscape); - return member.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : CallingMethodScope; + return member.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : SafeContext.CallingMethod; } } @@ -640,7 +640,7 @@ private void VisitArgumentsAndGetArgumentPlaceholders(BoundExpression? receiverO if (arg is BoundConversion { ConversionKind: ConversionKind.InterpolatedStringHandler, Operand: BoundInterpolatedString or BoundBinaryOperator } conversion) { var interpolationData = conversion.Operand.GetInterpolatedStringHandlerData(); - var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance(); GetInterpolatedStringPlaceholders(placeholders, interpolationData, receiverOpt, i, arguments); _ = new PlaceholderRegion(this, placeholders); } @@ -671,7 +671,7 @@ protected override void VisitArguments(BoundCall node) } private void GetInterpolatedStringPlaceholders( - ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders, + ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders, in InterpolatedStringHandlerData interpolationData, BoundExpression? receiver, int nArgumentsVisited, @@ -682,7 +682,7 @@ private void GetInterpolatedStringPlaceholders( foreach (var placeholder in interpolationData.ArgumentPlaceholders) { - uint valEscapeScope; + SafeContext valEscapeScope; int argIndex = placeholder.ArgumentIndex; switch (argIndex) { @@ -690,7 +690,7 @@ private void GetInterpolatedStringPlaceholders( Debug.Assert(receiver != null); if (receiver is null) { - valEscapeScope = CallingMethodScope; + valEscapeScope = SafeContext.CallingMethod; } else { @@ -712,7 +712,7 @@ private void GetInterpolatedStringPlaceholders( else { // Error condition, see ERR_InterpolatedStringHandlerArgumentLocatedAfterInterpolatedString. - valEscapeScope = CallingMethodScope; // Consider skipping this placeholder entirely since CallingMethodScope is the fallback in GetPlaceholderScope(). + valEscapeScope = SafeContext.CallingMethod; // Consider skipping this placeholder entirely since SafeContext.CallingMethod is the fallback in GetPlaceholderScope(). } break; default: @@ -812,7 +812,7 @@ private void VisitObjectCreationExpressionBase(BoundObjectCreationExpressionBase continue; } - if (escapeFrom > GetValEscape(argument, _localScopeDepth)) + if (!escapeFrom.IsConvertibleTo(GetValEscape(argument, _localScopeDepth))) { Error(_diagnostics, ErrorCode.ERR_CallArgMixing, argument.Syntax, constructor, parameter.Name); } @@ -881,14 +881,14 @@ private void VisitObjectCreationExpressionBase(BoundObjectCreationExpressionBase public override BoundNode? VisitAwaitExpression(BoundAwaitExpression node) { this.Visit(node.Expression); - var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance(); GetAwaitableInstancePlaceholders(placeholders, node.AwaitableInfo, GetValEscape(node.Expression, _localScopeDepth)); using var _ = new PlaceholderRegion(this, placeholders); this.Visit(node.AwaitableInfo); return null; } - private void GetAwaitableInstancePlaceholders(ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders, BoundAwaitableInfo awaitableInfo, uint valEscapeScope) + private void GetAwaitableInstancePlaceholders(ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders, BoundAwaitableInfo awaitableInfo, SafeContext valEscapeScope) { if (awaitableInfo.AwaitableInstancePlaceholder is { } placeholder) { @@ -942,7 +942,7 @@ private void VisitDeconstructionArguments(ArrayBuilder v return; } - var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance(); placeholders.Add((conversion.DeconstructionInfo.InputPlaceholder, GetValEscape(right, _localScopeDepth))); var parameters = deconstructMethod.Parameters; @@ -955,7 +955,7 @@ private void VisitDeconstructionArguments(ArrayBuilder v var variable = variables[i]; var nestedVariables = variable.NestedVariables; var arg = (BoundDeconstructValuePlaceholder)invocation.Arguments[i + offset]; - uint valEscape = nestedVariables is null + SafeContext valEscape = nestedVariables is null ? GetValEscape(variable.Expression, _localScopeDepth) : _localScopeDepth; placeholders.Add((arg, valEscape)); @@ -991,10 +991,10 @@ private void VisitDeconstructionArguments(ArrayBuilder v private readonly struct DeconstructionVariable { internal readonly BoundExpression Expression; - internal readonly uint ValEscape; + internal readonly SafeContext ValEscape; internal readonly ArrayBuilder? NestedVariables; - internal DeconstructionVariable(BoundExpression expression, uint valEscape, ArrayBuilder? nestedVariables) + internal DeconstructionVariable(BoundExpression expression, SafeContext valEscape, ArrayBuilder? nestedVariables) { Expression = expression; ValEscape = valEscape; @@ -1015,7 +1015,7 @@ private ArrayBuilder GetDeconstructionAssignmentVariable DeconstructionVariable getDeconstructionAssignmentVariable(BoundExpression expr) { return expr is BoundTupleExpression tuple - ? new DeconstructionVariable(expr, valEscape: uint.MaxValue, GetDeconstructionAssignmentVariables(tuple)) + ? new DeconstructionVariable(expr, valEscape: SafeContext.Empty, GetDeconstructionAssignmentVariables(tuple)) : new DeconstructionVariable(expr, GetValEscape(expr, _localScopeDepth), null); } } @@ -1042,7 +1042,7 @@ private static ImmutableArray GetDeconstructionRightParts(Bound public override BoundNode? VisitForEachStatement(BoundForEachStatement node) { this.Visit(node.Expression); - uint collectionEscape; + SafeContext collectionEscape; if (node.EnumeratorInfoOpt is { InlineArraySpanType: not WellKnownType.Unknown and var spanType, InlineArrayUsedAsValue: false }) { @@ -1078,7 +1078,7 @@ private static ImmutableArray GetDeconstructionRightParts(Bound AddLocalScopes(local, refEscapeScope: local.RefKind == RefKind.None ? _localScopeDepth : collectionEscape, valEscapeScope: collectionEscape); } - var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance(); if (node.DeconstructionOpt?.TargetPlaceholder is { } targetPlaceholder) { placeholders.Add((targetPlaceholder, collectionEscape)); diff --git a/src/Compilers/CSharp/Portable/Binder/SafeContext.cs b/src/Compilers/CSharp/Portable/Binder/SafeContext.cs new file mode 100644 index 0000000000000..cb8c426c07a03 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/SafeContext.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + /// + /// A representation of the program region in which the *referent* of a `ref` is *live*. + /// Limited to what is expressible in C#. + /// See also: + /// - https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#detailed-design + /// - https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#972-ref-safe-contexts + /// - https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#16412-safe-context-constraint + /// + /// + /// - A *referent* is the variable being referenced by a `ref`. + /// - Informally, a variable is *live* if it has storage allocated for it (either on heap or stack). + /// - In this design, all SafeContexts have a known relationship to all other SafeContexts. + /// + internal readonly struct SafeContext + { + private const uint CallingMethodRaw = 0; + private const uint ReturnOnlyRaw = 1; + private const uint CurrentMethodRaw = 2; + + /// + /// For the purpose of escape verification we operate with the depth of local scopes. + /// The depth is a uint, with smaller number representing shallower/wider scopes. + /// Since sibling scopes do not intersect and a value cannot escape from one to another without + /// escaping to a wider scope, we can use simple depth numbering without ambiguity. + /// + private readonly uint _value; + private SafeContext(uint value) => _value = value; + + /// + /// The "calling method" scope that is outside of the containing method/lambda. + /// If something can escape to this scope, it can escape to any scope in a given method through a ref parameter or return. + /// + public static readonly SafeContext CallingMethod = new SafeContext(CallingMethodRaw); + + /// + /// The "return-only" scope that is outside of the containing method/lambda. + /// If something can escape to this scope, it can escape to any scope in a given method or can be returned, but it can't escape through a ref parameter. + /// + public static readonly SafeContext ReturnOnly = new SafeContext(ReturnOnlyRaw); + + /// + /// The "current method" scope that is just inside the containing method/lambda. + /// If something can escape to this scope, it can escape to any scope in a given method, but cannot be returned. + /// + public static readonly SafeContext CurrentMethod = new SafeContext(CurrentMethodRaw); + + /// + /// Gets a SafeContext which is "empty". i.e. which refers to a variable whose storage is never allocated. + /// + public static readonly SafeContext Empty = new SafeContext(uint.MaxValue); + + /// + /// Gets a SafeContext which is narrower than the given SafeContext. + /// Used to "enter" a nested local scope. + /// + public SafeContext Narrower() + { + Debug.Assert(_value >= ReturnOnlyRaw); + return new SafeContext(_value + 1); + } + + /// + /// Gets a SafeContext which is wider than the given SafeContext. + /// Used to "exit" a nested local scope. + /// + public SafeContext Wider() + { + Debug.Assert(_value >= CurrentMethodRaw); + return new SafeContext(_value - 1); + } + + public bool IsCallingMethod => _value == CallingMethodRaw; + public bool IsReturnOnly => _value == ReturnOnlyRaw; + public bool IsReturnable => _value is CallingMethodRaw or ReturnOnlyRaw; + + /// Returns true if a 'ref' with this SafeContext can be converted to the 'other' SafeContext. Otherwise, returns false. + /// Generally, a wider SafeContext is convertible to a narrower SafeContext. + public bool IsConvertibleTo(SafeContext other) + => this._value <= other._value; + + /// + /// Returns the narrower of two SafeContexts. + /// + /// + /// In other words, this method returns the widest SafeContext which 'this' and 'other' are both convertible to. + /// If in future we added the concept of unrelated SafeContexts (e.g. to implement 'ref scoped'), this method would perhaps return a Nullable, + /// for the case that no SafeContext exists which both input SafeContexts are convertible to. + /// + public SafeContext Intersect(SafeContext other) + => this.IsConvertibleTo(other) ? other : this; + + /// + /// Returns the wider of two SafeContexts. + /// + /// In other words, this method returns the narrowest SafeContext which can be converted to both 'this' and 'other'. + public SafeContext Union(SafeContext other) + => this.IsConvertibleTo(other) ? this : other; + + /// Returns true if this SafeContext is the same as 'other' (i.e. for invariant nested conversion). + public bool Equals(SafeContext other) + => this._value == other._value; + + public override bool Equals(object? obj) + => obj is SafeContext other && this.Equals(other); + + public override int GetHashCode() + => unchecked((int)_value); + + public static bool operator ==(SafeContext lhs, SafeContext rhs) + => lhs._value == rhs._value; + + public static bool operator !=(SafeContext lhs, SafeContext rhs) + => lhs._value != rhs._value; + + public override string ToString() + => _value switch + { + CallingMethodRaw => "SafeContext", + ReturnOnlyRaw => "SafeContext", + CurrentMethodRaw => "SafeContext", + _ => $"SafeContext<{_value}>" + }; + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 28e5f8a9cc8c0..b68f25b9d7efe 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -39,6 +39,7 @@ + @@ -108,7 +109,7 @@ --> - +