Browse Source

WIP: yield return decompiler

pull/728/merge
Daniel Grunwald 9 years ago
parent
commit
fe84ea9730
  1. 19
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 22
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 20
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
  5. 2
      ICSharpCode.Decompiler/DecompilerSettings.cs
  6. 10
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  7. 2
      ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs
  8. 7
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  9. 2
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  10. 1
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  11. 13
      ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs
  12. 2
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  13. 195
      ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
  14. 161
      ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs
  15. 585
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  16. 12
      ICSharpCode.Decompiler/IL/ILReader.cs
  17. 211
      ICSharpCode.Decompiler/IL/Instructions.cs
  18. 16
      ICSharpCode.Decompiler/IL/Instructions.tt
  19. 13
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  20. 6
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  21. 50
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  22. 43
      ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs
  23. 9
      ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs
  24. 36
      ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs
  25. 2
      ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs
  26. 45
      ICSharpCode.Decompiler/Tests/TestCases/Pretty/YieldReturn.cs
  27. 8
      ICSharpCode.Decompiler/TypeSystem/CecilLoader.cs
  28. 2
      ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultUnresolvedAttribute.cs
  29. 27
      ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
  30. 6
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs
  31. 6
      ICSharpCode.Decompiler/Util/ExtensionMethods.cs
  32. 52
      ICSharpCode.Decompiler/Util/LongSet.cs
  33. 12
      ILSpy/ILSpyTraceListener.cs
  34. 4
      ILSpy/XmlDoc/XmlDocLoader.cs

19
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -48,6 +48,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -48,6 +48,18 @@ namespace ICSharpCode.Decompiler.CSharp
List<IILTransform> ilTransforms = GetILTransforms();
/// <summary>
/// Pre-yield/await transforms.
/// </summary>
internal static List<IILTransform> EarlyILTransforms()
{
return new List<IILTransform> {
new ControlFlowSimplification(),
new SplitVariables(),
new ILInlining(),
};
}
public static List<IILTransform> GetILTransforms()
{
return new List<IILTransform> {
@ -57,6 +69,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -57,6 +69,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(),
new ILInlining(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new YieldReturnDecompiler(), // must run after inlining but before loop detection
new DetectExitPoints(),
new BlockILTransform {
PostOrderTransforms = {
@ -161,8 +174,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -161,8 +174,8 @@ namespace ICSharpCode.Decompiler.CSharp
if (type.DeclaringType != null) {
if (settings.AnonymousMethods && IsClosureType(type))
return true;
// if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type))
// return true;
if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type))
return true;
// if (settings.AsyncAwait && AsyncDecompiler.IsCompilerGeneratedStateMachine(type))
// return true;
} else if (type.IsCompilerGenerated()) {
@ -616,7 +629,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -616,7 +629,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
AddDefinesForConditionalAttributes(function);
var statementBuilder = new StatementBuilder(specializingTypeSystem, decompilationContext, method);
var statementBuilder = new StatementBuilder(specializingTypeSystem, decompilationContext, method, function);
entityDecl.AddChild(statementBuilder.ConvertAsBlock(function.Body), Roles.Body);
}

22
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -942,7 +942,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -942,7 +942,7 @@ namespace ICSharpCode.Decompiler.CSharp
AnonymousMethodExpression ame = new AnonymousMethodExpression();
ame.Parameters.AddRange(MakeParameters(method, function));
ame.HasParameterList = true;
StatementBuilder builder = new StatementBuilder(typeSystem.GetSpecializingTypeSystem(new SimpleTypeResolveContext(method)), this.decompilationContext, method);
StatementBuilder builder = new StatementBuilder(typeSystem.GetSpecializingTypeSystem(new SimpleTypeResolveContext(method)), this.decompilationContext, method, function);
var body = builder.ConvertAsBlock(function.Body);
bool isLambda = false;
bool isMultiLineLambda = false;
@ -1548,10 +1548,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1548,10 +1548,10 @@ namespace ICSharpCode.Decompiler.CSharp
.WithILInstruction(inst)
.WithRR(new ByReferenceResolveResult(value.ResolveResult, false));
}
protected internal override TranslatedExpression VisitInvalidInstruction(InvalidInstruction inst, TranslationContext context)
protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context)
{
string message = "Invalid IL";
string message = "Error";
if (inst.ILRange.Start != 0) {
message += $" near IL_{inst.ILRange.Start:x4}";
}
@ -1560,7 +1560,19 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1560,7 +1560,19 @@ namespace ICSharpCode.Decompiler.CSharp
}
return ErrorExpression(message);
}
protected internal override TranslatedExpression VisitInvalidExpression(InvalidExpression inst, TranslationContext context)
{
string message = "Error";
if (inst.ILRange.Start != 0) {
message += $" near IL_{inst.ILRange.Start:x4}";
}
if (!string.IsNullOrEmpty(inst.Message)) {
message += ": " + inst.Message;
}
return ErrorExpression(message);
}
protected override TranslatedExpression Default(ILInstruction inst, TranslationContext context)
{
return ErrorExpression("OpCode not supported: " + inst.OpCode);

20
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -31,12 +31,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -31,12 +31,14 @@ namespace ICSharpCode.Decompiler.CSharp
class StatementBuilder : ILVisitor<Statement>
{
internal readonly ExpressionBuilder exprBuilder;
readonly ILFunction currentFunction;
readonly IMethod currentMethod;
public StatementBuilder(IDecompilerTypeSystem typeSystem, ITypeResolveContext decompilationContext, IMethod currentMethod)
public StatementBuilder(IDecompilerTypeSystem typeSystem, ITypeResolveContext decompilationContext, IMethod currentMethod, ILFunction currentFunction)
{
Debug.Assert(typeSystem != null && decompilationContext != null && currentMethod != null);
this.exprBuilder = new ExpressionBuilder(typeSystem, decompilationContext);
this.currentFunction = currentFunction;
this.currentMethod = currentMethod;
}
@ -146,8 +148,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -146,8 +148,12 @@ namespace ICSharpCode.Decompiler.CSharp
{
if (inst.TargetContainer == breakTarget)
return new BreakStatement();
if (inst.TargetContainer.SlotInfo == ILFunction.BodySlot)
return new ReturnStatement();
if (inst.TargetContainer.SlotInfo == ILFunction.BodySlot) {
if (currentFunction.IsIterator)
return new YieldBreakStatement();
else
return new ReturnStatement();
}
string label;
if (!endContainerLabels.TryGetValue(inst.TargetContainer, out label)) {
label = "end_" + inst.TargetLabel;
@ -173,6 +179,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -173,6 +179,14 @@ namespace ICSharpCode.Decompiler.CSharp
return new ReturnStatement(exprBuilder.Translate(inst.ReturnValue).ConvertTo(currentMethod.ReturnType, exprBuilder));
}
protected internal override Statement VisitYieldReturn(YieldReturn inst)
{
var elementType = currentMethod.ReturnType.GetElementTypeFromIEnumerable(currentMethod.Compilation, true, out var isGeneric);
return new YieldReturnStatement {
Expression = exprBuilder.Translate(inst.Value).ConvertTo(elementType, exprBuilder)
};
}
TryCatchStatement MakeTryCatch(ILInstruction tryBlock)
{
var tryBlockConverted = Convert(tryBlock);

2
ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs

@ -198,7 +198,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -198,7 +198,7 @@ namespace ICSharpCode.Decompiler.CSharp
var asm = module.AssemblyResolver.Resolve(r);
if (!IsGacAssembly(r, asm)) {
if (asm != null) {
w.WriteElementString("HintPath", asm.MainModule.FullyQualifiedName);
w.WriteElementString("HintPath", asm.MainModule.FileName);
}
}
w.WriteEndElement();

2
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -57,7 +57,7 @@ namespace ICSharpCode.Decompiler @@ -57,7 +57,7 @@ namespace ICSharpCode.Decompiler
}
}
bool yieldReturn = true;
bool yieldReturn = false;
/// <summary>
/// Decompile enumerators.

10
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -271,7 +271,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -271,7 +271,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
#if DEBUG
DebugPoint(debugOutputState, inst);
#endif
}
/// <summary>
@ -447,7 +446,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -447,7 +446,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
MarkUnreachable();
}
protected internal override void VisitInvalidInstruction(InvalidInstruction inst)
protected internal override void VisitInvalidBranch(InvalidBranch inst)
{
MarkUnreachable();
}
@ -579,5 +578,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -579,5 +578,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
state = afterSections;
DebugEndPoint(inst);
}
protected internal override void VisitYieldReturn(YieldReturn inst)
{
DebugStartPoint(inst);
inst.Value.AcceptVisitor(this);
DebugEndPoint(inst);
}
}
}

2
ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs

