diff --git a/ICSharpCode.Decompiler/CSharp/Annotations.cs b/ICSharpCode.Decompiler/CSharp/Annotations.cs index 108a30895..8c7cd3281 100644 --- a/ICSharpCode.Decompiler/CSharp/Annotations.cs +++ b/ICSharpCode.Decompiler/CSharp/Annotations.cs @@ -35,6 +35,7 @@ namespace ICSharpCode.Decompiler.CSharp // TODO: actually, we could use the type info instead? // * AddCheckedBlocks.CheckedAnnotation / AddCheckedBlocks.UnCheckedAnnotation is used on checked/unchecked integer arithmetic // TODO: here the info is also redundant, we could peek at the BinaryNumericInstruction instead + // but on the other hand, some unchecked casts are not backed by any BinaryNumericInstruction /// /// Currently unused; we'll probably use the LdToken ILInstruction as annotation instead when LdToken support gets reimplemented. diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index d0c07a676..d51d43c75 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System.Diagnostics; +using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.NRefactory.CSharp.TypeSystem; using ExpressionType = System.Linq.Expressions.ExpressionType; using ICSharpCode.NRefactory.CSharp.Refactoring; @@ -68,6 +69,17 @@ namespace ICSharpCode.Decompiler.CSharp public ExpressionWithResolveResult ConvertConstantValue(ResolveResult rr) { var expr = astBuilder.ConvertConstantValue(rr); + var pe = expr as PrimitiveExpression; + if (pe != null) { + if (pe.Value is sbyte) + expr = expr.CastTo(new NRefactory.CSharp.PrimitiveType("sbyte")); + else if (pe.Value is byte) + expr = expr.CastTo(new NRefactory.CSharp.PrimitiveType("byte")); + else if (pe.Value is short) + expr = expr.CastTo(new NRefactory.CSharp.PrimitiveType("short")); + else if (pe.Value is ushort) + expr = expr.CastTo(new NRefactory.CSharp.PrimitiveType("ushort")); + } var exprRR = expr.Annotation(); if (exprRR == null) { exprRR = rr; @@ -510,9 +522,13 @@ namespace ICSharpCode.Decompiler.CSharp } var targetType = compilation.FindType(inst.TargetType.ToKnownTypeCode()); var rr = resolver.WithCheckForOverflow(inst.CheckForOverflow).ResolveCast(targetType, arg.ResolveResult); - return new CastExpression(ConvertType(targetType), arg.Expression) - .WithILInstruction(inst) - .WithRR(rr); + Expression castExpr = new CastExpression(ConvertType(targetType), arg.Expression); + if (inst.Kind == ConversionKind.Nop || inst.Kind == ConversionKind.Truncate || inst.Kind == ConversionKind.FloatToInt + || (inst.Kind == ConversionKind.ZeroExtend && arg.Type.GetSign() == Sign.Signed)) + { + castExpr.AddAnnotation(inst.CheckForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); + } + return castExpr.WithILInstruction(inst).WithRR(rr); } protected internal override TranslatedExpression VisitCall(Call inst) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs b/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs index edd7fdf94..7d03fd979 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs @@ -48,16 +48,16 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms (where goal 1 has the highest priority) Goal 4a (open checked blocks as late as possible) is necessary so that we don't move variable declarations - into checked blocks, as the variable might still be used after the checked block. - (this could cause DeclareVariables to omit the variable declaration, producing incorrect code) + into checked blocks, as the variable might still be used after the checked block. + (this could cause DeclareVariables to omit the variable declaration, producing incorrect code) Goal 4b (close checked blocks as late as possible) makes the code look nicer in this case: - checked { - int c = a + b; + checked { + int c = a + b; int r = a + c; - return r; - } + return r; + } If the checked block was closed as early as possible, the variable r would have to be declared outside - (this would work, but look badly) + (this would work, but look badly) */ #region struct Cost @@ -302,6 +302,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms costUncheckedContextCheckedBlockOpen += stmtResult.CostInCheckedContext; nodesUncheckedContextCheckedBlockOpen += stmtResult.NodesToInsertInCheckedContext; + if (statement is LabelStatement) { + // We can't move labels into blocks because that might cause goto-statements + // to be unable to just to the labels. + costCheckedContextUncheckedBlockOpen = Cost.Infinite; + costUncheckedContextCheckedBlockOpen = Cost.Infinite; + } + statement = statement.GetNextStatement(); } diff --git a/ICSharpCode.Decompiler/IL/Instructions/Conv.cs b/ICSharpCode.Decompiler/IL/Instructions/Conv.cs index 37ece55d6..4550ae6a0 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Conv.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Conv.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Diagnostics; namespace ICSharpCode.Decompiler.IL { @@ -41,7 +42,7 @@ namespace ICSharpCode.Decompiler.IL IntToFloat, /// /// Float-to-integer conversion. - /// (truncates toward zero) + /// Truncates toward zero; may perform overflow-checking. /// FloatToInt, /// @@ -56,6 +57,7 @@ namespace ICSharpCode.Decompiler.IL SignExtend, /// /// Conversion of integer type to larger unsigned integer type. + /// May involve overflow checking (when converting from a small signed type). /// ZeroExtend, /// @@ -110,6 +112,7 @@ namespace ICSharpCode.Decompiler.IL this.CheckForOverflow = checkForOverflow; this.Sign = sign; this.Kind = GetConversionKind(targetType, argument.ResultType); + Debug.Assert(this.Kind != ConversionKind.Invalid); } /// @@ -220,6 +223,9 @@ namespace ICSharpCode.Decompiler.IL case ConversionKind.ZeroExtend: output.Write(""); break; + case ConversionKind.Invalid: + output.Write(""); + break; } output.Write('('); Argument.WriteTo(output); diff --git a/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs index e245c1092..92a023e44 100644 --- a/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Transforms; @@ -26,12 +27,17 @@ namespace ICSharpCode.Decompiler.Tests.Helpers { public static CompilerResults CompileCSharp(string sourceFileName, CompilerOptions flags = CompilerOptions.UseDebug) { + List sourceFileNames = new List { sourceFileName }; + foreach (Match match in Regex.Matches(File.ReadAllText(sourceFileName), @"#include ""([\w\d./]+)""")) { + sourceFileNames.Add(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(sourceFileName), match.Groups[1].Value))); + } + CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary { { "CompilerVersion", "v4.0" } }); CompilerParameters options = new CompilerParameters(); options.GenerateExecutable = true; options.CompilerOptions = "/unsafe /o" + (flags.HasFlag(CompilerOptions.Optimize) ? "+" : "-") + (flags.HasFlag(CompilerOptions.UseDebug) ? " /debug" : ""); options.ReferencedAssemblies.Add("System.Core.dll"); - CompilerResults results = provider.CompileAssemblyFromFile(options, sourceFileName); + CompilerResults results = provider.CompileAssemblyFromFile(options, sourceFileNames.ToArray()); if (results.Errors.Cast().Any(e => !e.IsWarning)) { StringBuilder b = new StringBuilder("Compiler error:"); foreach (var error in results.Errors) { diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index 891c64fa6..69365d5d6 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -117,6 +117,7 @@ + diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Comparisons.cs b/ICSharpCode.Decompiler/Tests/TestCases/Comparisons.cs index 784ac66bc..f0d7d5d49 100644 --- a/ICSharpCode.Decompiler/Tests/TestCases/Comparisons.cs +++ b/ICSharpCode.Decompiler/Tests/TestCases/Comparisons.cs @@ -18,7 +18,7 @@ using System; -//#pragma warning disable 652 +#pragma warning disable 652 namespace ICSharpCode.Decompiler.Tests.TestCases { diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Conversions.cs b/ICSharpCode.Decompiler/Tests/TestCases/Conversions.cs new file mode 100644 index 000000000..be4ac26f5 --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/TestCases/Conversions.cs @@ -0,0 +1,137 @@ +// Copyright (c) 2016 Daniel Grunwald +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// #include "../../../NRefactory/ICSharpCode.NRefactory/Utils/CSharpPrimitiveCast.cs" + +using System; +using ICSharpCode.NRefactory.Utils; + +namespace ICSharpCode.Decompiler.Tests.TestCases +{ + public class Conversions + { + static readonly TypeCode[] targetTypes = { + TypeCode.Char, + TypeCode.SByte, + TypeCode.Byte, + TypeCode.Int16, + TypeCode.UInt16, + TypeCode.Int32, + TypeCode.UInt32, + TypeCode.Int64, + TypeCode.UInt64, + TypeCode.Single, + TypeCode.Double, + //TypeCode.Decimal + }; + + static object[] inputValues = { + '\0', + 'a', + '\uFFFE', + + sbyte.MinValue, + sbyte.MaxValue, + (sbyte)-1, + (sbyte)1, + + byte.MinValue, + byte.MaxValue, + (byte)1, + + short.MinValue, + short.MaxValue, + (short)-1, + (short)1, + + ushort.MinValue, + ushort.MaxValue, + (ushort)1, + + int.MinValue, + int.MaxValue, + (int)-1, + (int)1, + + uint.MinValue, + uint.MaxValue, + (uint)1, + + long.MinValue, + long.MaxValue, + (long)-1, + (long)1, + + ulong.MinValue, + ulong.MaxValue, + (ulong)1, + + -1.1f, + 1.1f, + float.MinValue, + float.MaxValue, + float.NegativeInfinity, + float.PositiveInfinity, + float.NaN, + + -1.1, + 1.1, + double.MinValue, + double.MaxValue, + double.NegativeInfinity, + double.PositiveInfinity, + double.NaN, + + decimal.MinValue, + decimal.MaxValue, + decimal.MinusOne, + decimal.One + }; + + static void Main(string[] args) + { + RunTest(checkForOverflow: false); + RunTest(checkForOverflow: true); + } + + static void RunTest(bool checkForOverflow) + { + string mode = checkForOverflow ? "checked" : "unchecked"; + foreach (object input in inputValues) { + string inputType = input.GetType().Name; + foreach (var targetType in targetTypes) { + try { + object result = CSharpPrimitiveCast.Cast(targetType, input, checkForOverflow); + Console.WriteLine("{0} ({1})({2}){3} = ({4}){5}", mode, targetType, inputType, input, + result.GetType().Name, result); + } catch (Exception ex) { + Console.WriteLine("{0} ({1})({2}){3} = {4}", mode, targetType, inputType, input, + ex.GetType().Name); + } + } + } + } + + static object MM(sbyte c) + { + checked { + return (UInt64)c; + } + } + } +} diff --git a/ICSharpCode.Decompiler/Tests/TestRunner.cs b/ICSharpCode.Decompiler/Tests/TestRunner.cs index c5d8a119e..036c92723 100644 --- a/ICSharpCode.Decompiler/Tests/TestRunner.cs +++ b/ICSharpCode.Decompiler/Tests/TestRunner.cs @@ -34,6 +34,12 @@ namespace ICSharpCode.Decompiler.Tests TestCompileDecompileCompileOutputAll("Comparisons.cs"); } + [Test] + public void Conversions() + { + TestCompileDecompileCompileOutputAll("Conversions.cs"); + } + [Test] public void HelloWorld() { @@ -111,6 +117,18 @@ namespace ICSharpCode.Decompiler.Tests if (result1 != result2 || output1 != output2 || error1 != error2) { Console.WriteLine("Test {0} failed.", testFileName); Console.WriteLine("Decompiled code in {0}:line 1", decompiledCodeFile); + string outputFileName = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(testFileName)); + File.WriteAllText(outputFileName + ".original.out", output1); + File.WriteAllText(outputFileName + ".decompiled.out", output2); + int diffLine = 0; + foreach (var pair in output1.Split('\n').Zip(output2.Split('\n'), Tuple.Create)) { + diffLine++; + if (pair.Item1 != pair.Item2) { + break; + } + } + Console.WriteLine("Output: {0}.original.out:line {1}", outputFileName, diffLine); + Console.WriteLine("Output: {0}.decompiled.out:line {1}", outputFileName, diffLine); } Assert.AreEqual(result1, result2);