Browse Source

Add support for calling `operator checked`.

pull/3017/head
Daniel Grunwald 2 years ago
parent
commit
b80d20b15d
  1. 27
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs
  2. 11
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 6
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  4. 71
      ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
  5. 13
      ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs
  6. 2
      ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs
  7. 4
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs
  8. 6
      ILSpy/Properties/Resources.resx

27
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs

@ -217,6 +217,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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 @@ -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 @@ -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;
}
}

11
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1810,6 +1810,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 @@ -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;

6
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs

@ -297,10 +297,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -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:

71
ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs

@ -16,6 +16,7 @@ @@ -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 @@ -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 @@ -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 @@ -174,17 +192,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
).CopyAnnotationsFrom(invocationExpression)
);
}
return;
}
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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}

13
ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs

@ -315,7 +315,18 @@ namespace ICSharpCode.Decompiler.IL @@ -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)

2
ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs

@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var callsToFix = new List<Call>();
foreach (var call in function.Descendants.OfType<Call>())
{
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;

4
ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

@ -392,7 +392,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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

6
ILSpy/Properties/Resources.resx

@ -279,6 +279,9 @@ Are you sure you want to continue?</value> @@ -279,6 +279,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.AsyncEnumerator" xml:space="preserve">
<value>Decompile async IAsyncEnumerator methods</value>
</data>
<data name="DecompilerSettings.CheckedOperators" xml:space="preserve">
<value>User-defined checked operators</value>
</data>
<data name="DecompilerSettings.DecompileAnonymousMethodsLambdas" xml:space="preserve">
<value>Decompile anonymous methods/lambdas</value>
</data>
@ -1069,7 +1072,4 @@ Do you want to continue?</value> @@ -1069,7 +1072,4 @@ Do you want to continue?</value>
<data name="_Window" xml:space="preserve">
<value>_Window</value>
</data>
<data name="DecompilerSettings.CheckedOperators" xml:space="preserve">
<value>User-defined checked operators</value>
</data>
</root>
Loading…
Cancel
Save