diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs index d86411a14..6936bb485 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs @@ -134,25 +134,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow return new LongSet(exitIntervals); case IfInstruction ifInst: val = evalContext.Eval(ifInst.Condition).AsBool(); - LongSet trueRanges; - if (val.Type == SymbolicValueType.StateEquals) { - trueRanges = new LongSet(val.Constant); - } else if (val.Type == SymbolicValueType.StateInEquals) { - trueRanges = new LongSet(val.Constant).Invert(); - } else if (val.Type == SymbolicValueType.StateLessThan) { - // note: val.Constant is of type int, so it can't be equal to long.MinValue, - // which would cause problems. - trueRanges = new LongSet(new LongInterval(long.MinValue, val.Constant)); - } else if (val.Type == SymbolicValueType.StateLessEqual) { - trueRanges = new LongSet(LongInterval.Inclusive(long.MinValue, val.Constant)); - } else if (val.Type == SymbolicValueType.StateGreaterThan) { - // note: val.Constant is of type int, so the addition can't overflow. - trueRanges = new LongSet(LongInterval.Inclusive(val.Constant + 1L, long.MaxValue)); - } else if (val.Type == SymbolicValueType.StateGreaterEqual) { - trueRanges = new LongSet(LongInterval.Inclusive(val.Constant, long.MaxValue)); - } else { + if (val.Type != SymbolicValueType.StateInSet) { goto default; } + LongSet trueRanges = val.ValueSet; var afterTrue = AssignStateRanges(ifInst.TrueInst, stateRange.IntersectWith(trueRanges)); var afterFalse = AssignStateRanges(ifInst.FalseInst, stateRange.ExceptWith(trueRanges)); return afterTrue.UnionWith(afterFalse); diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs index fa4bf58ca..cf437b0be 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; using System; using System.Collections.Generic; using System.Linq; @@ -54,35 +55,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// This, /// - /// bool: State == Constant + /// bool: ValueSet.Contains(State) /// - StateEquals, - /// - /// bool: State != Constant - /// - StateInEquals, - /// - /// bool: State < Constant - /// - StateLessThan, - /// - /// bool: State <= Constant - /// - StateLessEqual, - /// - /// bool: State > Constant - /// - StateGreaterThan, - /// - /// bool: State >= Constant - /// - StateGreaterEqual + StateInSet, } struct SymbolicValue { public readonly int Constant; public readonly SymbolicValueType Type; + public readonly LongSet ValueSet; public SymbolicValue(SymbolicValueType type, int constant = 0) { @@ -90,12 +72,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow this.Constant = constant; } + public SymbolicValue(SymbolicValueType type, LongSet valueSet) + { + this.Type = type; + this.Constant = 0; + this.ValueSet = valueSet; + } + public SymbolicValue AsBool() { if (Type == SymbolicValueType.State) { // convert state integer to bool: // if (state + c) = if (state + c != 0) = if (state != -c) - return new SymbolicValue(SymbolicValueType.StateInEquals, unchecked(-Constant)); + return new SymbolicValue(SymbolicValueType.StateInSet, new LongSet(unchecked(-Constant)).Invert()); } return this; } @@ -154,39 +143,42 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant) return Failed; // bool: (state + left.Constant == right.Constant) - // bool: (state == right.Constant - left.Constant) - if (comp.Kind == ComparisonKind.Equality) - return new SymbolicValue(SymbolicValueType.StateEquals, unchecked(right.Constant - left.Constant)); - else if (comp.Kind == ComparisonKind.Inequality) - return new SymbolicValue(SymbolicValueType.StateInEquals, unchecked(right.Constant - left.Constant)); - else if (comp.Kind == ComparisonKind.LessThan && left.Constant == 0) - return new SymbolicValue(SymbolicValueType.StateLessThan, right.Constant); - else if (comp.Kind == ComparisonKind.LessThanOrEqual && left.Constant == 0) - return new SymbolicValue(SymbolicValueType.StateLessEqual, right.Constant); - else if (comp.Kind == ComparisonKind.GreaterThan && left.Constant == 0) - return new SymbolicValue(SymbolicValueType.StateGreaterThan, right.Constant); - else if (comp.Kind == ComparisonKind.GreaterThanOrEqual && left.Constant == 0) - return new SymbolicValue(SymbolicValueType.StateGreaterEqual, right.Constant); - else - return Failed; - } else if (inst is LogicNot logicNot) { - SymbolicValue val = Eval(logicNot.Argument).AsBool(); - switch (val.Type) { - case SymbolicValueType.StateEquals: - return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant); - case SymbolicValueType.StateInEquals: - return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant); - case SymbolicValueType.StateLessThan: - return new SymbolicValue(SymbolicValueType.StateGreaterEqual, val.Constant); - case SymbolicValueType.StateLessEqual: - return new SymbolicValue(SymbolicValueType.StateGreaterThan, val.Constant); - case SymbolicValueType.StateGreaterThan: - return new SymbolicValue(SymbolicValueType.StateLessEqual, val.Constant); - case SymbolicValueType.StateGreaterEqual: - return new SymbolicValue(SymbolicValueType.StateLessThan, val.Constant); + LongSet trueSums; // evals to true if trueSums.Contains(state + left.Constant) + switch (comp.Kind) { + case ComparisonKind.Equality: + trueSums = new LongSet(right.Constant); + break; + case ComparisonKind.Inequality: + trueSums = new LongSet(right.Constant).Invert(); + break; + case ComparisonKind.LessThan: + // note: right.Constant is of type int, so it can't be equal to long.MinValue, + // which would cause problems. + trueSums = new LongSet(new LongInterval(long.MinValue, right.Constant)); + break; + case ComparisonKind.LessThanOrEqual: + trueSums = new LongSet(LongInterval.Inclusive(long.MinValue, right.Constant)); + break; + case ComparisonKind.GreaterThan: + // note: val.Constant is of type int, so the addition can't overflow. + trueSums = new LongSet(LongInterval.Inclusive(right.Constant + 1L, long.MaxValue)); + break; + case ComparisonKind.GreaterThanOrEqual: + trueSums = new LongSet(LongInterval.Inclusive(right.Constant, long.MaxValue)); + break; default: return Failed; } + LongSet trueStates = trueSums.AddOffset(unchecked(-left.Constant)); + // evals to true if trueStates.Contains(state) + return new SymbolicValue(SymbolicValueType.StateInSet, trueStates); + } else if (inst is LogicNot logicNot) { + SymbolicValue val = Eval(logicNot.Argument).AsBool(); + if (val.Type == SymbolicValueType.StateInSet) { + return new SymbolicValue(SymbolicValueType.StateInSet, val.ValueSet.Invert()); + } else { + return Failed; + } } else { return Failed; } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index 2ac6782a5..b29457faf 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -409,16 +409,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow #region Analyze MoveNext() and generate new body BlockContainer AnalyzeMoveNext() { + context.Stepper.StartGroup("AnalyzeMoveNext"); MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); ILFunction moveNextFunction = CreateILAst(moveNextMethod); // Copy-propagate temporaries holding a copy of 'this'. // This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables. - context.Stepper.StartGroup("AnalyzeMoveNext"); foreach (var stloc in moveNextFunction.Descendants.OfType().Where(s => s.Variable.IsSingleDefinition && s.Value.MatchLdThis()).ToList()) { CopyPropagation.Propagate(stloc, context); } - context.Stepper.EndGroup(); var body = (BlockContainer)moveNextFunction.Body; if (body.Blocks.Count == 1 && body.Blocks[0].Instructions.Count == 1 && body.Blocks[0].Instructions[0] is TryFault tryFault) { @@ -437,6 +436,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } } + PropagateCopiesOfFields(body); + // Note: body may contain try-catch or try-finally statements that have nested block containers, // but those cannot contain any yield statements. // So for reconstructing the control flow, we only consider the blocks directly within body. @@ -448,9 +449,41 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow moveNextFunction.Variables.Clear(); // release references from old moveNextFunction to instructions that were moved over to newBody moveNextFunction.ReleaseRef(); + context.Stepper.EndGroup(); return newBody; } + private void PropagateCopiesOfFields(BlockContainer body) + { + // Roslyn may optimize MoveNext() by copying fields from the iterator class into local variables + // at the beginning of MoveNext(). Undo this optimization. + context.Stepper.StartGroup("PropagateCopiesOfFields"); + var mutableFields = body.Descendants.OfType().Where(ldflda => ldflda.Parent.OpCode != OpCode.LdObj).Select(ldflda => ldflda.Field).ToHashSet(); + for (int i = 0; i < body.EntryPoint.Instructions.Count; i++) { + if (body.EntryPoint.Instructions[i] is StLoc store + && store.Variable.IsSingleDefinition + && store.Value is LdObj ldobj + && ldobj.Target is LdFlda ldflda + && ldflda.Target.MatchLdThis()) + { + if (!mutableFields.Contains(ldflda.Field)) { + // perform copy propagation: (unlike CopyPropagation.Propagate(), copy the ldobj arguments as well) + foreach (var expr in store.Variable.LoadInstructions.ToArray()) { + expr.ReplaceWith(store.Value.Clone()); + } + body.EntryPoint.Instructions.RemoveAt(i--); + } else if (ldflda.Field.MemberDefinition == stateField.MemberDefinition) { + continue; + } else { + break; // unsupported: load of mutable field (other than state field) + } + } else { + break; // unknown instruction + } + } + context.Stepper.EndGroup(); + } + /// /// Convert the old body (of MoveNext function) to the new body (of decompiled iterator method). /// diff --git a/ICSharpCode.Decompiler/Util/Interval.cs b/ICSharpCode.Decompiler/Util/Interval.cs index 7c8abddae..6f92b6fd3 100644 --- a/ICSharpCode.Decompiler/Util/Interval.cs +++ b/ICSharpCode.Decompiler/Util/Interval.cs @@ -259,10 +259,16 @@ namespace ICSharpCode.Decompiler.Util public override string ToString() { - if (End == long.MinValue) - return string.Format("[{0}..long.MaxValue]", Start); - else + if (End == long.MinValue) { + if (Start == long.MinValue) + return string.Format("[long.MinValue..long.MaxValue]", End); + else + return string.Format("[{0}..long.MaxValue]", Start); + } else if (Start == long.MinValue) { + return string.Format("[long.MinValue..{0})", End); + } else { return string.Format("[{0}..{1})", Start, End); + } } #region Equals and GetHashCode implementation