diff options
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst/StateRange.cs')
-rw-r--r-- | ICSharpCode.Decompiler/ILAst/StateRange.cs | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/StateRange.cs b/ICSharpCode.Decompiler/ILAst/StateRange.cs new file mode 100644 index 00000000..ef27c498 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/StateRange.cs @@ -0,0 +1,312 @@ +// Copyright (c) 2012 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + internal struct Interval + { + public readonly int Start, End; + + public Interval(int start, int end) + { + Debug.Assert(start <= end || (start == 0 && end == -1)); + Start = start; + End = end; + } + + public override string ToString() + { + return string.Format("({0} to {1})", Start, End); + } + } + + internal class StateRange + { + readonly List<Interval> data = new List<Interval>(); + + public StateRange() + { + } + + public StateRange(int start, int end) + { + data.Add(new Interval(start, end)); + } + + public bool IsEmpty { + get { return data.Count == 0; } + } + + public bool Contains(int val) + { + foreach (Interval v in data) { + if (v.Start <= val && val <= v.End) + return true; + } + return false; + } + + public void UnionWith(StateRange other) + { + data.AddRange(other.data); + } + + /// <summary> + /// Unions this state range with (other intersect (minVal to maxVal)) + /// </summary> + public void UnionWith(StateRange other, int minVal, int maxVal) + { + foreach (Interval v in other.data) { + int start = Math.Max(v.Start, minVal); + int end = Math.Min(v.End, maxVal); + if (start <= end) + data.Add(new Interval(start, end)); + } + } + + /// <summary> + /// Merges overlapping interval ranges. + /// </summary> + public void Simplify() + { + if (data.Count < 2) + return; + data.Sort((a, b) => a.Start.CompareTo(b.Start)); + Interval prev = data[0]; + int prevIndex = 0; + for (int i = 1; i < data.Count; i++) { + Interval next = data[i]; + Debug.Assert(prev.Start <= next.Start); + if (next.Start <= prev.End + 1) { // intervals overlapping or touching + prev = new Interval(prev.Start, Math.Max(prev.End, next.End)); + data[prevIndex] = prev; + data[i] = new Interval(0, -1); // mark as deleted + } else { + prev = next; + prevIndex = i; + } + } + data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted + } + + public override string ToString() + { + return string.Join(",", data); + } + + public Interval ToEnclosingInterval() + { + if (data.Count == 0) + throw new SymbolicAnalysisFailedException(); + return new Interval(data[0].Start, data[data.Count - 1].End); + } + } + + internal enum StateRangeAnalysisMode + { + IteratorMoveNext, + IteratorDispose, + AsyncMoveNext + } + + internal class StateRangeAnalysis + { + readonly StateRangeAnalysisMode mode; + readonly FieldDefinition stateField; + internal DefaultDictionary<ILNode, StateRange> ranges; + SymbolicEvaluationContext evalContext; + + internal Dictionary<MethodDefinition, StateRange> finallyMethodToStateRange; // used only for IteratorDispose + + /// <summary> + /// Initializes the state range logic: + /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) + /// </summary> + public StateRangeAnalysis(ILNode entryPoint, StateRangeAnalysisMode mode, FieldDefinition stateField, ILVariable cachedStateVar = null) + { + this.mode = mode; + this.stateField = stateField; + if (mode == StateRangeAnalysisMode.IteratorDispose) { + finallyMethodToStateRange = new Dictionary<MethodDefinition, StateRange>(); + } + + ranges = new DefaultDictionary<ILNode, StateRange>(n => new StateRange()); + ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); + evalContext = new SymbolicEvaluationContext(stateField); + if (cachedStateVar != null) + evalContext.AddStateVariable(cachedStateVar); + } + + public int AssignStateRanges(List<ILNode> body, int bodyLength) + { + if (bodyLength == 0) + return 0; + for (int i = 0; i < bodyLength; i++) { + StateRange nodeRange = ranges[body[i]]; + nodeRange.Simplify(); + + ILLabel label = body[i] as ILLabel; + if (label != null) { + ranges[body[i + 1]].UnionWith(nodeRange); + continue; + } + + ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; + if (tryFinally != null) { + if (mode == StateRangeAnalysisMode.IteratorDispose) { + if (tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) + throw new SymbolicAnalysisFailedException(); + ranges[tryFinally.TryBlock].UnionWith(nodeRange); + if (tryFinally.TryBlock.Body.Count != 0) { + ranges[tryFinally.TryBlock.Body[0]].UnionWith(nodeRange); + AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count); + } + continue; + } else if (mode == StateRangeAnalysisMode.AsyncMoveNext) { + return i; + } else { + throw new SymbolicAnalysisFailedException(); + } + } + + ILExpression expr = body[i] as ILExpression; + if (expr == null) + throw new SymbolicAnalysisFailedException(); + switch (expr.Code) { + case ILCode.Switch: + { + SymbolicValue val = evalContext.Eval(expr.Arguments[0]); + if (val.Type != SymbolicValueType.State) + goto default; + ILLabel[] targetLabels = (ILLabel[])expr.Operand; + for (int j = 0; j < targetLabels.Length; j++) { + int state = j - val.Constant; + ranges[targetLabels[j]].UnionWith(nodeRange, state, state); + } + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); + nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); + break; + } + case ILCode.Br: + case ILCode.Leave: + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); + break; + case ILCode.Brtrue: + { + SymbolicValue val = evalContext.Eval(expr.Arguments[0]).AsBool(); + if (val.Type == SymbolicValueType.StateEquals) { + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + break; + } else if (val.Type == SymbolicValueType.StateInEquals) { + ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange targetRange = ranges[(ILLabel)expr.Operand]; + targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + break; + } else { + goto default; + } + } + case ILCode.Nop: + ranges[body[i + 1]].UnionWith(nodeRange); + break; + case ILCode.Ret: + break; + case ILCode.Stloc: + { + SymbolicValue val = evalContext.Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.State && val.Constant == 0) { + evalContext.AddStateVariable((ILVariable)expr.Operand); + goto case ILCode.Nop; + } else { + goto default; + } + } + case ILCode.Call: + // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks + if (mode == StateRangeAnalysisMode.IteratorDispose) { + MethodDefinition mdef = (expr.Operand as MethodReference).ResolveWithinSameModule(); + if (mdef == null || finallyMethodToStateRange.ContainsKey(mdef)) + throw new SymbolicAnalysisFailedException(); + finallyMethodToStateRange.Add(mdef, nodeRange); + break; + } else { + goto default; + } + default: + if (mode == StateRangeAnalysisMode.IteratorDispose) { + throw new SymbolicAnalysisFailedException(); + } else { + return i; + } + } + } + return bodyLength; + } + + public void EnsureLabelAtPos(List<ILNode> body, ref int pos, ref int bodyLength) + { + if (pos > 0 && body[pos - 1] is ILLabel) { + pos--; + } else { + // ensure that the first element at body[pos] is a label: + ILLabel newLabel = new ILLabel(); + newLabel.Name = "YieldReturnEntryPoint"; + ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos] + body.Insert(pos, newLabel); + bodyLength++; + } + } + + public LabelRangeMapping CreateLabelRangeMapping(List<ILNode> body, int pos, int bodyLength) + { + LabelRangeMapping result = new LabelRangeMapping(); + CreateLabelRangeMapping(body, pos, bodyLength, result, false); + return result; + } + + void CreateLabelRangeMapping(List<ILNode> body, int pos, int bodyLength, LabelRangeMapping result, bool onlyInitialLabels) + { + for (int i = pos; i < bodyLength; i++) { + ILLabel label = body[i] as ILLabel; + if (label != null) { + result.Add(new KeyValuePair<ILLabel, StateRange>(label, ranges[label])); + } else { + ILTryCatchBlock tryCatchBlock = body[i] as ILTryCatchBlock; + if (tryCatchBlock != null) { + CreateLabelRangeMapping(tryCatchBlock.TryBlock.Body, 0, tryCatchBlock.TryBlock.Body.Count, result, true); + } else if (onlyInitialLabels) { + break; + } + } + } + } + } + + internal class LabelRangeMapping : List<KeyValuePair<ILLabel, StateRange>> {} +} |