@ -140,7 +140,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -140,7 +140,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
if (j.IsReachable && (j.Predecessors.Count >= 2 || (j.Predecessors.Count >= 1 && j.ImmediateDominator == null))) {
// Add j to frontier of all predecessors and their dominators up to j's immediate dominator.
foreach (var p in j.Predecessors) {
for (var runner = p; runner != j.ImmediateDominator && runner != j; runner = runner.ImmediateDominator) {
for (var runner = p; runner != j.ImmediateDominator && runner != j && runner != null; runner = runner.ImmediateDominator) {
nonEmpty.Set(runner.UserIndex);
}
}

7
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -280,8 +280,11 @@ @@ -280,8 +280,11 @@
<Compile Include="Documentation\IdStringProvider.cs" />
<Compile Include="Documentation\XmlDocumentationProvider.cs" />
<Compile Include="IL\ControlFlow\ControlFlowGraph.cs" />
<Compile Include="IL\ControlFlow\StateRangeAnalysis.cs" />
<Compile Include="IL\ControlFlow\SymbolicExecution.cs" />
<Compile Include="IL\Instructions\ILVariableCollection.cs" />
<Compile Include="IL\Patterns\AnyNode.cs" />
<Compile Include="IL\ControlFlow\YieldReturnDecompiler.cs" />
<Compile Include="Util\UnicodeNewline.cs" />
<Compile Include="FlowAnalysis\ControlFlowNode.cs" />
<Compile Include="FlowAnalysis\DataFlowVisitor.cs" />
@ -547,9 +550,7 @@ @@ -547,9 +550,7 @@
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<Folder Include="ILAst\" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<None Include="..\nrefactory\doc\XML Documentation.html">
<Link>Documentation\XML Documentation.html</Link>

2
ICSharpCode.Decompiler/IL/BlockBuilder.cs

@ -187,7 +187,7 @@ namespace ICSharpCode.Decompiler.IL @@ -187,7 +187,7 @@ namespace ICSharpCode.Decompiler.IL
foreach (var block in container.Blocks) {
ConnectBranches(block);
if (block.Instructions.Count == 0 || !block.Instructions.Last().HasFlag(InstructionFlags.EndPointUnreachable)) {
block.Instructions.Add(new InvalidInstruction("Unexpected end of block"));
block.Instructions.Add(new InvalidBranch("Unexpected end of block"));
}
}
containerStack.Pop();

1
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -79,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -79,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (IsUsableBranchToChild(cfgNode, exitInst)) {
// "...; goto usableblock;"
// -> embed target block in this block
context.Step("Inline target block of unconditional branch", exitInst);
var targetBlock = ((Branch)exitInst).TargetBlock;
Debug.Assert(exitInst == block.Instructions.Last());
block.Instructions.RemoveAt(block.Instructions.Count - 1);

13
ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs

@ -397,20 +397,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -397,20 +397,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void ReplacePinnedVar(ILVariable oldVar, ILVariable newVar, ILInstruction inst)
{
Conv conv = inst as Conv;
if (conv != null && conv.Kind == ConversionKind.StopGCTracking && conv.Argument.MatchLdLoc(oldVar)) {
if (inst is Conv conv && conv.Kind == ConversionKind.StopGCTracking && conv.Argument.MatchLdLoc(oldVar)) {
// conv ref->i (ldloc oldVar)
// => ldloc newVar
conv.AddILRange(conv.Argument.ILRange);
conv.ReplaceWith(new LdLoc(newVar) { ILRange = conv.ILRange });
return;
}
var iwvo = inst as IInstructionWithVariableOperand;
if (iwvo != null && iwvo.Variable == oldVar) {
if (inst is IInstructionWithVariableOperand iwvo && iwvo.Variable == oldVar) {
iwvo.Variable = newVar;
if (inst is StLoc && oldVar.Type.Kind == TypeKind.ByReference) {
((StLoc)inst).Value = new Conv(((StLoc)inst).Value, PrimitiveType.I, false, Sign.None);
if (inst is StLoc stloc && oldVar.Type.Kind == TypeKind.ByReference) {
stloc.Value = new Conv(stloc.Value, PrimitiveType.I, false, Sign.None);
}
} else if (inst.MatchLdStr(out var val) && val == "Is this ILSpy?") {
inst.ReplaceWith(new LdStr("This is ILSpy!")); // easter egg ;)
return;
}
foreach (var child in inst.Children) {
ReplacePinnedVar(oldVar, newVar, child);

2
ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs

@ -96,7 +96,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -96,7 +96,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(loop[0] == h);
foreach (var node in loop) {
node.Visited = false; // reset visited flag so that we can find outer loops
Debug.Assert(h.Dominates(node), "The loop body must be dominated by the loop head");
Debug.Assert(h.Dominates(node) || !node.IsReachable, "The loop body must be dominated by the loop head");
}
ConstructLoop(loop, exitPoint);
}

195
ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs

@ -0,0 +1,195 @@ @@ -0,0 +1,195 @@
// 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;
LongSet falseRanges;
if (val.Type == SymbolicValueType.StateEquals) {
trueRanges = stateRange.IntersectWith(new LongSet(val.Constant));
falseRanges = stateRange.ExceptWith(new LongSet(val.Constant));
} else if (val.Type == SymbolicValueType.StateInEquals) {
trueRanges = stateRange.ExceptWith(new LongSet(val.Constant));
falseRanges = stateRange.IntersectWith(new LongSet(val.Constant));
} else {
goto default;
}
var afterTrue = AssignStateRanges(ifInst.TrueInst, trueRanges);
var afterFalse = AssignStateRanges(ifInst.FalseInst, falseRanges);
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.OpCode != OpCode.Return) {
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);
}
}
}
}

161
ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs

@ -0,0 +1,161 @@ @@ -0,0 +1,161 @@
// Copyright (c) 2011 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
/// <summary>
/// This exception is thrown when we find something else than we expect from the C# compiler.
/// This aborts the analysis and makes the whole transform fail.
/// </summary>
class SymbolicAnalysisFailedException : Exception
{
public SymbolicAnalysisFailedException() { }
public SymbolicAnalysisFailedException(string message) : base(message) { }
}
enum SymbolicValueType
{
/// <summary>
/// Unknown value
/// </summary>
Unknown,
/// <summary>
/// int: Constant (result of ldc.i4)
/// </summary>
IntegerConstant,
/// <summary>
/// int: State + Constant
/// </summary>
State,
/// <summary>
/// This pointer (result of ldarg.0)
/// </summary>
This,
/// <summary>
/// bool: State == Constant
/// </summary>
StateEquals,
/// <summary>
/// bool: State != Constant
/// </summary>
StateInEquals
}
struct SymbolicValue
{
public readonly int Constant;
public readonly SymbolicValueType Type;
public SymbolicValue(SymbolicValueType type, int constant = 0)
{
this.Type = type;
this.Constant = constant;
}
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 this;
}
public override string ToString()
{
return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant);
}
}
class SymbolicEvaluationContext
{
readonly IField stateField;
readonly List<ILVariable> stateVariables = new List<ILVariable>();
public SymbolicEvaluationContext(IField stateField)
{
this.stateField = stateField;
}
public void AddStateVariable(ILVariable v)
{
if (!stateVariables.Contains(v))
stateVariables.Add(v);
}
static readonly SymbolicValue Failed = new SymbolicValue(SymbolicValueType.Unknown);
public SymbolicValue Eval(ILInstruction inst)
{
if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && !bni.CheckForOverflow) {
var left = Eval(bni.Left);
var right = Eval(bni.Right);
if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant)
return Failed;
if (right.Type != SymbolicValueType.IntegerConstant)
return Failed;
return new SymbolicValue(left.Type, unchecked(left.Constant - right.Constant));
} else if (inst.MatchLdFld(out var target, out var field)) {
if (Eval(target).Type != SymbolicValueType.This)
return Failed;
if (field.MemberDefinition != stateField)
return Failed;
return new SymbolicValue(SymbolicValueType.State);
} else if (inst.MatchLdLoc(out var loadedVariable)) {
if (stateVariables.Contains(loadedVariable))
return new SymbolicValue(SymbolicValueType.State);
else if (loadedVariable.Kind == VariableKind.Parameter && loadedVariable.Index < 0)
return new SymbolicValue(SymbolicValueType.This);
else
return Failed;
} else if (inst.MatchLdcI4(out var value)) {
return new SymbolicValue(SymbolicValueType.IntegerConstant, value);
} else if (inst is Comp comp) {
var left = Eval(comp.Left);
var right = Eval(comp.Right);
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
return Failed;
} else if (inst is LogicNot logicNot) {
SymbolicValue val = Eval(logicNot.Argument).AsBool();
if (val.Type == SymbolicValueType.StateEquals)
return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant);
else if (val.Type == SymbolicValueType.StateInEquals)
return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant);
else
return Failed;
} else {
return Failed;
}
}
}
}

