diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs new file mode 100644 index 0000000..5db791b --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs @@ -0,0 +1,400 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// 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. + +using System; +using System.Reflection; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class TypeAnalysisTests + { + private class @_ + { + } + + private byte[] byteArray; + private ulong uint64Field; + + public int Issue1796a { + get { + return (int)(uint64Field & 0x7FFFFFFF); + } + set { + uint64Field = (uint64Field & 0xFFFFFFFF80000000uL) | (ulong)value; + } + } + + public ushort Issue1796b { + get { + return (ushort)((uint64Field & 0xFFFF000000000000uL) >> 48); + } + set { + uint64Field = (uint64Field & 0xFFFFFFFFFFFFL) | ((ulong)value << 48); + } + } + + public byte SubtractFrom256(byte b) + { + return (byte)(256 - b); + } + + #region Shift + public int LShiftInteger(int num1, int num2) + { + return num1 << num2; + } + + public uint LShiftUnsignedInteger(uint num1, uint num2) + { + return num1 << (int)num2; + } + + public long LShiftLong(long num1, long num2) + { + return num1 << (int)num2; + } + + public ulong LShiftUnsignedLong(ulong num1, ulong num2) + { + return num1 << (int)num2; + } + + public int RShiftInteger(int num1, int num2) + { + return num1 >> num2; + } + + public uint RShiftUnsignedInteger(uint num1, int num2) + { + return num1 >> num2; + } + + public long RShiftLong(long num1, long num2) + { + return num1 >> (int)num2; + } + + public ulong RShiftUnsignedLong(ulong num1, ulong num2) + { + return num1 >> (int)num2; + } + + public int ShiftByte(byte num) + { + return num << 8; + } + + public int RShiftByte(byte num) + { + return num >> 8; + } + + public uint RShiftByteWithZeroExtension(byte num) + { + // zero extend -> cast to unsigned -> unsigned shift + return (uint)num >> 8; + } + + public int RShiftByteWithZeroExtensionReturnAsSigned(byte num) + { +#if CS110 + // zero extend -> unsigned shift + return num >>> 8; +#else + // zero extend -> cast to unsigned -> unsigned shift -> cast to signed + return (int)((uint)num >> 8); +#endif + } + + public int RShiftByteAsSByte(byte num) + { + return (sbyte)num >> 8; + } + + public int RShiftSByte(sbyte num) + { + return num >> 8; + } + + public uint RShiftSByteWithZeroExtension(sbyte num) + { + return (uint)((byte)num >> 4); + } + + public uint RShiftSByteWithSignExtension(sbyte num) + { + // sign extend -> cast to unsigned -> unsigned shift + return (uint)num >> 4; + } + + public int RShiftSByteWithSignExtensionReturnAsSigned(sbyte num) + { +#if CS110 + // sign extend -> unsigned shift + return num >>> 4; +#else + // sign extend -> cast to unsigned -> unsigned shift -> cast to signed + return (int)((uint)num >> 4); +#endif + } + public int RShiftSByteAsByte(sbyte num) + { + return (byte)num >> 8; + } + #endregion + + public int GetHashCode(long num) + { + return (int)num ^ (int)(num >> 32); + } + + public void TernaryOp(Random a, Random b, bool c) + { + if ((c ? a : b) == null) + { + Console.WriteLine(); + } + } + + public void OperatorIs(object o) + { + Console.WriteLine(o is Random); + Console.WriteLine(!(o is Random)); + // If we didn't escape the '_' identifier here, this would look like a discard pattern + Console.WriteLine(o is @_); + } + + public byte[] CreateArrayWithInt(int length) + { + return new byte[length]; + } + + public byte[] CreateArrayWithLong(long length) + { + return new byte[length]; + } + + public byte[] CreateArrayWithUInt(uint length) + { + return new byte[length]; + } + + public byte[] CreateArrayWithULong(ulong length) + { + return new byte[length]; + } + + public byte[] CreateArrayWithShort(short length) + { + return new byte[length]; + } + + public byte[] CreateArrayWithUShort(ushort length) + { + return new byte[length]; + } + + public byte UseArrayWithInt(int i) + { + return byteArray[i]; + } + + public byte UseArrayWithUInt(uint i) + { + return byteArray[i]; + } + + public byte UseArrayWithLong(long i) + { + return byteArray[i]; + } + + public byte UseArrayWithULong(ulong i) + { + return byteArray[i]; + } + + public byte UseArrayWithShort(short i) + { + return byteArray[i]; + } + + public byte UseArrayWithUShort(ushort i) + { + return byteArray[i]; + } + + public byte UseArrayWithCastToUShort(int i) + { + // Unchecked cast = truncate to 16 bits + return byteArray[(ushort)i]; + } + + public void EnumSubtractionWithHighBitValues(UIntEnum value) + { + if (value - UIntEnum.Val1 <= 4) + { + Console.WriteLine(value - UIntEnum.Val1); + } + } + + public void EnumSubtractionWithHighBitValues16(UShortEnum value) + { + // A 16-bit enum member always fits its underlying type as a positive constant, + // so the compiler emits it as such and enum-minus-underlying resolves right away. + if ((int)(value - 65035) <= 4) + { + Console.WriteLine((int)(value - 65035)); + } + } + + public void EnumSubtractionWithHighBitValues64(ULongEnum value) + { + if (value - ULongEnum.Val1 <= 4) + { + Console.WriteLine(value - ULongEnum.Val1); + } + } + + public void EnumSubtractionWithZeroExtendedValue(ULongEnum value) + { + // Val3 is emitted as a zero-extended uint constant (ldc.i4.m1 + conv.u8), so it + // must not be reinterpreted as the sign-extended long -1. + Console.WriteLine((ulong)(value - uint.MaxValue)); + } + + public StringComparison EnumDiffNumber(StringComparison data) + { + return data - 1; + } + + public int EnumDiff(StringComparison a, StringComparison b) + { + return Math.Abs(a - b); + } + + public bool CompareDelegatesByValue(Action a, Action b) + { + return a == b; + } + + public bool CompareDelegatesByReference(Action a, Action b) + { + return (object)a == b; + } + + public bool CompareDelegateWithNull(Action a) + { + return a == null; + } + + public bool CompareStringsByValue(string a, string b) + { + return a == b; + } + + public bool CompareStringsByReference(string a, string b) + { + return (object)a == b; + } + + public bool CompareStringWithNull(string a) + { + return a == null; + } + + public bool CompareType(Type a, Type b) + { + return a == b; + } + + public bool CompareTypeByReference(Type a, Type b) + { + return (object)a == b; + } + + public bool CompareTypeWithNull(Type t) + { + return t == null; + } + + public Attribute CallExtensionMethodViaBaseClass(Type type) + { + return type.GetCustomAttribute(); + } + + public decimal ImplicitConversionToDecimal(byte v) + { + return v; + } + + public decimal ImplicitConversionToDecimal(ulong v) + { + return v; + } + + public bool EnumInConditionalOperator(bool b) + { + return string.Equals("", "", b ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + } + + public bool MethodCallOnEnumConstant() + { + return AttributeTargets.All.HasFlag(AttributeTargets.Assembly); + } + + public static string ImpossibleCast1(int i) + { + return (string)(object)i; + } + + public static string ImpossibleCast2(Action a) + { + return (string)(object)a; + } + + public static bool CompareLast32Bits(long a, long b) + { + return (int)a == (int)b; + } + + public static bool Last32BitsAreZero(long a) + { + return (int)a == 0; + } + } + + public enum UIntEnum : uint + { + Val1 = 4294966795u, + Val2 = 4294966799u + } + + public enum ULongEnum : ulong + { + Val1 = 18446744073709551115uL, + Val2 = 18446744073709551119uL, + Val3 = 4294967295uL + } + + public enum UShortEnum : ushort + { + Val1 = 65035, + Val2 = 65039 + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs new file mode 100644 index 0000000..e6d44b0 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs @@ -0,0 +1,208 @@ +// Copyright (c) 2011-2015 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. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Runs a very simple form of copy propagation. + /// Copy propagation is used in two cases: + /// 1) assignments from arguments to local variables + /// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg), + /// then we can replace the variable with the argument. + /// 2) assignments of address-loading instructions to local variables + /// + public class CopyPropagation : IILTransform + { + public static void Propagate(StLoc store, ILTransformContext context) + { + Debug.Assert(store.Variable.IsSingleDefinition); + Block block = (Block)store.Parent; + int i = store.ChildIndex; + DoPropagate(store.Variable, store.Value, block, ref i, context); + } + + public void Run(ILFunction function, ILTransformContext context) + { + var splitVariables = new HashSet(ILVariableEqualityComparer.Instance); + foreach (var g in function.Variables.GroupBy(v => v, ILVariableEqualityComparer.Instance)) + { + if (g.Count() > 1) + { + splitVariables.Add(g.Key); + } + } + foreach (var block in function.Descendants.OfType()) + { + if (block.Kind != BlockKind.ControlFlow) + continue; + RunOnBlock(block, context, splitVariables); + } + } + + static void RunOnBlock(Block block, ILTransformContext context, HashSet splitVariables = null) + { + for (int i = 0; i < block.Instructions.Count; i++) + { + if (block.Instructions[i].MatchStLoc(out ILVariable v, out ILInstruction copiedExpr)) + { + if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) + { + // dead store to stack + if (SemanticHelper.IsPure(copiedExpr.Flags)) + { + // no-op -> delete + context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); + block.Instructions.RemoveAt(i); + // This can open up new inlining opportunities: + int c = ILInlining.InlineInto(block, i, InliningOptions.None, context: context); + i -= c + 1; + } + else + { + // evaluate the value for its side-effects + context.Step("remove dead store to stack: evaluate the value for its side-effects", block.Instructions[i]); + copiedExpr.AddILRange(block.Instructions[i]); + block.Instructions[i] = copiedExpr; + context.EndStep(copiedExpr); + } + } + else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr, splitVariables, context.Settings)) + { + DoPropagate(v, copiedExpr, block, ref i, context); + } + } + } + } + + static bool CanPerformCopyPropagation(ILVariable target, ILInstruction value, HashSet splitVariables, DecompilerSettings settings) + { + Debug.Assert(target.StackType == value.ResultType); + if (target.Type.IsSmallIntegerType()) + return false; + if (splitVariables != null && splitVariables.Contains(target)) + { + return false; // non-local code move might change semantics when there's split variables + } + switch (value.OpCode) + { + case OpCode.LdLoca: + case OpCode.LdsFlda: + // All address-loading instructions always return the same value for a given operand/argument combination, + // so they can be safely copied. + // ... except for LdElema and LdFlda, because those might throw an exception, and we don't want to + // change the place where the exception is thrown. + return true; + case OpCode.LdElema: + case OpCode.LdFlda: + return !settings.UseRefLocalsForAccurateOrderOfEvaluation; + case OpCode.LdLoc: + var v = ((LdLoc)value).Variable; + if (splitVariables != null && splitVariables.Contains(v)) + { + return false; // non-local code move might change semantics when there's split variables + } + switch (v.Kind) + { + case VariableKind.Parameter: + // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) + // note: the initialization by the caller is the first store -> StoreCount must be 1 + return v.IsSingleDefinition; + default: + // Variables can be copied if both are single-definition. + // To avoid removing too many variables, we do this only if the target + // is either a stackslot or a ref local. + Debug.Assert(target.IsSingleDefinition); + return v.IsSingleDefinition && (target.Kind == VariableKind.StackSlot || target.StackType == StackType.Ref); + } + default: + // All instructions without special behavior that target a stack-variable can be copied. + return value.Flags == InstructionFlags.None && value.Children.Count == 0 && target.Kind == VariableKind.StackSlot; + } + } + + static void DoPropagate(ILVariable v, ILInstruction copiedExpr, Block block, ref int i, ILTransformContext context) + { + context.Step($"Copy propagate {v.Name}", copiedExpr); + int firstUninlinedArg = i; + // un-inline the arguments of the ldArg instruction + ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Children.Count]; + for (int j = 0; j < uninlinedArgs.Length; j++) + { + var arg = copiedExpr.Children[j]; + var type = arg.InferType(context.TypeSystem); + uninlinedArgs[j] = new ILVariable(VariableKind.StackSlot, type, arg.ResultType) { + Name = "C_" + arg.StartILOffset, + HasGeneratedName = true, + }; + block.Instructions.Insert(i++, new StLoc(uninlinedArgs[j], arg)); + } + v.Function.Variables.AddRange(uninlinedArgs); + // perform copy propagation: + foreach (var expr in v.LoadInstructions.ToArray()) + { + var clone = copiedExpr.Clone(); + for (int j = 0; j < uninlinedArgs.Length; j++) + { + clone.Children[j].ReplaceWith(new LdLoc(uninlinedArgs[j])); + } + // We are copying an expression from far away, reusing the ILRange would result in incorrect sequence points. + clone.SetILRange(new Interval()); + if (expr.SlotInfo == StObj.TargetSlot && clone.HasDirectFlag(InstructionFlags.MayThrow) + && !expr.Parent.SatisfiesSlotRestrictionForInlining(expr.ChildIndex, clone)) + { + // A LdFlda/LdElema used as StObj target has to delay its exception, because C# + // computes the value to be stored before dereferencing the target. Accept the + // changed point at which the exception is thrown, mirroring + // InliningOptions.AllowChangingOrderOfEvaluationForExceptions in ILInlining. + // Unlike inlining, copy propagation has no third option of just giving up: the + // defining store is gone by the end of this loop, so every load must be replaced. + // Refusing the copy is only possible before propagation starts, which is what + // CanPerformCopyPropagation does when ref locals are requested; the assert + // guards the public Propagate() entry point, which bypasses that check. + Debug.Assert(!context.Settings.UseRefLocalsForAccurateOrderOfEvaluation); + if (clone is LdFlda ldflda) + ldflda.DelayExceptions = true; + else if (clone is LdElema ldelema) + ldelema.DelayExceptions = true; + } + expr.ReplaceWith(clone); + } + block.Instructions.RemoveAt(i); + int c = ILInlining.InlineInto(block, i, InliningOptions.None, context: context); + i -= c + 1; + if (uninlinedArgs.Length > 0) + { + // The stores holding the un-inlined arguments are themselves copy-propagation + // candidates: copying an ldflda leaves its target address behind in a stack slot, + // and if that address is e.g. an ldloca it can be copied as well. They sit before + // the current position, so rewind far enough for the caller's loop to visit them; + // otherwise a multiply-loaded stack slot survives as a spurious ref local. + i = Math.Min(i, firstUninlinedArg - 1); + } + } + } +} +