From b80d20b15d3d9d0061aabbe47f4aced92197e76f Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 3 Jun 2023 18:37:15 +0200 Subject: [PATCH] Add support for calling `operator checked`. --- .../TestCases/Pretty/Operators.cs | 27 +++++++ .../CSharp/ExpressionBuilder.cs | 11 +++ .../CSharp/OutputVisitor/CSharpAmbience.cs | 6 ++ .../ReplaceMethodCallsWithOperators.cs | 79 ++++++++++++++++--- .../CompoundAssignmentInstruction.cs | 13 ++- .../IL/Transforms/FixRemainingIncrements.cs | 2 +- .../IL/Transforms/TransformAssignment.cs | 4 +- ILSpy/Properties/Resources.resx | 6 +- 8 files changed, 131 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs index 54c8f2254..d6a521bf3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs @@ -217,6 +217,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty c = a - b; c = a * b; c = a / b; +#if CS110 + checked + { + c = a + b; + c = a - b; + c = a * b; + c = a / b; + } + // force end of checked block: + ++a; +#endif + c = a % b; c = a & b; c = a | b; @@ -232,6 +244,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty c = +a; c = ++a; c = --a; +#if CS110 + checked + { + c = -a; + c = ++a; + c = --a; + } + // force end of checked block: + ++a; +#endif if (a) { Console.WriteLine("a"); @@ -265,6 +287,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine("a >= b"); } int num = (int)a; +#if CS110 + num = checked((int)a); + // force end of checked block: + num = (int)a; +#endif a = num; } } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 6c0e30200..7f9658868 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1810,6 +1810,15 @@ namespace ICSharpCode.Decompiler.CSharp { target = Translate(inst.Target, loadType); } + var opType = OperatorDeclaration.GetOperatorType(inst.Method.Name); + if (opType != null && OperatorDeclaration.IsChecked(opType.Value)) + { + target.Expression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation); + } + else if (ReplaceMethodCallsWithOperators.HasCheckedEquivalent(inst.Method)) + { + target.Expression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation); + } if (UserDefinedCompoundAssign.IsStringConcat(inst.Method)) { Debug.Assert(inst.Method.Parameters.Count == 2); @@ -1876,8 +1885,10 @@ namespace ICSharpCode.Decompiler.CSharp switch (name) { case "op_Increment": + case "op_CheckedIncrement": return isPostfix ? UnaryOperatorType.PostIncrement : UnaryOperatorType.Increment; case "op_Decrement": + case "op_CheckedDecrement": return isPostfix ? UnaryOperatorType.PostDecrement : UnaryOperatorType.Decrement; default: return null; diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index 7a41a9d82..8e6c5df31 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -297,10 +297,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor ConvertType(member.ReturnType, writer, formattingPolicy); break; case "op_Explicit": + case "op_CheckedExplicit": writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit"); writer.Space(); writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); writer.Space(); + if (member.Name == "op_CheckedExplicit") + { + writer.WriteToken(OperatorDeclaration.CheckedKeywordRole, "checked"); + writer.Space(); + } ConvertType(member.ReturnType, writer, formattingPolicy); break; default: diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index 738fcd46a..cf9970232 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Linq; using System.Reflection; @@ -142,10 +143,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms break; } - BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name, context.Settings); + bool isChecked; + BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name, out isChecked, context.Settings); if (bop != null && arguments.Length == 2) { invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression + if (isChecked) + { + invocationExpression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation); + } + else if (HasCheckedEquivalent(method)) + { + invocationExpression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation); + } invocationExpression.ReplaceWith( new BinaryOperatorExpression( arguments[0].UnwrapInDirectionExpression(), @@ -155,9 +165,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms ); return; } - UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(method.Name); + UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(method.Name, out isChecked, context.Settings); if (uop != null && arguments.Length == 1) { + if (isChecked) + { + invocationExpression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation); + } + else if (HasCheckedEquivalent(method)) + { + invocationExpression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation); + } if (uop == UnaryOperatorType.Increment || uop == UnaryOperatorType.Decrement) { // `op_Increment(a)` is not equivalent to `++a`, @@ -174,17 +192,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms ).CopyAnnotationsFrom(invocationExpression) ); } - return; } - arguments[0].Remove(); // detach argument - invocationExpression.ReplaceWith( - new UnaryOperatorExpression(uop.Value, arguments[0].UnwrapInDirectionExpression()).CopyAnnotationsFrom(invocationExpression) - ); + else + { + arguments[0].Remove(); // detach argument + invocationExpression.ReplaceWith( + new UnaryOperatorExpression(uop.Value, arguments[0].UnwrapInDirectionExpression()).CopyAnnotationsFrom(invocationExpression) + ); + } return; } - if (method.Name == "op_Explicit" && arguments.Length == 1) + if (method.Name is "op_Explicit" or "op_CheckedExplicit" && arguments.Length == 1) { arguments[0].Remove(); // detach argument + if (method.Name == "op_CheckedExplicit") + { + invocationExpression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation); + } + else if (HasCheckedEquivalent(method)) + { + invocationExpression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation); + } invocationExpression.ReplaceWith( new CastExpression(context.TypeSystemAstBuilder.ConvertType(method.ReturnType), arguments[0].UnwrapInDirectionExpression()) .CopyAnnotationsFrom(invocationExpression) @@ -200,6 +228,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return; } + internal static bool HasCheckedEquivalent(IMethod method) + { + string name = method.Name; + if (name.StartsWith("op_", StringComparison.Ordinal)) + name = "op_Checked" + name.Substring(3); + return method.DeclaringType.GetMethods(m => m.IsOperator && m.Name == name).Any(); + } + bool IsInstantiableTypeParameter(IType type) { return type is ITypeParameter tp && tp.HasDefaultConstructorConstraint; @@ -350,8 +386,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name, DecompilerSettings settings) + static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name, out bool isChecked, DecompilerSettings settings) { + isChecked = false; switch (name) { case "op_Addition": @@ -362,6 +399,18 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return BinaryOperatorType.Multiply; case "op_Division": return BinaryOperatorType.Divide; + case "op_CheckedAddition" when settings.CheckedOperators: + isChecked = true; + return BinaryOperatorType.Add; + case "op_CheckedSubtraction" when settings.CheckedOperators: + isChecked = true; + return BinaryOperatorType.Subtract; + case "op_CheckedMultiply" when settings.CheckedOperators: + isChecked = true; + return BinaryOperatorType.Multiply; + case "op_CheckedDivision" when settings.CheckedOperators: + isChecked = true; + return BinaryOperatorType.Divide; case "op_Modulus": return BinaryOperatorType.Modulus; case "op_BitwiseAnd": @@ -393,8 +442,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - static UnaryOperatorType? GetUnaryOperatorTypeFromMetadataName(string name) + static UnaryOperatorType? GetUnaryOperatorTypeFromMetadataName(string name, out bool isChecked, DecompilerSettings settings) { + isChecked = false; switch (name) { case "op_LogicalNot": @@ -403,12 +453,21 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return UnaryOperatorType.BitNot; case "op_UnaryNegation": return UnaryOperatorType.Minus; + case "op_CheckedUnaryNegation" when settings.CheckedOperators: + isChecked = true; + return UnaryOperatorType.Minus; case "op_UnaryPlus": return UnaryOperatorType.Plus; case "op_Increment": return UnaryOperatorType.Increment; case "op_Decrement": return UnaryOperatorType.Decrement; + case "op_CheckedIncrement" when settings.CheckedOperators: + isChecked = true; + return UnaryOperatorType.Increment; + case "op_CheckedDecrement" when settings.CheckedOperators: + isChecked = true; + return UnaryOperatorType.Decrement; default: return null; } diff --git a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs index a25e39a96..4e3a504f5 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs @@ -315,7 +315,18 @@ namespace ICSharpCode.Decompiler.IL { this.Method = method; Debug.Assert(Method.IsOperator || IsStringConcat(method)); - Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || (Method.Name == "op_Increment" || Method.Name == "op_Decrement")); + Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || IsIncrementOrDecrement(method)); + } + + public static bool IsIncrementOrDecrement(IMethod method, DecompilerSettings? settings = null) + { + if (!(method.IsOperator && method.IsStatic)) + return false; + if (method.Name is "op_Increment" or "op_Decrement") + return true; + if (method.Name is "op_CheckedIncrement" or "op_CheckedDecrement") + return settings?.CheckedOperators ?? true; + return false; } public static bool IsStringConcat(IMethod method) diff --git a/ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs b/ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs index 71bbbcad2..aaf926945 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms var callsToFix = new List(); foreach (var call in function.Descendants.OfType()) { - if (!(call.Method.IsOperator && (call.Method.Name == "op_Increment" || call.Method.Name == "op_Decrement"))) + if (!UserDefinedCompoundAssign.IsIncrementOrDecrement(call.Method, context.Settings)) continue; if (call.Arguments.Count != 1) continue; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs index 849ac8840..c66646750 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs @@ -392,7 +392,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else if (operatorCall.Arguments.Count == 1) { - if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement")) + if (!UserDefinedCompoundAssign.IsIncrementOrDecrement(operatorCall.Method, context.Settings)) return false; // use a dummy node so that we don't need a dedicated instruction for user-defined unary operator calls rhs = new LdcI4(1); @@ -988,7 +988,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!operatorCall.Arguments[0].MatchLdLoc(tmpVar)) return false; - if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement")) + if (!UserDefinedCompoundAssign.IsIncrementOrDecrement(operatorCall.Method, context.Settings)) return false; if (operatorCall.IsLifted) return false; // TODO: add tests and think about whether nullables need special considerations diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 680268294..13c4d0ba2 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -279,6 +279,9 @@ Are you sure you want to continue? Decompile async IAsyncEnumerator methods + + User-defined checked operators + Decompile anonymous methods/lambdas @@ -1069,7 +1072,4 @@ Do you want to continue? _Window - - User-defined checked operators - \ No newline at end of file