585
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -0,0 +1,585 @@ @@ -0,0 +1,585 @@
// Copyright (c) 2011 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.CSharp;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
class YieldReturnDecompiler : IILTransform
{
// For a description on the code generated by the C# compiler for yield return:
// http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx
// The idea here is:
// - Figure out whether the current method is instanciating an enumerator
// - Figure out which of the fields is the state field
// - Construct an exception table based on states. This allows us to determine, for each state, what the parent try block is.
// See http://community.sharpdevelop.net/blogs/danielgrunwald/archive/2011/03/06/ilspy-yield-return.aspx
// for a description of this step.
ILTransformContext context;
/// <summary>The type that contains the function being decompiled.</summary>
TypeDefinition currentType;
/// <summary>The compiler-generated enumerator class.</summary>
/// <remarks>Set in MatchEnumeratorCreationPattern()</remarks>
TypeDefinition enumeratorType;
/// <summary>The constructor of the compiler-generated enumerator class.</summary>
/// <remarks>Set in MatchEnumeratorCreationPattern()</remarks>
MethodDefinition enumeratorCtor;
/// <summary>The dispose method of the compiler-generated enumerator class.</summary>
/// <remarks>Set in ConstructExceptionTable()</remarks>
MethodDefinition disposeMethod;
/// <summary>The field in the compiler-generated class holding the current state of the state machine</summary>
/// <remarks>Set in AnalyzeCtor()</remarks>
IField stateField;
/// <summary>The backing field of the 'Current' property in the compiler-generated class</summary>
/// <remarks>Set in AnalyzeCurrentProperty()</remarks>
IField currentField;
/// <summary>Maps the fields of the compiler-generated class to the original parameters.</summary>
/// <remarks>Set in MatchEnumeratorCreationPattern() and ResolveIEnumerableIEnumeratorFieldMapping()</remarks>
readonly Dictionary<IField, ILVariable> fieldToParameterMap = new Dictionary<IField, ILVariable>();
/// <summary>This dictionary stores the information extracted from the Dispose() method:
/// for each "Finally Method", it stores the set of states for which the method is being called.</summary>
/// <remarks>Set in ConstructExceptionTable()</remarks>
Dictionary<IMethod, LongSet> finallyMethodToStateRange;
/// <summary>
/// List of blocks that change the iterator state on block entry.
/// </summary>
readonly List<(int state, Block block)> stateChanges = new List<(int state, Block block)>();
#region Run() method
public void Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.YieldReturn)
return; // abort if enumerator decompilation is disabled
this.context = context;
this.currentType = function.Method.DeclaringType;
this.enumeratorType = null;
this.enumeratorCtor = null;
this.stateField = null;
this.currentField = null;
this.fieldToParameterMap.Clear();
this.finallyMethodToStateRange = null;
this.stateChanges.Clear();
if (!MatchEnumeratorCreationPattern(function))
return;
BlockContainer newBody;
try {
AnalyzeCtor();
AnalyzeCurrentProperty();
ResolveIEnumerableIEnumeratorFieldMapping();
ConstructExceptionTable();
newBody = AnalyzeMoveNext();
} catch (SymbolicAnalysisFailedException) {
return;
}
context.Step("Replacing body with MoveNext() body", function);
function.IsIterator = true;
function.Body = newBody;
// register any locals used in newBody
function.Variables.AddRange(newBody.Descendants.OfType<IInstructionWithVariableOperand>().Select(inst => inst.Variable).Distinct());
function.CheckInvariant(ILPhase.Normal);
PrintFinallyMethodStateRanges(newBody);
context.Step("Delete unreachable blocks", function);
// Note: because this only deletes blocks outright, the 'stateChanges' entries remain valid
// (though some may point to now-deleted blocks)
newBody.SortBlocks(deleteUnreachableBlocks: true);
context.Step("Reconstruct try-finally blocks", function);
ReconstructTryFinallyBlocks(newBody);
context.Step("Translate fields to local accesses", function);
TranslateFieldsToLocalAccess(function, function, fieldToParameterMap);
// Re-run control flow simplification over the newly constructed set of gotos,
// and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities.
function.RunTransforms(CSharpDecompiler.EarlyILTransforms(), context);
}
#endregion
#region Match the enumerator creation pattern
bool MatchEnumeratorCreationPattern(ILFunction function)
{
Block body = SingleBlock(function.Body);
if (body == null || body.Instructions.Count == 0) {
return false;
}
ILInstruction newObj;
if (body.Instructions.Count == 1) {
// No parameters passed to enumerator (not even 'this'):
// ret(newobj(...))
if (body.Instructions[0].MatchReturn(out newObj))
return MatchEnumeratorCreationNewObj(newObj);
else
return false;
}
// If there's parameters passed to the helper class, the class instance is first
// stored in a variable, then the parameters are copied over, then the instance is returned.
// stloc(var_1, newobj(..))
if (!body.Instructions[0].MatchStLoc(out var var1, out newObj))
return false;
if (!MatchEnumeratorCreationNewObj(newObj))
return false;
int i;
for (i = 1; i < body.Instructions.Count; i++) {
// stfld(..., ldloc(var_1), ldloc(parameter))
if (!body.Instructions[i].MatchStFld(out var ldloc, out var storedField, out var loadParameter))
break;
if (ldloc.MatchLdLoc(var1)
&& loadParameter.MatchLdLoc(out var parameter)
&& parameter.Kind == VariableKind.Parameter) {
fieldToParameterMap[(IField)storedField.MemberDefinition] = parameter;
} else {
return false;
}
}
// In debug builds, the compiler may copy the var1 into another variable (var2) before returning it.
if (i < body.Instructions.Count
&& body.Instructions[i].MatchStLoc(out var var2, out var ldlocForStloc2)
&& ldlocForStloc2.MatchLdLoc(var1)) {
// stloc(var_2, ldloc(var_1))
i++;
} else {
// in release builds, var1 is returned directly
var2 = var1;
}
if (i < body.Instructions.Count
&& body.Instructions[i].MatchReturn(out var retVal)
&& retVal.MatchLdLoc(var2)) {
// ret(ldloc(var_2))
return true;
} else {
return false;
}
}
/// <summary>
/// Matches the body of a method as a single basic block.
/// </summary>
static Block SingleBlock(ILInstruction body)
{
var block = body as Block;
if (body is BlockContainer blockContainer && blockContainer.Blocks.Count == 1) {
block = blockContainer.Blocks.Single() as Block;
}
return block;
}
/// <summary>
/// Matches the newobj instruction that creates an instance of the compiler-generated enumerator helper class.
/// </summary>
bool MatchEnumeratorCreationNewObj(ILInstruction inst)
{
// newobj(CurrentType/...::.ctor, ldc.i4(-2))
if (!(inst is NewObj newObj))
return false;
if (newObj.Arguments.Count != 1)
return false;
if (!newObj.Arguments[0].MatchLdcI4(out int initialState))
return false;
if (!(initialState == -2 || initialState == 0))
return false;
enumeratorCtor = context.TypeSystem.GetCecil(newObj.Method) as MethodDefinition;
enumeratorType = enumeratorCtor?.DeclaringType;
return enumeratorType?.DeclaringType == currentType
&& IsCompilerGeneratorEnumerator(enumeratorType);
}
public static bool IsCompilerGeneratorEnumerator(TypeDefinition type)
{
if (!(type?.DeclaringType != null && type.IsCompilerGenerated()))
return false;
foreach (var i in type.Interfaces) {
var tr = i.InterfaceType;
if (tr.Namespace == "System.Collections" && tr.Name == "IEnumerator")
return true;
}
return false;
}
#endregion
#region Figure out what the 'state' field is (analysis of .ctor())
/// <summary>
/// Looks at the enumerator's ctor and figures out which of the fields holds the state.
/// </summary>
void AnalyzeCtor()
{
Block body = SingleBlock(CreateILAst(enumeratorCtor).Body);
if (body == null)
throw new SymbolicAnalysisFailedException("Missing enumeratorCtor.Body");
foreach (var inst in body.Instructions) {
if (inst.MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis()
&& value.MatchLdLoc(out var arg)
&& arg.Kind == VariableKind.Parameter && arg.Index == 0) {
stateField = (IField)field.MemberDefinition;
}
}
if (stateField == null)
throw new SymbolicAnalysisFailedException("Could not find stateField");
}
/// <summary>
/// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
/// </summary>
ILFunction CreateILAst(MethodDefinition method)
{
if (method == null || !method.HasBody)
throw new SymbolicAnalysisFailedException();
var il = new ILReader(context.TypeSystem).ReadIL(method.Body, context.CancellationToken);
il.RunTransforms(CSharpDecompiler.EarlyILTransforms(), new ILTransformContext {
Settings = context.Settings,
CancellationToken = context.CancellationToken,
TypeSystem = context.TypeSystem
});
return il;
}
#endregion
#region Figure out what the 'current' field is (analysis of get_Current())
/// <summary>
/// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value.
/// </summary>
void AnalyzeCurrentProperty()
{
MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault(
m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal)
&& m.Name.EndsWith(".get_Current", StringComparison.Ordinal));
Block body = SingleBlock(CreateILAst(getCurrentMethod).Body);
if (body == null)
throw new SymbolicAnalysisFailedException();
if (body.Instructions.Count == 1) {
// release builds directly return the current field
// ret(ldfld F(ldloc(this)))
if (body.Instructions[0].MatchReturn(out var retVal)
&& retVal.MatchLdFld(out var target, out var field)
&& target.MatchLdThis()) {
currentField = (IField)field.MemberDefinition;
}
} else if (body.Instructions.Count == 2) {
// debug builds store the return value in a temporary
// stloc V = ldfld F(ldloc(this))
// ret(ldloc V)
if (body.Instructions[0].MatchStLoc(out var v, out var ldfld)
&& ldfld.MatchLdFld(out var target, out var field)
&& target.MatchLdThis()
&& body.Instructions[1].MatchReturn(out var retVal)
&& retVal.MatchLdLoc(v)) {
currentField = (IField)field.MemberDefinition;
}
}
if (currentField == null)
throw new SymbolicAnalysisFailedException("Could not find currentField");
}
#endregion
#region Figure out the mapping of IEnumerable fields to IEnumerator fields (analysis of GetEnumerator())
void ResolveIEnumerableIEnumeratorFieldMapping()
{
MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault(
m => m.Name.StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal)
&& m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal));
if (getEnumeratorMethod == null)
return; // no mappings (maybe it's just an IEnumerator implementation?)
var function = CreateILAst(getEnumeratorMethod);
foreach (var block in function.Descendants.OfType<Block>()) {
foreach (var inst in block.Instructions) {
// storeTarget.storeField = this.loadField;
if (inst.MatchStFld(out var storeTarget, out var storeField, out var storeValue)
&& storeValue.MatchLdFld(out var loadTarget, out var loadField)
&& loadTarget.MatchLdThis()) {
storeField = (IField)storeField.MemberDefinition;
loadField = (IField)loadField.MemberDefinition;
if (fieldToParameterMap.TryGetValue(loadField, out var mappedParameter))
fieldToParameterMap[storeField] = mappedParameter;
}
}
}
}
#endregion
#region Construction of the exception table (analysis of Dispose())
// We construct the exception table by analyzing the enumerator's Dispose() method.
void ConstructExceptionTable()
{
disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose");
var function = CreateILAst(disposeMethod);
var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorDispose, stateField);
rangeAnalysis.AssignStateRanges(function.Body, LongSet.Universe);
finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange;
}
[Conditional("DEBUG")]
void PrintFinallyMethodStateRanges(BlockContainer bc)
{
foreach (var (method, stateRange) in finallyMethodToStateRange) {
bc.Blocks[0].Instructions.Insert(0, new Nop {
Comment = method.Name + " in " + stateRange
});
}
}
#endregion
#region Analyze MoveNext() and generate new body
BlockContainer AnalyzeMoveNext()
{
MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext");
ILFunction moveNextFunction = CreateILAst(moveNextMethod);
var body = (BlockContainer)moveNextFunction.Body;
if (body.Blocks.Count == 1 && body.Blocks[0].Instructions.Count == 1 && body.Blocks[0].Instructions[0] is TryFault tryFault) {
body = (BlockContainer)tryFault.TryBlock;
var faultBlockContainer = tryFault.FaultBlock as BlockContainer;
if (faultBlockContainer?.Blocks.Count != 1)
throw new SymbolicAnalysisFailedException("Unexpected number of blocks in MoveNext() fault block");
var faultBlock = faultBlockContainer.Blocks.Single();
if (!(faultBlock.Instructions.Count == 2
&& faultBlock.Instructions[0] is Call call
&& context.TypeSystem.GetCecil(call.Method) == disposeMethod
&& call.Arguments.Count == 1
&& call.Arguments[0].MatchLdThis()
&& faultBlock.Instructions[1].MatchLeave(faultBlockContainer))) {
throw new SymbolicAnalysisFailedException("Unexpected fault block contents in MoveNext()");
}
}
// 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 need at the blocks directly within body.
var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorMoveNext, stateField);
rangeAnalysis.AssignStateRanges(body, LongSet.Universe);
var newBody = ConvertBody(body, rangeAnalysis.GetBlockStateSetMapping(body));
moveNextFunction.Variables.Clear();
// release references from old moveNextFunction to instructions that were moved over to newBody
moveNextFunction.ReleaseRef();
return newBody;
}
/// <summary>
/// Convert the old body (of MoveNext function) to the new body (of decompiled iterator method).
///
/// * Replace the sequence
/// this.currentField = expr;
/// this.state = N;
/// return true;
/// with:
/// yield return expr;
/// goto blockForState(N);
/// * Replace the sequence:
/// this._finally2();
/// this._finally1();
/// return false;
/// with:
/// yield break;
/// * Reconstruct try-finally blocks from
/// (on enter) this.state = N;
/// (on exit) this._finallyX();
/// </summary>
private BlockContainer ConvertBody(BlockContainer oldBody, IEnumerable<(Block, LongSet)> blockStateSets)
{
BlockContainer newBody = new BlockContainer();
// create all new blocks so that they can be referenced by gotos
for (int blockIndex = 0; blockIndex < oldBody.Blocks.Count; blockIndex++) {
newBody.Blocks.Add(new Block { ILRange = oldBody.Blocks[blockIndex].ILRange });
}
// convert contents of blocks
var ssaDefs = new Dictionary<ILVariable, ILInstruction>();
for (int i = 0; i < oldBody.Blocks.Count; i++) {
var oldBlock = oldBody.Blocks[i];
var newBlock = newBody.Blocks[i];
foreach (var oldInst in oldBlock.Instructions) {
if (oldInst.MatchStFld(out var target, out var field, out var value) && target.MatchLdThis()) {
if (field.MemberDefinition.Equals(stateField)) {
if (value.MatchLdcI4(out int newState)) {
// On state change, break up the block (if necessary):
if (newBlock.Instructions.Count > 0) {
var newBlock2 = new Block();
newBlock2.ILRange = new Interval(oldInst.ILRange.Start, oldInst.ILRange.Start);
newBody.Blocks.Add(newBlock2);
newBlock.Instructions.Add(new Branch(newBlock2));
newBlock = newBlock2;
}
#if DEBUG
newBlock.Instructions.Add(new Nop { Comment = "iterator._state = " + newState });
#endif
stateChanges.Add((newState, newBlock));
} else {
newBlock.Instructions.Add(new InvalidExpression("Assigned non-constant to iterator.state field") {
ILRange = oldInst.ILRange
});
}
continue; // don't copy over this instruction, but continue with the basic block
} else if (field.MemberDefinition.Equals(currentField)) {
// create yield return
newBlock.Instructions.Add(new YieldReturn(value) { ILRange = oldInst.ILRange });
ConvertBranchAfterYieldReturn(newBlock, oldBlock, oldInst.ChildIndex);
break; // we're done with this basic block
}
} else if (oldInst.MatchReturn(out value)) {
if (value.MatchLdLoc(out var v)) {
ssaDefs.TryGetValue(v, out value);
}
if (value.MatchLdcI4(0)) {
// yield break
newBlock.Instructions.Add(new Leave(newBody) { ILRange = oldInst.ILRange });
} else {
newBlock.Instructions.Add(new InvalidBranch("Unexpected return in MoveNext()"));
}
break; // we're done with this basic block
} else if (oldInst.MatchStLoc(out var v, out value) && v.IsSingleDefinition) {
ssaDefs.Add(v, value);
}
// copy over the instruction to the new block
UpdateBranchTargets(oldInst);
newBlock.Instructions.Add(oldInst);
}
}
// Insert new artificial block as entry point, and jump to state 0.
// This causes the method to start directly at the first user code,
// and the whole compiler-generated state-dispatching logic becomes unreachable code
// and gets deleted.
newBody.Blocks.Insert(0, new Block {
Instructions = { MakeGoTo(0) }
});
return newBody;
void ConvertBranchAfterYieldReturn(Block newBlock, Block oldBlock, int i)
{
if (!(oldBlock.Instructions[i + 1].MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis()
&& field.MemberDefinition == stateField
&& value.MatchLdcI4(out int newState))) {
newBlock.Instructions.Add(new InvalidBranch("Unable to find new state assignment for yield return"));
return;
}
if (!(oldBlock.Instructions[i + 2].MatchReturn(out var retVal)
&& retVal.MatchLdcI4(1))) {
newBlock.Instructions.Add(new InvalidBranch("Unable to find 'return true' for yield return"));
return;
}
newBlock.Instructions.Add(MakeGoTo(newState));
}
ILInstruction MakeGoTo(int v)
{
Block targetBlock = null;
foreach (var (block, stateSet) in blockStateSets) {
if (stateSet.Contains(v))
targetBlock = block;
}
if (targetBlock != null)
return new Branch(newBody.Blocks[targetBlock.ChildIndex]);
else
return new InvalidBranch("Could not find block for state " + v);
}
void UpdateBranchTargets(ILInstruction inst)
{
switch (inst) {
case Branch branch:
if (branch.TargetContainer == oldBody) {
branch.TargetBlock = newBody.Blocks[branch.TargetBlock.ChildIndex];
}
break;
case Leave leave:
if (leave.TargetContainer == oldBody) {
leave.TargetContainer = newBody;
}
break;
}
foreach (var child in inst.Children) {
UpdateBranchTargets(child);
}
}
}
#endregion
#region TranslateFieldsToLocalAccess
/// <summary>
/// Translates all field accesses in `function` to local variable accesses.
/// </summary>
internal static void TranslateFieldsToLocalAccess(ILFunction function, ILInstruction inst, Dictionary<IField, ILVariable> fieldToVariableMap)
{
if (inst is LdFlda ldflda && ldflda.Target.MatchLdThis()) {
var fieldDef = (IField)ldflda.Field.MemberDefinition;
if (!fieldToVariableMap.TryGetValue(fieldDef, out var v)) {
string name = null;
if (!string.IsNullOrEmpty(fieldDef.Name) && fieldDef.Name[0] == '<') {
int pos = fieldDef.Name.IndexOf('>');
if (pos > 1)
name = fieldDef.Name.Substring(1, pos - 1);
}
v = function.RegisterVariable(VariableKind.Local, ldflda.Field.ReturnType, name);
fieldToVariableMap.Add(fieldDef, v);
}
inst.ReplaceWith(new LdLoca(v));
} else if (inst.MatchLdThis()) {
inst.ReplaceWith(new InvalidExpression("iterator") { ExpectedResultType = inst.ResultType });
} else {
foreach (var child in inst.Children) {
TranslateFieldsToLocalAccess(function, child, fieldToVariableMap);
}
}
}
#endregion
#region Reconstruct try-finally blocks
private void ReconstructTryFinallyBlocks(BlockContainer newBody)
{
// TODO
}
#endregion
}
}

