Skip to content
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

Merged
merged 3 commits into from
May 18, 2017
Merged

Include overflow checks in tuple conversions #19418

merged 3 commits into from
May 18, 2017

Conversation

cston
Copy link
Member

@cston cston commented May 10, 2017

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 a checked 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 if BoundConversion.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

@cston
Copy link
Member Author

cston commented May 11, 2017

@dotnet/roslyn-compiler please review.

@TyOverby
Copy link
Contributor

LGTM, but you might want to check out the test failure, those unit tests are typically not very flaky.

@cston
Copy link
Member Author

cston commented May 11, 2017

@dotnet-bot retest windows_release_unit32_prtest please

@cston
Copy link
Member Author

cston commented May 11, 2017

@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);
Copy link
Contributor

@AlekseyTs AlekseyTs May 11, 2017

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));
Copy link
Member

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.

Copy link
Member Author

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]
Copy link
Member

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?

Copy link
Member Author

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",
@"{
...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, nevermind, this is skipped ;-)


In reply to: 116064973 [](ancestors = 116064973)

@@ -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));
Copy link
Contributor

@AlekseyTs AlekseyTs May 11, 2017

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]
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Contributor

@AlekseyTs AlekseyTs May 11, 2017

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

Copy link
Member Author

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()
Copy link
Contributor

@AlekseyTs AlekseyTs May 11, 2017

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented May 11, 2017

Done with review pass. #Closed


return
sourceIsNumeric && (targetIsNumeric || target.IsPointerType()) ||
targetIsNumeric && source.IsPointerType();
Copy link
Member

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.

Copy link
Member Author

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)
Copy link
Contributor

@AlekseyTs AlekseyTs May 12, 2017

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@cston
Copy link
Member Author

cston commented May 13, 2017

@dotnet-bot retest this please

@VSadov
Copy link
Member

VSadov commented May 13, 2017

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 checked bit from conversions when it does not make sense or does not make any difference.
However there is no dependency on this behavior in emit and in the case of expression trees there was some redundancy.

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.

@cston
Copy link
Member Author

cston commented May 17, 2017

@dotnet/roslyn-compiler, all comments addressed, please review.

@jcouv
Copy link
Member

jcouv commented May 18, 2017

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);
}
Copy link
Member

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?

Copy link
Member Author

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.)

Copy link
Member

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
Copy link
Member

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".

Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ulong" as well?

Copy link
Member Author

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.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@cston
Copy link
Member Author

cston commented May 18, 2017

@MeiChin-Tsai for approval.

@MeiChin-Tsai
Copy link

@cston How do we evaluate the breaking change impact?

@cston
Copy link
Member Author

cston commented May 18, 2017

@MeiChin-Tsai The breaking change impact should be limited since the change affects checked conversions of tuples only, and tuples are new to C#7.0. Without this fix, overflow checks are dropped from checked conversions so the result is incorrect at runtime. With this fix, the conversion will result in a OverflowException at runtime.

The breaking change was approved by the compat council.

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MeiChin-Tsai
Copy link

approved.

@cston
Copy link
Member Author

cston commented May 18, 2017

@dotnet-bot retest windows_debug_vs-integration_prtest please

@cston
Copy link
Member Author

cston commented May 18, 2017

@dotnet-bot retest windows_release_vs-integration_prtest please

@cston cston merged commit ba0cefe into dotnet:master May 18, 2017
@cston cston deleted the 18459 branch March 15, 2024 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants