提交 7abde2f9 编写于 作者: V vsadov

Introduced CheckValEscape. Simple scenarios/tests work.

上级 18bb0438
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
using System.Diagnostics; using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities; using Roslyn.Utilities;
...@@ -918,10 +919,10 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ...@@ -918,10 +919,10 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
uint escapeTo, uint escapeTo,
DiagnosticBag diagnostics) DiagnosticBag diagnostics)
{ {
//TODO: VS optional "in" arguments - scopeOfTheContainingExpression
//TODO: VS ref-like receiver //TODO: VS ref-like receiver
ArrayBuilder<bool> inParametersMatchedWithValueArgs = null;
// check all arguments that are not passed by value // check all arguments that are not passed by value
if (!args.IsDefault) if (!args.IsDefault)
{ {
...@@ -934,6 +935,8 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ...@@ -934,6 +935,8 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
if (parameters[paramIndex].RefKind == RefKind.RefReadOnly) if (parameters[paramIndex].RefKind == RefKind.RefReadOnly)
{ {
effectiveRefKind = RefKind.RefReadOnly; effectiveRefKind = RefKind.RefReadOnly;
inParametersMatchedWithValueArgs = inParametersMatchedWithValueArgs ?? ArrayBuilder<bool>.GetInstance(parameters.Length, fillWithValue: false);
inParametersMatchedWithValueArgs[paramIndex] = true;
} }
} }
...@@ -941,7 +944,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ...@@ -941,7 +944,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
var argument = args[argIndex]; var argument = args[argIndex];
var valid = effectiveRefKind != RefKind.None ? var valid = effectiveRefKind != RefKind.None ?
CheckRefEscape(argument.Syntax, argument, escapeTo, false, diagnostics) : CheckRefEscape(argument.Syntax, argument, escapeTo, false, diagnostics) :
true; // CheckRefEscape(argument.Syntax, argument, escapeTo, false, diagnostics); //TODO: VS should be CheckVal CheckValEscape(argument.Syntax, argument, escapeTo, false, diagnostics);
if (!valid) if (!valid)
{ {
...@@ -953,7 +956,32 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ...@@ -953,7 +956,32 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
} }
} }
} }
// handle omitted optional "in" parameters if there are any
if (!parameters.IsDefault)
{
for (int i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (parameter.IsParams)
{
break;
}
if (parameter.RefKind == RefKind.RefReadOnly && inParametersMatchedWithValueArgs?[i] != true)
{
// unmatched "in" parameter is an RValue
var errorCode = checkingReceiver ? ErrorCode.ERR_RefReturnCall2 : ErrorCode.ERR_RefReturnCall;
var parameterName = parameters[i].Name;
Error(diagnostics, errorCode, syntax, symbol, parameterName);
inParametersMatchedWithValueArgs?.Free();
return false;
}
}
}
inParametersMatchedWithValueArgs?.Free();
return true; return true;
} }
...@@ -970,6 +998,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ...@@ -970,6 +998,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
//by default it is safe to escape //by default it is safe to escape
uint escapeScope = Binder.ExternalScope; uint escapeScope = Binder.ExternalScope;
ArrayBuilder<bool> inParametersMatchedWithValueArgs = null;
if (!args.IsDefault) if (!args.IsDefault)
{ {
...@@ -979,9 +1008,12 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ...@@ -979,9 +1008,12 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
if (effectiveRefKind == RefKind.None && argIndex < parameters.Length) if (effectiveRefKind == RefKind.None && argIndex < parameters.Length)
{ {
var paramIndex = argToParamsOpt.IsDefault ? argIndex : argToParamsOpt[argIndex]; var paramIndex = argToParamsOpt.IsDefault ? argIndex : argToParamsOpt[argIndex];
if (parameters[paramIndex].RefKind == RefKind.RefReadOnly) if (parameters[paramIndex].RefKind == RefKind.RefReadOnly)
{ {
effectiveRefKind = RefKind.RefReadOnly; effectiveRefKind = RefKind.RefReadOnly;
inParametersMatchedWithValueArgs = inParametersMatchedWithValueArgs ?? ArrayBuilder<bool>.GetInstance(parameters.Length, fillWithValue: false);
inParametersMatchedWithValueArgs[paramIndex] = true;
} }
} }
...@@ -995,11 +1027,33 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ...@@ -995,11 +1027,33 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
if (escapeScope >= scopeOfTheContainingExpression) if (escapeScope >= scopeOfTheContainingExpression)
{ {
// can't get any worse // can't get any worse
inParametersMatchedWithValueArgs?.Free();
return escapeScope; return escapeScope;
} }
} }
} }
// handle omitted optional "in" parameters if there are any
if (!parameters.IsDefault)
{
for(int i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (parameter.IsParams)
{
break;
}
if (parameter.RefKind == RefKind.RefReadOnly && inParametersMatchedWithValueArgs?[i] != true)
{
// unmatched "in" parameter is an RValue
inParametersMatchedWithValueArgs?.Free();
return scopeOfTheContainingExpression;
}
}
}
inParametersMatchedWithValueArgs?.Free();
return escapeScope; return escapeScope;
} }
...@@ -1222,7 +1276,7 @@ private static void ReportReadOnlyError(Symbol symbol, SyntaxNode node, BindValu ...@@ -1222,7 +1276,7 @@ private static void ReportReadOnlyError(Symbol symbol, SyntaxNode node, BindValu
Error(diagnostics, ReadOnlyErrors[index], node, symbolKind, symbol); Error(diagnostics, ReadOnlyErrors[index], node, symbolKind, symbol);
} }
internal BoundExpression CheckRefEscape(BoundExpression expr, uint escapeTo, DiagnosticBag diagnostics) internal BoundExpression EnsureRefEscape(BoundExpression expr, uint escapeTo, DiagnosticBag diagnostics)
{ {
if (CheckRefEscape(expr.Syntax, expr, escapeTo, checkingReceiver: false, diagnostics: diagnostics)) if (CheckRefEscape(expr.Syntax, expr, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{ {
...@@ -1242,7 +1296,12 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeT ...@@ -1242,7 +1296,12 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeT
return false; return false;
} }
Debug.Assert(expr.Type.GetSpecialTypeSafe() != SpecialType.System_Void); // void references cannot escape (error cases)
if (expr.Type?.GetSpecialTypeSafe() == SpecialType.System_Void)
{
Error(diagnostics, GetStandardRValueRefEscapeError(escapeTo), node);
return false;
}
// references to constants/literals cannot escape anywhere. // references to constants/literals cannot escape anywhere.
if (expr.ConstantValue != null) if (expr.ConstantValue != null)
...@@ -1545,6 +1604,114 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres ...@@ -1545,6 +1604,114 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres
return scopeOfTheContainingExpression; return scopeOfTheContainingExpression;
} }
internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeTo, bool checkingReceiver, DiagnosticBag diagnostics)
{
Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter());
// cannot infer anything from errors
if (expr.HasAnyErrors)
{
return true;
}
// constants/literals cannot refer to local state
if (expr.ConstantValue != null)
{
return true;
}
// to have local-referring values an expression must have a ref-like type
if (expr.Type?.IsByRefLikeType != true)
{
return true;
}
// cover case that can refer to local state
// otherwise default to ExternalScope (ordinary values)
switch (expr.Kind)
{
case BoundKind.Local:
if (((BoundLocal)expr).LocalSymbol.ValEscapeScope > escapeTo)
{
//TODO: VS right error
Error(diagnostics, GetStandardRValueRefEscapeError(escapeTo), node);
}
return true;
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
// byref conditional defers to its operands
// TODO: VS when do we do with no-mixing?
return CheckValEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeTo, checkingReceiver: false, diagnostics: diagnostics) &
CheckValEscape(conditional.Alternative.Syntax, conditional.Alternative, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.FieldAccess:
var fieldAccess = (BoundFieldAccess)expr;
var fieldSymbol = fieldAccess.FieldSymbol;
Debug.Assert(!fieldSymbol.IsStatic && fieldSymbol.ContainingType.IsByRefLikeType);
// for ref-like fields defer to the receiver.
return CheckValEscape(node, fieldAccess.ReceiverOpt, escapeTo, true, diagnostics);
case BoundKind.Call:
var call = (BoundCall)expr;
var methodSymbol = call.Method;
if (methodSymbol.RefKind == RefKind.None)
{
break;
}
return CheckInvocationEscape(
call.Syntax,
methodSymbol,
methodSymbol.Parameters,
call.Arguments,
call.ArgumentRefKindsOpt,
call.ArgsToParamsOpt,
checkingReceiver,
escapeTo,
diagnostics);
case BoundKind.IndexerAccess:
var indexerAccess = (BoundIndexerAccess)expr;
var indexerSymbol = indexerAccess.Indexer;
return CheckInvocationEscape(
indexerAccess.Syntax,
indexerSymbol,
indexerSymbol.Parameters,
indexerAccess.Arguments,
indexerAccess.ArgumentRefKindsOpt,
indexerAccess.ArgsToParamsOpt,
checkingReceiver,
escapeTo,
diagnostics);
case BoundKind.PropertyAccess:
var propertyAccess = (BoundPropertyAccess)expr;
var propertySymbol = propertyAccess.PropertySymbol;
// not passing any arguments/parameters
return CheckInvocationEscape(
propertyAccess.Syntax,
propertySymbol,
default,
default,
default,
default,
checkingReceiver,
escapeTo,
diagnostics);
}
// At this point we should have covered all the possible cases for anything that is not a strict RValue.
Error(diagnostics, GetStandardRValueRefEscapeError(escapeTo), node);
return false;
}
internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpression) internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpression)
{ {
// cannot infer anything from errors // cannot infer anything from errors
...@@ -1556,26 +1723,19 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres ...@@ -1556,26 +1723,19 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
// constants/literals cannot refer to local state // constants/literals cannot refer to local state
if (expr.ConstantValue != null) if (expr.ConstantValue != null)
{ {
return Binder.ExternalScope; ; return Binder.ExternalScope;
} }
// to have local-referring values an expression must have a ref-like type // to have local-referring values an expression must have a ref-like type
if (expr.Type?.IsByRefLikeType != true) if (expr.Type?.IsByRefLikeType != true)
{ {
return Binder.ExternalScope; ; return Binder.ExternalScope;
} }
// cover case that can refer to local state // cover case that can refer to local state
// otherwise default to ExternalScope (ordinary values) // otherwise default to ExternalScope (ordinary values)
switch (expr.Kind) switch (expr.Kind)
{ {
case BoundKind.RefValueOperator:
// The undocumented __refvalue(tr, T) expression results in a variable of type T.
// it is a readwrite variable, but could refer to unknown local data
// we will constrain it to the current scope
// TODO: VS is this right?
return scopeOfTheContainingExpression;
case BoundKind.Local: case BoundKind.Local:
return ((BoundLocal)expr).LocalSymbol.ValEscapeScope; return ((BoundLocal)expr).LocalSymbol.ValEscapeScope;
...@@ -1631,6 +1791,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres ...@@ -1631,6 +1791,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
//TODO: VS other exprs with operands - Obj creations, binary, unary, parenthesized?, etc... //TODO: VS other exprs with operands - Obj creations, binary, unary, parenthesized?, etc...
//TODO: __refvalue. It is not possible to __makeref(span), but it is allowed to do __refvalue(tr, Span), perhaps it shoudl not be allowed, looks like a bug.
} }
// At this point we should have covered all the possible cases for anything that is not a strict RValue. // At this point we should have covered all the possible cases for anything that is not a strict RValue.
......
...@@ -2324,7 +2324,7 @@ private BoundStatement BindReturn(ReturnStatementSyntax syntax, DiagnosticBag di ...@@ -2324,7 +2324,7 @@ private BoundStatement BindReturn(ReturnStatementSyntax syntax, DiagnosticBag di
if (refKind != RefKind.None) if (refKind != RefKind.None)
{ {
arg = CheckRefEscape(arg, Binder.ExternalScope, diagnostics); arg = EnsureRefEscape(arg, Binder.ExternalScope, diagnostics);
} }
} }
else else
...@@ -2804,7 +2804,7 @@ private static bool IsValidExpressionBody(SyntaxNode expressionSyntax, BoundExpr ...@@ -2804,7 +2804,7 @@ private static bool IsValidExpressionBody(SyntaxNode expressionSyntax, BoundExpr
if (refKind != RefKind.None) if (refKind != RefKind.None)
{ {
expression = CheckRefEscape(expression, Binder.ExternalScope, diagnostics); expression = EnsureRefEscape(expression, Binder.ExternalScope, diagnostics);
} }
return bodyBinder.CreateBlockFromExpression(expressionBody, bodyBinder.GetDeclaredLocalsForScope(expressionBody), refKind, expression, expressionSyntax, diagnostics); return bodyBinder.CreateBlockFromExpression(expressionBody, bodyBinder.GetDeclaredLocalsForScope(expressionBody), refKind, expression, expressionSyntax, diagnostics);
...@@ -2825,7 +2825,7 @@ public BoundBlock BindLambdaExpressionAsBlock(ExpressionSyntax body, DiagnosticB ...@@ -2825,7 +2825,7 @@ public BoundBlock BindLambdaExpressionAsBlock(ExpressionSyntax body, DiagnosticB
if (refKind != RefKind.None) if (refKind != RefKind.None)
{ {
expression = CheckRefEscape(expression, Binder.ExternalScope, diagnostics); expression = EnsureRefEscape(expression, Binder.ExternalScope, diagnostics);
} }
return bodyBinder.CreateBlockFromExpression(body, bodyBinder.GetDeclaredLocalsForScope(body), refKind, expression, expressionSyntax, diagnostics); return bodyBinder.CreateBlockFromExpression(body, bodyBinder.GetDeclaredLocalsForScope(body), refKind, expression, expressionSyntax, diagnostics);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics
{
[CompilerTrait(CompilerFeature.ReadOnlyReferences)]
public class RefEscapingTests : CompilingTestBase
{
[Fact()]
public void SimpleRefLikeReturnEscape()
{
var text = @"
class Program
{
static void Main()
{
}
static ref int Test1(S1 arg)
{
return ref (new int[1])[0];
}
static S1 Test2(ref int arg)
{
return default;
}
static ref int Test3()
{
int local = 42;
return ref Test1(Test2(ref local));
}
ref struct S1
{
}
}
";
CreateStandardCompilation(text).VerifyDiagnostics(
// (21,30): error CS8156: An expression cannot be used in this context because it may not be returned by reference
// return ref Test1(Test2(ref local));
Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "Test2(ref local)").WithLocation(21, 30),
// (21,24): error CS8164: Cannot return by reference a result of 'Program.Test1(Program.S1)' because the argument passed to parameter 'arg' cannot be returned by reference
// return ref Test1(Test2(ref local));
Diagnostic(ErrorCode.ERR_RefReturnCall, "Test1(Test2(ref local))").WithArguments("Program.Test1(Program.S1)", "arg").WithLocation(21, 24)
);
}
[Fact()]
public void SimpleRefLikeReturnEscape1()
{
var text = @"
class Program
{
static void Main()
{
}
static ref int Test1(S1 arg)
{
return ref (new int[1])[0];
}
static S1 Test2(ref int arg)
{
return default;
}
static ref int Test3()
{
int local = 42;
var sp = Test2(ref local);
return ref Test1(sp);
}
ref struct S1
{
}
}
";
CreateStandardCompilation(text).VerifyDiagnostics(
// (22,30): error CS8156: An expression cannot be used in this context because it may not be returned by reference
// return ref Test1(sp);
Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "sp").WithLocation(22, 30)
);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册