12
ICSharpCode.Decompiler/IL/ILReader.cs

@ -335,7 +335,7 @@ namespace ICSharpCode.Decompiler.IL @@ -335,7 +335,7 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction DecodeInstruction()
{
if (nextInstructionIndex >= body.Instructions.Count)
return new InvalidInstruction("Unexpected end of body");
return new InvalidBranch("Unexpected end of body");
var cecilInst = body.Instructions[nextInstructionIndex++];
currentInstruction = cecilInst;
switch (cecilInst.OpCode.Code) {
@ -792,7 +792,7 @@ namespace ICSharpCode.Decompiler.IL @@ -792,7 +792,7 @@ namespace ICSharpCode.Decompiler.IL
case Cil.Code.Unbox_Any:
return Push(new UnboxAny(Pop(), ReadAndDecodeTypeReference()));
default:
return new InvalidInstruction("Unknown opcode: " + cecilInst.OpCode.ToString());
return new InvalidBranch("Unknown opcode: " + cecilInst.OpCode.ToString());
}
}
@ -869,7 +869,7 @@ namespace ICSharpCode.Decompiler.IL @@ -869,7 +869,7 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction Peek()
{
if (currentStack.IsEmpty) {
return new InvalidInstruction("Stack underflow") { ILRange = GetCurrentInstructionInterval() };
return new InvalidExpression("Stack underflow") { ILRange = GetCurrentInstructionInterval() };
}
return new LdLoc(currentStack.Peek());
}
@ -877,7 +877,7 @@ namespace ICSharpCode.Decompiler.IL @@ -877,7 +877,7 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction Pop()
{
if (currentStack.IsEmpty) {
return new InvalidInstruction("Stack underflow") { ILRange = GetCurrentInstructionInterval() };
return new InvalidExpression("Stack underflow") { ILRange = GetCurrentInstructionInterval() };
}
ILVariable v;
currentStack = currentStack.Pop(out v);
@ -892,8 +892,8 @@ namespace ICSharpCode.Decompiler.IL @@ -892,8 +892,8 @@ namespace ICSharpCode.Decompiler.IL
inst = new Conv(inst, PrimitiveType.I, false, Sign.None);
} else if (expectedType == StackType.Ref && inst.ResultType == StackType.I) {
// implicitly start GC tracking
} else if (inst is InvalidInstruction) {
((InvalidInstruction)inst).ExpectedResultType = expectedType;
} else if (inst is InvalidExpression) {
((InvalidExpression)inst).ExpectedResultType = expectedType;
} else {
Warn($"Expected {expectedType}, but got {inst.ResultType}");
}

