-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Include overflow checks in tuple conversions #19418
Conversation
@dotnet/roslyn-compiler please review. |
LGTM, but you might want to check out the test failure, those unit tests are typically not very flaky. |
@dotnet-bot retest windows_release_unit32_prtest please |
@dotnet-bot retest windows_release_vs-integration_prtest please |
return | ||
sourceIsNumeric && (targetIsNumeric || target.IsPointerType()) || | ||
targetIsNumeric && source.IsPointerType(); | ||
return CheckOverflowAtRuntime && LocalRewriter.NeedsCheckedConversion(source, target, compareNumericTypes: false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return CheckOverflowAtRuntime && LocalRewriter.NeedsCheckedConversion(source, target, compareNumericTypes: false); [](start = 12, length = 114)
I do not understand why do we need this complicated analysis in binding, simply capturing the value of CheckOverflowAtRuntime should be good enough, I believe. #Closed
{ | ||
try | ||
{ | ||
var u = Unchecked((-1, -1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider move Unchecked call out of try/catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Diagnostic(ErrorCode.ERR_ConstOutOfRangeChecked, "-2").WithArguments("-2", "byte").WithLocation(14, 44)); | ||
} | ||
|
||
[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be useful to check emitted IL when neither checked or unchecked is used? Which one is the default behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
overflow"); | ||
verifier.VerifyIL("C.Unchecked", | ||
@"{ | ||
... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -110,7 +110,7 @@ private static bool IsFloatPointExpressionOfUnknownPrecision(BoundExpression rew | |||
Debug.Assert((object)rewrittenType != null); | |||
@checked = @checked && | |||
(_inExpressionLambda && (explicitCastInCode || DistinctSpecialTypes(rewrittenOperand.Type, rewrittenType)) || | |||
NeedsChecked(rewrittenOperand.Type, rewrittenType)); | |||
NeedsCheckedConversion(rewrittenOperand.Type, rewrittenType, compareNumericTypes: true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NeedsCheckedConversion(rewrittenOperand.Type, rewrittenType, compareNumericTypes: true)); [](start = 16, length = 89)
The same comment is here, I think we should simply propagate what is given as an input and let IL generation to ignore it when it is not applicable. I think this is what VB does. #Closed
@@ -22872,5 +22871,174 @@ void M<T>() | |||
Diagnostic(ErrorCode.ERR_TypelessTupleInAs, "(0, null) as (int, T)?").WithLocation(6, 17) | |||
); | |||
} | |||
|
|||
[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to notify/review this change with the compat council?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
{ | ||
unchecked | ||
{ | ||
(a, b) = ((long, byte))t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(a, b) = ((long, byte))t; [](start = 12, length = 25)
(a, b) = ((long, byte))t; [](start = 12, length = 25)
It doesn't look like this tests deconstruction conversions, because types on both sides match. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed test.
@@ -20200,6 +20200,51 @@ End Namespace | |||
|
|||
End Sub | |||
|
|||
<Fact> | |||
Public Sub CheckedConversions() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CheckedConversions [](start = 19, length = 18)
Should also test "unchecked" context. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Done with review pass. #Closed |
|
||
return | ||
sourceIsNumeric && (targetIsNumeric || target.IsPointerType()) || | ||
targetIsNumeric && source.IsPointerType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like NeedsCheckedConversion
handles more case than this method used to. The new method checks sourceIsNumeric && targetIsNumeric && (!compareNumericTypes || s_needsChecked[sourceST - SpecialType.System_Char, targetST - SpecialType.System_Char])
.
Does that mean we fixed another gap by this refactoring? If that gap exists and can be identified, maybe it should be tested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method calls NeedsCheckedConversion(..., compareNumericTypes: false)
to skip that last check, for compatibility with the previous implementation.
That said, I'd prefer to remove the checks and just use CheckOverflowAtRuntime
as @AlekseyTs suggested. I'll test that.
@@ -428,22 +385,22 @@ private static bool NeedsChecked(TypeSymbol source, TypeSymbol target) | |||
return false; | |||
} | |||
|
|||
if (source.IsDynamic()) | |||
if (source.IsTupleType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (source.IsTupleType) [](start = 12, length = 23)
if (source.IsTupleType) [](start = 12, length = 23)
I would remove this check if its effect cannot be observed today. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@dotnet-bot retest this please |
We went with @cston over these changes trying to really understand the purpose of old code. It appears that we had several layers that would try removing As we can see in the regular (not expression tree) case, removal of inapplicable checked is not observable since codegen ignores inapplicable checked anyways. In the case of expression trees we keep the removal of inapplicable checked, but it was moved to the lowering and some redundant checks removed. Other possible ways to see the difference are through semantic model or IOperation, but neither expose information about "checked". So overall this change makes behavior of "checked" bit more rational, while we do not see any observable changes in the results. |
@dotnet/roslyn-compiler, all comments addressed, please review. |
Looks like there are some legitimate test failures. |
sourceIsNumeric && targetIsNumeric && s_needsChecked[sourceST - SpecialType.System_Char, targetST - SpecialType.System_Char]; | ||
return IsUnderlyingTypeInRange(source, SpecialType.System_Char, SpecialType.System_Double) && | ||
IsUnderlyingTypeInRange(target, SpecialType.System_Char, SpecialType.System_UInt64); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This simplification seems like magic to me. How did we do away with the truth table?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The table was not used for Expression lambas. See the previous code at LocalRewriter_Conversions.cs
line 111, @checked
depended on DistinctSpecialTypes
only in that case. (And DistinctSpecialTypes
had overlap with this method, so the two were combined.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. Thanks for the clarification!
CheckedConversionInExpressionTree_Implicit("ulong", "float", ConvertMethod.Convert); | ||
CheckedConversionInExpressionTree_Implicit("ulong", "double", ConvertMethod.Convert); | ||
|
||
// decimal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, there is something else I didn't understand here. I expected to see test for "decimal" to " "float" and "decimal" to "double", to complete the testing of half of the matrix.
On a related note, I didn't get why only testing half of the matrix. For instance, "decimal" to "ulong".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, nevermind the second question. The other half of the matrix cannot be implicit conversion, right?
CheckedConversionInExpressionTree_Implicit("uint", "double", ConvertMethod.Convert); | ||
|
||
// long | ||
CheckedConversionInExpressionTree_Implicit("long", "long", ConvertMethod.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"ulong" as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is testing implicit conversions, so some combinations such as long
to ulong
cannot be tested since there is no implicit conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@MeiChin-Tsai for approval. |
@cston How do we evaluate the breaking change impact? |
@MeiChin-Tsai The breaking change impact should be limited since the change affects The breaking change was approved by the compat council. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
approved. |
@dotnet-bot retest windows_debug_vs-integration_prtest please |
@dotnet-bot retest windows_release_vs-integration_prtest please |
Customer scenario
Tuple conversions in
checked
contexts should include overflow checks.Bugs this fixes:
#18459
Workarounds, if any
Deconstruct and reconstruct tuples, applying conversions on each element individually.
Risk
Low. Note however that the conditions in which
BoundConversion.Checked
is set have been simplified significantly. For non-Expression trees, binding and lowering set the state based only on whether the expression is in achecked
context. For Expression trees, the conditions are now implemented in lowering only rather than split between binding and lowering. The code generator, which is responsible for emitting overflow checks, has not changed however and the code generator implements a set of checks based on the actual types ifBoundConversion.Checked
is set.Note, this is a breaking change since overflow checks were not included within tuple conversions in 15.0.
Performance impact
Low
How was the bug found?
Customer reported