mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
10 KiB
321 lines
10 KiB
// 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 |
|
{ |
|
struct Interval |
|
{ |
|
public readonly int Start, End; |
|
|
|
public Interval(int start, int end) |
|
{ |
|
Debug.Assert(start <= end || (start == 0 && end == -1)); |
|
this.Start = start; |
|
this.End = end; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return string.Format("({0} to {1})", Start, End); |
|
} |
|
} |
|
|
|
class StateRange |
|
{ |
|
readonly List<Interval> data = new List<Interval>(); |
|
|
|
public StateRange() |
|
{ |
|
} |
|
|
|
public StateRange(int start, int end) |
|
{ |
|
this.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); |
|
} |
|
} |
|
|
|
enum StateRangeAnalysisMode |
|
{ |
|
IteratorMoveNext, |
|
IteratorDispose, |
|
AsyncMoveNext |
|
} |
|
|
|
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--; |
|
return; // label found |
|
} |
|
|
|
// ensure that the first element at body[pos] is a label: |
|
ILLabel newLabel = new ILLabel(); |
|
newLabel.Name = "YieldReturnEntryPoint"; |
|
|
|
ILExpression expr = pos == 1 && body.Count == 1 ? body[0] as ILExpression : null; |
|
if (expr != null && expr.Code == ILCode.Leave && expr.Operand is ILLabel) { |
|
ranges[newLabel] = ranges[(ILLabel)expr.Operand]; |
|
pos = 0; |
|
} else { |
|
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; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
class LabelRangeMapping : List<KeyValuePair<ILLabel, StateRange>> {} |
|
}
|
|
|