211
ICSharpCode.Decompiler/IL/Instructions.cs

@ -30,7 +30,9 @@ namespace ICSharpCode.Decompiler.IL @@ -30,7 +30,9 @@ namespace ICSharpCode.Decompiler.IL
public enum OpCode
{
/// <summary>Represents invalid IL. Semantically, this instruction is considered to throw some kind of exception.</summary>
InvalidInstruction,
InvalidBranch,
/// <summary>Represents invalid IL. Semantically, this instruction is considered to produce some kind of value.</summary>
InvalidExpression,
/// <summary>No operation. Takes 0 arguments and returns void.</summary>
Nop,
/// <summary>A container of IL blocks.</summary>
@ -156,6 +158,8 @@ namespace ICSharpCode.Decompiler.IL @@ -156,6 +158,8 @@ namespace ICSharpCode.Decompiler.IL
RefAnyType,
/// <summary>Push the address stored in a typed reference.</summary>
RefAnyValue,
/// <summary>Yield an element from an iterator.</summary>
YieldReturn,
/// <summary>Matches any node</summary>
AnyNode,
}
@ -448,36 +452,73 @@ namespace ICSharpCode.Decompiler.IL.Patterns @@ -448,36 +452,73 @@ namespace ICSharpCode.Decompiler.IL.Patterns
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Represents invalid IL. Semantically, this instruction is considered to throw some kind of exception.</summary>
public sealed partial class InvalidInstruction : SimpleInstruction
public sealed partial class InvalidBranch : SimpleInstruction
{
public InvalidInstruction() : base(OpCode.InvalidInstruction)
public InvalidBranch() : base(OpCode.InvalidBranch)
{
}
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable;
return InstructionFlags.MayThrow | InstructionFlags.SideEffect | InstructionFlags.EndPointUnreachable;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable;
return InstructionFlags.MayThrow | InstructionFlags.SideEffect | InstructionFlags.EndPointUnreachable;
}
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitInvalidInstruction(this);
visitor.VisitInvalidBranch(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitInvalidInstruction(this);
return visitor.VisitInvalidBranch(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitInvalidInstruction(this, context);
return visitor.VisitInvalidBranch(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as InvalidInstruction;
var o = other as InvalidBranch;
return o != null;
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Represents invalid IL. Semantically, this instruction is considered to produce some kind of value.</summary>
public sealed partial class InvalidExpression : SimpleInstruction
{
public InvalidExpression() : base(OpCode.InvalidExpression)
{
}
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.MayThrow | InstructionFlags.SideEffect;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayThrow | InstructionFlags.SideEffect;
}
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitInvalidExpression(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitInvalidExpression(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitInvalidExpression(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as InvalidExpression;
return o != null;
}
}
@ -2442,7 +2483,7 @@ namespace ICSharpCode.Decompiler.IL @@ -2442,7 +2483,7 @@ namespace ICSharpCode.Decompiler.IL
clone.Target = this.target.Clone();
return clone;
}
public bool DelayExceptions;
public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced
readonly IField field;
/// <summary>Returns the field operand.</summary>
public IField Field { get { return field; } }
@ -3406,7 +3447,7 @@ namespace ICSharpCode.Decompiler.IL @@ -3406,7 +3447,7 @@ namespace ICSharpCode.Decompiler.IL
clone.Indices.AddRange(this.Indices.Select(arg => arg.Clone()));
return clone;
}
public bool DelayExceptions;
public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced
public override StackType ResultType { get { return StackType.Ref; } }
/// <summary>Gets whether the 'readonly' prefix was applied to this instruction.</summary>
public bool IsReadOnly { get; set; }
@ -3665,6 +3706,98 @@ namespace ICSharpCode.Decompiler.IL @@ -3665,6 +3706,98 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Yield an element from an iterator.</summary>
public sealed partial class YieldReturn : ILInstruction
{
public YieldReturn(ILInstruction value) : base(OpCode.YieldReturn)
{
this.Value = value;
}
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
ILInstruction value;
public ILInstruction Value {
get { return this.value; }
set {
ValidateChild(value);
SetChildInstruction(ref this.value, value, 0);
}
}
protected sealed override int GetChildCount()
{
return 1;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index) {
case 0:
return this.value;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index) {
case 0:
this.Value = value;
break;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (YieldReturn)ShallowClone();
clone.Value = this.value.Clone();
return clone;
}
public override StackType ResultType { get { return StackType.Void; } }
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.MayBranch | InstructionFlags.SideEffect | value.Flags;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayBranch | InstructionFlags.SideEffect;
}
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
output.Write('(');
this.value.WriteTo(output);
output.Write(')');
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitYieldReturn(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitYieldReturn(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitYieldReturn(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as YieldReturn;
return o != null && this.value.PerformMatch(o.value, ref match);
}
}
}
namespace ICSharpCode.Decompiler.IL.Patterns
{
/// <summary>Matches any node</summary>
@ -3719,7 +3852,11 @@ namespace ICSharpCode.Decompiler.IL @@ -3719,7 +3852,11 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Called by Visit*() methods that were not overridden</summary>
protected abstract void Default(ILInstruction inst);
protected internal virtual void VisitInvalidInstruction(InvalidInstruction inst)
protected internal virtual void VisitInvalidBranch(InvalidBranch inst)
{
Default(inst);
}
protected internal virtual void VisitInvalidExpression(InvalidExpression inst)
{
Default(inst);
}
@ -3971,6 +4108,10 @@ namespace ICSharpCode.Decompiler.IL @@ -3971,6 +4108,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitYieldReturn(YieldReturn inst)
{
Default(inst);
}
}
/// <summary>
@ -3981,7 +4122,11 @@ namespace ICSharpCode.Decompiler.IL @@ -3981,7 +4122,11 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Called by Visit*() methods that were not overridden</summary>
protected abstract T Default(ILInstruction inst);
protected internal virtual T VisitInvalidInstruction(InvalidInstruction inst)
protected internal virtual T VisitInvalidBranch(InvalidBranch inst)
{
return Default(inst);
}
protected internal virtual T VisitInvalidExpression(InvalidExpression inst)
{
return Default(inst);
}
@ -4233,6 +4378,10 @@ namespace ICSharpCode.Decompiler.IL @@ -4233,6 +4378,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitYieldReturn(YieldReturn inst)
{
return Default(inst);
}
}
/// <summary>
@ -4243,7 +4392,11 @@ namespace ICSharpCode.Decompiler.IL @@ -4243,7 +4392,11 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Called by Visit*() methods that were not overridden</summary>
protected abstract T Default(ILInstruction inst, C context);
protected internal virtual T VisitInvalidInstruction(InvalidInstruction inst, C context)
protected internal virtual T VisitInvalidBranch(InvalidBranch inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitInvalidExpression(InvalidExpression inst, C context)
{
return Default(inst, context);
}
@ -4495,12 +4648,17 @@ namespace ICSharpCode.Decompiler.IL @@ -4495,12 +4648,17 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
protected internal virtual T VisitYieldReturn(YieldReturn inst, C context)
{
return Default(inst, context);
}
}
partial class InstructionOutputExtensions
{
static readonly string[] originalOpCodeNames = {
"invalid",
"invalid.branch",
"invalid.expr",
"nop",
"ILFunction",
"BlockContainer",
@ -4563,15 +4721,24 @@ namespace ICSharpCode.Decompiler.IL @@ -4563,15 +4721,24 @@ namespace ICSharpCode.Decompiler.IL
"mkrefany",
"refanytype",
"refanyval",
"yield.return",
"AnyNode",
};
}
partial class ILInstruction
{
public bool MatchInvalidInstruction()
public bool MatchInvalidBranch()
{
var inst = this as InvalidBranch;
if (inst != null) {
return true;
}
return false;
}
public bool MatchInvalidExpression()
{
var inst = this as InvalidInstruction;
var inst = this as InvalidExpression;
if (inst != null) {
return true;
}
@ -5023,6 +5190,16 @@ namespace ICSharpCode.Decompiler.IL @@ -5023,6 +5190,16 @@ namespace ICSharpCode.Decompiler.IL
type = default(IType);
return false;
}
public bool MatchYieldReturn(out ILInstruction value)
{
var inst = this as YieldReturn;
if (inst != null) {
value = inst.Value;
return true;
}
value = default(ILInstruction);
return false;
}
}
}

16
ICSharpCode.Decompiler/IL/Instructions.tt

@ -37,10 +37,12 @@ @@ -37,10 +37,12 @@
};
OpCode[] opCodes = {
new OpCode("invalid", "Represents invalid IL. Semantically, this instruction is considered to throw some kind of exception.",
CustomClassName("InvalidInstruction"), NoArguments, MayThrow, HasFlag("InstructionFlags.EndPointUnreachable")),
new OpCode("invalid.branch", "Represents invalid IL. Semantically, this instruction is considered to throw some kind of exception.",
CustomClassName("InvalidBranch"), NoArguments, MayThrow, SideEffect, HasFlag("InstructionFlags.EndPointUnreachable")),
new OpCode("invalid.expr", "Represents invalid IL. Semantically, this instruction is considered to produce some kind of value.",
CustomClassName("InvalidExpression"), NoArguments, MayThrow, SideEffect),
new OpCode("nop", "No operation. Takes 0 arguments and returns void.",
VoidResult, NoArguments),
VoidResult, NoArguments, CustomWriteTo),
new OpCode("ILFunction", "A container of IL blocks.",
CustomChildren(new [] {
new ChildInfo("body")
@ -211,6 +213,12 @@ @@ -211,6 +213,12 @@
new OpCode("refanyval", "Push the address stored in a typed reference.",
CustomClassName("RefAnyValue"), Unary, HasTypeOperand, MayThrow, ResultType("Ref")),
new OpCode("yield.return", "Yield an element from an iterator.",
MayBranch, // yield return may end up returning if the consumer disposes the iterator without
SideEffect, // consumer can have arbitrary side effects while we're yielding
CustomArguments("value"), VoidResult),
// note: "yield break" is always represented using a "leave" instruction
// patterns
new OpCode("AnyNode", "Matches any node", Pattern, CustomArguments(), CustomConstructor),
};
@ -552,7 +560,7 @@ namespace ICSharpCode.Decompiler.IL @@ -552,7 +560,7 @@ namespace ICSharpCode.Decompiler.IL
static Action<OpCode> ControlFlow = HasFlag("InstructionFlags.ControlFlow");
static Action<OpCode> MayThrowIfNotDelayed = HasFlag("(DelayExceptions ? InstructionFlags.None : InstructionFlags.MayThrow)") + (opCode => {
opCode.Members.Add("public bool DelayExceptions;");
opCode.Members.Add("public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced");
});
static Action<OpCode> BaseClass(string name)

13
ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs

@ -34,6 +34,15 @@ namespace ICSharpCode.Decompiler.IL @@ -34,6 +34,15 @@ namespace ICSharpCode.Decompiler.IL
public readonly MethodDefinition Method;
public readonly ILVariableCollection Variables;
/// <summary>
/// Gets whether this function is a decompiled iterator (is using yield).
/// This flag gets set by the YieldReturnDecompiler.
///
/// If set, the 'return' instruction has the semantics of 'yield break;'
/// instead of a normal return.
/// </summary>
public bool IsIterator;
public ILFunction(MethodDefinition method, ILInstruction body) : base(OpCode.ILFunction)
{
this.Body = body;
@ -65,6 +74,10 @@ namespace ICSharpCode.Decompiler.IL @@ -65,6 +74,10 @@ namespace ICSharpCode.Decompiler.IL
output.WriteLine(" {");
output.Indent();
if (IsIterator) {
output.WriteLine(".iterator");
}
output.MarkFoldStart(Variables.Count + " variable(s)", true);
foreach (var variable in Variables) {
variable.WriteDefinitionTo(output);

6
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -312,7 +312,7 @@ namespace ICSharpCode.Decompiler.IL @@ -312,7 +312,7 @@ namespace ICSharpCode.Decompiler.IL
readonly int end;
int pos;
public ChildrenEnumerator(ILInstruction inst)
internal ChildrenEnumerator(ILInstruction inst)
{
Debug.Assert(inst != null);
this.inst = inst;
@ -443,10 +443,10 @@ namespace ICSharpCode.Decompiler.IL @@ -443,10 +443,10 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
/// <remarks>
/// This property returns true if the ILInstruction is reachable from the root node
/// of the ILAst; it does make use of the <c>Parent</c> field so the considerations
/// of the ILAst; it does not make use of the <c>Parent</c> field so the considerations
/// about orphaned nodes and stale positions don't apply.
/// </remarks>
protected bool IsConnected {
protected internal bool IsConnected {
get { return refCount > 0; }
}

50
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -111,6 +111,17 @@ namespace ICSharpCode.Decompiler.IL @@ -111,6 +111,17 @@ namespace ICSharpCode.Decompiler.IL
var inst = this as Leave;
return inst != null && inst.TargetContainer == targetContainer;
}
public bool MatchReturn(out ILInstruction returnValue)
{
if (this is Return ret) {
returnValue = ret.ReturnValue;
return returnValue != null;
} else {
returnValue = null;
return false;
}
}
public bool MatchIfInstruction(out ILInstruction condition, out ILInstruction trueInst, out ILInstruction falseInst)
{
@ -216,28 +227,27 @@ namespace ICSharpCode.Decompiler.IL @@ -216,28 +227,27 @@ namespace ICSharpCode.Decompiler.IL
public bool MatchLdsFld(IField field)
{
LdsFlda ldsflda = (this as LdObj)?.Target as LdsFlda;
if (ldsflda != null) {
if (this is LdObj ldobj && ldobj.Target is LdsFlda ldsflda) {
return field.Equals(ldsflda.Field);
}
return false;
}
public bool MatchLdFld(out IField field)
public bool MatchLdFld(out ILInstruction target, out IField field)
{
LdFlda ldflda = (this as LdObj)?.Target as LdFlda;
if (ldflda != null) {
if (this is LdObj ldobj && ldobj.Target is LdFlda ldflda) {
target = ldflda.Target;
field = ldflda.Field;
return true;
}
target = null;
field = null;
return false;
}
public bool MatchLdsFld(out IField field)
{
LdsFlda ldsflda = (this as LdObj)?.Target as LdsFlda;
if (ldsflda != null) {
if (this is LdObj ldobj && ldobj.Target is LdsFlda ldsflda) {
field = ldsflda.Field;
return true;
}
@ -245,31 +255,29 @@ namespace ICSharpCode.Decompiler.IL @@ -245,31 +255,29 @@ namespace ICSharpCode.Decompiler.IL
return false;
}
public bool MatchStsFld(out ILInstruction value, out IField field)
{
var stobj = this as StObj;
LdsFlda ldsflda = stobj?.Target as LdsFlda;
if (ldsflda != null) {
value = stobj.Value;
public bool MatchStsFld(out IField field, out ILInstruction value)
{
if (this is StObj stobj && stobj.Target is LdsFlda ldsflda) {
field = ldsflda.Field;
value = stobj.Value;
return true;
}
value = null;
field = null;
value = null;
return false;
}
public bool MatchStFld(out ILInstruction value, out IField field)
{
var stobj = this as StObj;
LdFlda ldflda = stobj?.Target as LdFlda;
if (ldflda != null) {
value = stobj.Value;
public bool MatchStFld(out ILInstruction target, out IField field, out ILInstruction value)
{
if (this is StObj stobj && stobj.Target is LdFlda ldflda) {
target = ldflda.Target;
field = ldflda.Field;
value = stobj.Value;
return true;
}
value = null;
target = null;
field = null;
value = null;
return false;
}

43
ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs

@ -31,12 +31,25 @@ namespace ICSharpCode.Decompiler.IL @@ -31,12 +31,25 @@ namespace ICSharpCode.Decompiler.IL
}
}
partial class InvalidInstruction : SimpleInstruction
partial class Nop
{
public string Comment;
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
if (!string.IsNullOrEmpty(Comment)) {
output.Write(" // " + Comment);
}
}
}
partial class InvalidBranch : SimpleInstruction
{
public string Message;
public StackType ExpectedResultType = StackType.Unknown;
public InvalidInstruction(string message) : this()
public InvalidBranch(string message) : this()
{
this.Message = message;
}
@ -55,4 +68,30 @@ namespace ICSharpCode.Decompiler.IL @@ -55,4 +68,30 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
partial class InvalidExpression : SimpleInstruction
{
public string Message;
public StackType ExpectedResultType = StackType.Unknown;
public InvalidExpression(string message) : this()
{
this.Message = message;
}
public override StackType ResultType
{
get { return ExpectedResultType; }
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
if (!string.IsNullOrEmpty(Message)) {
output.Write('(');
output.Write(Message);
output.Write(')');
}
}
}
}

9
ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs

@ -67,7 +67,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -67,7 +67,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var storeInst = trueInst.Instructions[0];
if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdsFld(out IField field) || !right.MatchLdNull())
return false;
if (!storeInst.MatchStsFld(out ILInstruction value, out IField field2) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
if (!storeInst.MatchStsFld(out IField field2, out ILInstruction value) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
if (!DelegateConstruction.IsDelegateConstruction(value as NewObj, true))
return false;
@ -106,7 +106,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -106,7 +106,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// the optional field store was moved into storeInst by inline assignment:
if (!(value is NewObj)) {
IField field, field2;
if (value.MatchStsFld(out value2, out field)) {
if (value.MatchStsFld(out field, out value2)) {
if (!(value2 is NewObj) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
var storeBeforeIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex - 1) as StLoc;
@ -114,11 +114,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -114,11 +114,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
value = value2;
hasFieldStore = true;
} else if (value.MatchStFld(out value2, out field)) {
} else if (value.MatchStFld(out var target, out field, out value2)) {
// TODO: shouldn't we test 'target'?
if (!(value2 is NewObj) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
var storeBeforeIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex - 1) as StLoc;
if (storeBeforeIf == null || storeBeforeIf.Variable != v || !storeBeforeIf.Value.MatchLdFld(out field2) || !field.Equals(field2))
if (storeBeforeIf == null || storeBeforeIf.Variable != v || !storeBeforeIf.Value.MatchLdFld(out var target2, out field2) || !field.Equals(field2))
return false;
value = value2;
hasFieldStore = true;

36
ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs

@ -16,45 +16,19 @@ @@ -16,45 +16,19 @@
// 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;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Repeats the child transforms until the ILAst no longer changes.
/// </summary>
public class LoopingTransform : IILTransform
{
readonly IILTransform[] children;
public LoopingTransform(params IILTransform[] children)
{
this.children = children;
}
public void Run(ILFunction function, ILTransformContext context)
{
do {
function.ResetDirty();
function.RunTransforms(children, context);
if (function.IsDirty)
context.Step("Function is dirty; running another loop iteration.", function);
} while (function.IsDirty);
}
public IReadOnlyCollection<IILTransform> Transforms
{
get { return children; }
}
}
/// <summary>
/// Repeats the child transforms until the ILAst no longer changes.
/// </summary>
public class LoopingBlockTransform : IBlockTransform
{
readonly IBlockTransform[] children;
bool running;
public LoopingBlockTransform(params IBlockTransform[] children)
{
this.children = children;
@ -62,12 +36,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -62,12 +36,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public void Run(Block block, BlockTransformContext context)
{
if (running)
throw new InvalidOperationException("LoopingBlockTransform already running. Transforms (and the CSharpDecompiler) are neither neither thread-safe nor re-entrant.");
running = true;
do {
block.ResetDirty();
block.RunTransforms(children, context);
if (block.IsDirty)
context.Step("Block is dirty; running another loop iteration.", block);
} while (block.IsDirty);
running = false;
}
public IReadOnlyCollection<IBlockTransform> Transforms {

2
ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs

@ -251,7 +251,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -251,7 +251,7 @@ namespace ICSharpCode.Decompiler.Tests
{
if (asm == null)
return false;
return !localAssemblies.Contains(asm.MainModule.FullyQualifiedName);
return !localAssemblies.Contains(asm.MainModule.FileName);
}
}

45
ICSharpCode.Decompiler/Tests/TestCases/Pretty/YieldReturn.cs

@ -21,8 +21,10 @@ using System.Collections.Generic; @@ -21,8 +21,10 @@ using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public static class YieldReturn
public class YieldReturn
{
int fieldOnThis;
public static IEnumerable<string> SimpleYieldReturn()
{
yield return "A";
@ -30,6 +32,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -30,6 +32,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
yield return "C";
}
public static IEnumerator<string> SimpleYieldReturnEnumerator()
{
yield return "A";
yield return "B";
yield return "C";
}
public IEnumerable<int> YieldReturnParameters(int p)
{
yield return p;
yield return fieldOnThis;
}
public IEnumerator<int> YieldReturnParametersEnumerator(int p)
{
yield return p;
yield return fieldOnThis;
}
public static IEnumerable<int> YieldReturnInLoop()
{
for (int i = 0; i < 100; i++) {
@ -147,5 +168,27 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -147,5 +168,27 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
yield return 'b';
yield return 'c';
}
public static IEnumerable<char> ExceptionHandling()
{
yield return 'a';
try {
Console.WriteLine("1 - try");
} catch (Exception) {
Console.WriteLine("1 - catch");
}
yield return 'b';
try {
try {
Console.WriteLine("2 - try");
} finally {
Console.WriteLine("2 - finally");
}
yield return 'c';
} finally {
Console.WriteLine("outer finally");
}
}
}
}

8
ICSharpCode.Decompiler/TypeSystem/CecilLoader.cs

@ -176,9 +176,9 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -176,9 +176,9 @@ namespace ICSharpCode.Decompiler.TypeSystem
moduleAttributes = interningProvider.InternList(moduleAttributes);
this.currentAssembly = new DefaultUnresolvedAssembly(assemblyDefinition != null ? assemblyDefinition.Name.FullName : moduleDefinition.Name);
currentAssembly.Location = moduleDefinition.FullyQualifiedName;
ExtensionMethods.AddRange(currentAssembly.AssemblyAttributes, assemblyAttributes);
ExtensionMethods.AddRange(currentAssembly.ModuleAttributes, assemblyAttributes);
currentAssembly.Location = moduleDefinition.FileName;
currentAssembly.AssemblyAttributes.AddRange(assemblyAttributes);
currentAssembly.ModuleAttributes.AddRange(assemblyAttributes);
// Register type forwarders:
foreach (ExportedType type in moduleDefinition.ExportedTypes) {
@ -831,7 +831,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -831,7 +831,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
return; // https://github.com/icsharpcode/SharpDevelop/issues/284
}
var blobSecDecl = new UnresolvedSecurityDeclarationBlob((int)secDecl.Action, blob);
ExtensionMethods.AddRange(targetCollection, blobSecDecl.UnresolvedAttributes);
targetCollection.AddRange(blobSecDecl.UnresolvedAttributes);
}
#endregion
#endregion

2
ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultUnresolvedAttribute.cs

@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
if (attributeType == null)
throw new ArgumentNullException("attributeType");
this.attributeType = attributeType;
ExtensionMethods.AddRange(this.ConstructorParameterTypes, constructorParameterTypes);
this.ConstructorParameterTypes.AddRange(constructorParameterTypes);
}
protected override void FreezeInternal()

27
ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs

@ -787,5 +787,32 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -787,5 +787,32 @@ namespace ICSharpCode.Decompiler.TypeSystem
return null;
}
#endregion
public static IType GetElementTypeFromIEnumerable(this IType collectionType, ICompilation compilation, bool allowIEnumerator, out bool? isGeneric)
{
bool foundNonGenericIEnumerable = false;
foreach (IType baseType in collectionType.GetAllBaseTypes()) {
ITypeDefinition baseTypeDef = baseType.GetDefinition();
if (baseTypeDef != null) {
KnownTypeCode typeCode = baseTypeDef.KnownTypeCode;
if (typeCode == KnownTypeCode.IEnumerableOfT || (allowIEnumerator && typeCode == KnownTypeCode.IEnumeratorOfT)) {
ParameterizedType pt = baseType as ParameterizedType;
if (pt != null) {
isGeneric = true;
return pt.GetTypeArgument(0);
}
}
if (typeCode == KnownTypeCode.IEnumerable || (allowIEnumerator && typeCode == KnownTypeCode.IEnumerator))
foundNonGenericIEnumerable = true;
}
}
// System.Collections.IEnumerable found in type hierarchy -> Object is element type.
if (foundNonGenericIEnumerable) {
isGeneric = false;
return compilation.FindType(KnownTypeCode.Object);
}
isGeneric = null;
return SpecialType.UnknownType;
}
}
}

6
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -5,6 +5,12 @@ namespace ICSharpCode.Decompiler.Util @@ -5,6 +5,12 @@ namespace ICSharpCode.Decompiler.Util
{
static class CollectionExtensions
{
public static void Deconstruct<K, V>(this KeyValuePair<K, V> pair, out K key, out V value)
{
key = pair.Key;
value = pair.Value;
}
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> input)
{
return new HashSet<T>(input);

6
ICSharpCode.Decompiler/Util/ExtensionMethods.cs

@ -26,12 +26,6 @@ namespace ICSharpCode.Decompiler.Util @@ -26,12 +26,6 @@ namespace ICSharpCode.Decompiler.Util
/// </summary>
static class ExtensionMethods
{
public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> input)
{
foreach (T item in input)
target.Add(item);
}
public static Predicate<T> And<T>(this Predicate<T> filter1, Predicate<T> filter2)
{
if (filter1 == null)

52
ICSharpCode.Decompiler/Util/LongSet.cs

@ -29,12 +29,21 @@ namespace ICSharpCode.Decompiler.Util @@ -29,12 +29,21 @@ namespace ICSharpCode.Decompiler.Util
/// </summary>
public struct LongSet : IEquatable<LongSet>
{
/// <summary>
/// The intervals in this set of longs.
/// </summary>
/// <remarks>
/// Invariant: the intervals in this array are non-empty, non-overlapping, non-touching, and sorted.
///
/// This invariant ensures every LongSet is always in a normalized representation.
/// </remarks>
public readonly ImmutableArray<LongInterval> Intervals;
public LongSet(ImmutableArray<LongInterval> intervals)
private LongSet(ImmutableArray<LongInterval> intervals)
{
this.Intervals = intervals;
#if DEBUG
// Check invariant
long minValue = long.MinValue;
for (int i = 0; i < intervals.Length; i++) {
Debug.Assert(!intervals[i].IsEmpty);
@ -57,7 +66,7 @@ namespace ICSharpCode.Decompiler.Util @@ -57,7 +66,7 @@ namespace ICSharpCode.Decompiler.Util
: this(ImmutableArray.Create(LongInterval.Inclusive(value, value)))
{
}
/// <summary>
/// Create a new LongSet that contains the values from the interval.
/// </summary>
@ -66,6 +75,14 @@ namespace ICSharpCode.Decompiler.Util @@ -66,6 +75,14 @@ namespace ICSharpCode.Decompiler.Util
{
}
/// <summary>
/// Creates a new LongSet the contains the values from the specified intervals.
/// </summary>
public LongSet(IEnumerable<LongInterval> intervals)
: this(MergeOverlapping(intervals.Where(i => !i.IsEmpty).OrderBy(i => i.Start)).ToImmutableArray())
{
}
/// <summary>
/// The empty LongSet.
/// </summary>
@ -233,7 +250,34 @@ namespace ICSharpCode.Decompiler.Util @@ -233,7 +250,34 @@ namespace ICSharpCode.Decompiler.Util
}
return new LongSet(newIntervals.ToImmutableArray());
}
/// <summary>
/// Gets whether this set is a subset of other, or equal.
/// </summary>
public bool IsSubsetOf(LongSet other)
{
// TODO: optimize IsSubsetOf -- there's no need to build a temporary set
return this.UnionWith(other).SetEquals(other);
}
/// <summary>
/// Gets whether this set is a superset of other, or equal.
/// </summary>
public bool IsSupersetOf(LongSet other)
{
return other.IsSubsetOf(this);
}
public bool IsProperSubsetOf(LongSet other)
{
return IsSubsetOf(other) && !SetEquals(other);
}
public bool IsProperSupersetOf(LongSet other)
{
return IsSupersetOf(other) && !SetEquals(other);
}
public bool Contains(long val)
{
int index = upper_bound(val);

12
ILSpy/ILSpyTraceListener.cs

@ -21,6 +21,7 @@ using System.Collections.Generic; @@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using ICSharpCode.ILSpy.Controls;
using System.Linq;
namespace ICSharpCode.ILSpy
{
@ -49,12 +50,19 @@ namespace ICSharpCode.ILSpy @@ -49,12 +50,19 @@ namespace ICSharpCode.ILSpy
public override void Fail(string message, string detailMessage)
{
base.Fail(message, detailMessage); // let base class write the assert to the debug console
string topFrame = "";
string stackTrace = "";
try {
stackTrace = new StackTrace(true).ToString();
var frames = stackTrace.Split('\r', '\n')
.Where(f => f.Length > 0)
.SkipWhile(f => f.Contains("ILSpyTraceListener") || f.Contains("System.Diagnostics"))
.ToList();
topFrame = frames[0];
stackTrace = string.Join(Environment.NewLine, frames);
} catch { }
lock (ignoredStacks) {
if (ignoredStacks.Contains(stackTrace))
if (ignoredStacks.Contains(topFrame))
return;
if (dialogIsOpen)
return;
@ -76,7 +84,7 @@ namespace ICSharpCode.ILSpy @@ -76,7 +84,7 @@ namespace ICSharpCode.ILSpy
} else if (result == 2) { // ignore
} else if (result == 3) {
lock (ignoredStacks) {
ignoredStacks.Add(stackTrace);
ignoredStacks.Add(topFrame);
}
}
}

4
ILSpy/XmlDoc/XmlDocLoader.cs

@ -54,9 +54,9 @@ namespace ICSharpCode.ILSpy.XmlDoc @@ -54,9 +54,9 @@ namespace ICSharpCode.ILSpy.XmlDoc
lock (cache) {
XmlDocumentationProvider xmlDoc;
if (!cache.TryGetValue(module, out xmlDoc)) {
string xmlDocFile = LookupLocalizedXmlDoc(module.FullyQualifiedName);
string xmlDocFile = LookupLocalizedXmlDoc(module.FileName);
if (xmlDocFile == null) {
xmlDocFile = FindXmlDocumentation(Path.GetFileName(module.FullyQualifiedName), module.Runtime);
xmlDocFile = FindXmlDocumentation(Path.GetFileName(module.FileName), module.Runtime);
}
if (xmlDocFile != null) {
xmlDoc = new XmlDocumentationProvider(xmlDocFile);

Loading…
Cancel
Save