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.
203 lines
8.8 KiB
203 lines
8.8 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 ICSharpCode.Decompiler.TypeSystem; |
|
using ICSharpCode.Decompiler.Util; |
|
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Threading.Tasks; |
|
|
|
namespace ICSharpCode.Decompiler.IL.ControlFlow |
|
{ |
|
enum StateRangeAnalysisMode |
|
{ |
|
IteratorMoveNext, |
|
IteratorDispose, |
|
AsyncMoveNext |
|
} |
|
|
|
/// <summary> |
|
/// Symbolically executes code to determine which blocks are reachable for which values |
|
/// of the 'state' field. |
|
/// </summary> |
|
/// <remarks> |
|
/// Assumption: there are no loops/backward jumps |
|
/// We 'run' the code, with "state" being a symbolic variable |
|
/// so it can form expressions like "state + x" (when there's a sub instruction) |
|
/// |
|
/// For each block, we maintain the set of values for state for which the block is reachable. |
|
/// This is (int.MinValue, int.MaxValue) for the first instruction. |
|
/// These ranges are propagated depending on the conditional jumps performed by the code. |
|
/// </remarks> |
|
class StateRangeAnalysis |
|
{ |
|
readonly StateRangeAnalysisMode mode; |
|
readonly IField stateField; |
|
readonly SymbolicEvaluationContext evalContext; |
|
|
|
readonly Dictionary<Block, LongSet> ranges = new Dictionary<Block, LongSet>(); |
|
readonly internal Dictionary<IMethod, LongSet> finallyMethodToStateRange; // used only for IteratorDispose |
|
|
|
public StateRangeAnalysis(StateRangeAnalysisMode mode, IField stateField, ILVariable cachedStateVar = null) |
|
{ |
|
this.mode = mode; |
|
this.stateField = stateField; |
|
if (mode == StateRangeAnalysisMode.IteratorDispose) { |
|
finallyMethodToStateRange = new Dictionary<IMethod, LongSet>(); |
|
} |
|
|
|
evalContext = new SymbolicEvaluationContext(stateField); |
|
if (cachedStateVar != null) |
|
evalContext.AddStateVariable(cachedStateVar); |
|
} |
|
|
|
/// <summary> |
|
/// Assign state ranges for all blocks within 'inst'. |
|
/// </summary> |
|
/// <returns> |
|
/// The set of states for which the exit point of the instruction is reached. |
|
/// This must be a subset of the input set. |
|
/// |
|
/// Returns an empty set for unsupported instructions. |
|
/// </returns> |
|
public LongSet AssignStateRanges(ILInstruction inst, LongSet stateRange) |
|
{ |
|
switch (inst) { |
|
case BlockContainer blockContainer: |
|
AddStateRange(blockContainer.EntryPoint, stateRange); |
|
foreach (var block in blockContainer.Blocks) { |
|
// We assume that there are no jumps to blocks already processed. |
|
// TODO: is SortBlocks() guaranteeing this, even if the user code has loops? |
|
if (ranges.TryGetValue(block, out stateRange)) { |
|
AssignStateRanges(block, stateRange); |
|
} |
|
} |
|
// Since we don't track 'leave' edges, we can only conservatively |
|
// return LongSet.Empty. |
|
return LongSet.Empty; |
|
case Block block: |
|
foreach (var instInBlock in block.Instructions) { |
|
if (stateRange.IsEmpty) |
|
break; |
|
var oldStateRange = stateRange; |
|
stateRange = AssignStateRanges(instInBlock, stateRange); |
|
// End-point can only be reachable in a subset of the states where the start-point is reachable. |
|
Debug.Assert(stateRange.IsSubsetOf(oldStateRange)); |
|
// If the end-point is unreachable, it must be reachable in no states. |
|
Debug.Assert(stateRange.IsEmpty || !instInBlock.HasFlag(InstructionFlags.EndPointUnreachable)); |
|
} |
|
return stateRange; |
|
case TryFinally tryFinally: |
|
var afterTry = AssignStateRanges(tryFinally.TryBlock, stateRange); |
|
// really finally should start with 'stateRange.UnionWith(afterTry)', but that's |
|
// equal to 'stateRange'. |
|
Debug.Assert(afterTry.IsSubsetOf(stateRange)); |
|
var afterFinally = AssignStateRanges(tryFinally.FinallyBlock, stateRange); |
|
return afterTry.IntersectWith(afterFinally); |
|
case SwitchInstruction switchInst: |
|
SymbolicValue val = evalContext.Eval(switchInst.Value); |
|
if (val.Type != SymbolicValueType.State) |
|
goto default; |
|
List<LongInterval> allSectionLabels = new List<LongInterval>(); |
|
List<LongInterval> exitIntervals = new List<LongInterval>(); |
|
foreach (var section in switchInst.Sections) { |
|
// switch (state + Constant) |
|
// matches 'case VALUE:' |
|
// iff (state + Constant == value) |
|
// iff (state == value - Constant) |
|
var effectiveLabels = section.Labels.AddOffset(unchecked(-val.Constant)); |
|
allSectionLabels.AddRange(effectiveLabels.Intervals); |
|
var result = AssignStateRanges(section.Body, stateRange.IntersectWith(effectiveLabels)); |
|
exitIntervals.AddRange(result.Intervals); |
|
} |
|
var defaultSectionLabels = stateRange.ExceptWith(new LongSet(allSectionLabels)); |
|
exitIntervals.AddRange(AssignStateRanges(switchInst.DefaultBody, defaultSectionLabels).Intervals); |
|
// exitIntervals = union of exits of all sections |
|
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 { |
|
goto default; |
|
} |
|
var afterTrue = AssignStateRanges(ifInst.TrueInst, stateRange.IntersectWith(trueRanges)); |
|
var afterFalse = AssignStateRanges(ifInst.FalseInst, stateRange.ExceptWith(trueRanges)); |
|
return afterTrue.UnionWith(afterFalse); |
|
case Branch br: |
|
AddStateRange(br.TargetBlock, stateRange); |
|
return LongSet.Empty; |
|
case Nop nop: |
|
return stateRange; |
|
case StLoc stloc when stloc.Variable.IsSingleDefinition: |
|
val = evalContext.Eval(stloc.Value); |
|
if (val.Type == SymbolicValueType.State && val.Constant == 0) { |
|
evalContext.AddStateVariable(stloc.Variable); |
|
return stateRange; |
|
} else { |
|
goto default; // user code |
|
} |
|
case Call call when mode == StateRangeAnalysisMode.IteratorDispose: |
|
// Call to finally method. |
|
// Usually these are in finally blocks, but sometimes (e.g. foreach over array), |
|
// the C# compiler puts the call to a finally method outside the try-finally block. |
|
finallyMethodToStateRange.Add((IMethod)call.Method.MemberDefinition, stateRange); |
|
return LongSet.Empty; // return Empty since we executed user code (the finally method) |
|
default: |
|
// User code - abort analysis |
|
if (mode == StateRangeAnalysisMode.IteratorDispose && !(inst is Leave l && l.IsLeavingFunction)) { |
|
throw new SymbolicAnalysisFailedException("Unexpected instruction in Iterator.Dispose()"); |
|
} |
|
return LongSet.Empty; |
|
} |
|
} |
|
|
|
private void AddStateRange(Block block, LongSet stateRange) |
|
{ |
|
if (ranges.TryGetValue(block, out var existingRange)) |
|
ranges[block] = stateRange.UnionWith(existingRange); |
|
else |
|
ranges.Add(block, stateRange); |
|
} |
|
|
|
public IEnumerable<(Block, LongSet)> GetBlockStateSetMapping(BlockContainer container) |
|
{ |
|
foreach (var block in container.Blocks) { |
|
if (ranges.TryGetValue(block, out var stateSet)) |
|
yield return (block, stateSet); |
|
} |
|
} |
|
} |
|
}
|
|
|