Browse Source

Merge branch 'master' of git://github.com/icsharpcode/ILSpy into Debugger

pull/191/merge
Eusebiu Marcu 15 years ago
parent
commit
4472205081
  1. 5
      Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs
  2. 35
      ICSharpCode.Decompiler/Ast/AstBuilder.cs
  3. 9
      ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
  4. 1
      ICSharpCode.Decompiler/Ast/DecompilerContext.cs
  5. 3
      ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs
  6. 55
      ICSharpCode.Decompiler/DecompilerSettings.cs
  7. 3
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  8. 117
      ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs
  9. 20
      ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs
  10. 120
      ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs
  11. 17
      ICSharpCode.Decompiler/ILAst/ILAstTypes.cs
  12. 3
      ICSharpCode.Decompiler/ILAst/ILCodes.cs
  13. 19
      ICSharpCode.Decompiler/ILAst/Pattern.cs
  14. 942
      ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
  15. 1
      ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
  16. 103
      ICSharpCode.Decompiler/Tests/YieldReturn.cs
  17. 5
      ILSpy/CSharpLanguage.cs
  18. 11
      ILSpy/DecompilationOptions.cs
  19. 9
      ILSpy/DecompilerSettingsPanel.xaml
  20. 67
      ILSpy/DecompilerSettingsPanel.xaml.cs
  21. 3
      ILSpy/ILAstLanguage.cs
  22. 10
      ILSpy/ILSpy.csproj
  23. 16
      ILSpy/MainWindow.xaml.cs
  24. 21
      ILSpy/OptionsDialog.xaml
  25. 95
      ILSpy/OptionsDialog.xaml.cs

5
Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs

@ -4,6 +4,7 @@ using System; @@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Media;
@ -159,9 +160,9 @@ namespace ILSpy.Debugger.Models.TreeModel @@ -159,9 +160,9 @@ namespace ILSpy.Debugger.Models.TreeModel
}
// get local variable index
List<ILVariable> list;
IEnumerable<ILVariable> list;
if (ILAstBuilder.MemberLocalVariables.TryGetValue(token, out list)) {
var variable = list.Find(v => v.Name == targetName);
var variable = list.FirstOrDefault(v => v.Name == targetName);
if (variable != null) {
if (expression is MemberReferenceExpression) {
var memberExpression = (MemberReferenceExpression)expression;

35
ICSharpCode.Decompiler/Ast/AstBuilder.cs

@ -33,19 +33,27 @@ namespace ICSharpCode.Decompiler.Ast @@ -33,19 +33,27 @@ namespace ICSharpCode.Decompiler.Ast
this.context = context;
}
public static bool MemberIsHidden(MemberReference member)
public static bool MemberIsHidden(MemberReference member, DecompilerSettings settings)
{
MethodDefinition method = member as MethodDefinition;
if (method != null && (method.IsGetter || method.IsSetter || method.IsAddOn || method.IsRemoveOn))
return true;
if (method != null && method.Name.StartsWith("<", StringComparison.Ordinal) && method.IsCompilerGenerated())
return true;
if (method != null) {
if (method.IsGetter || method.IsSetter || method.IsAddOn || method.IsRemoveOn)
return true;
if (settings.AnonymousMethods && method.Name.StartsWith("<", StringComparison.Ordinal) && method.IsCompilerGenerated())
return true;
}
TypeDefinition type = member as TypeDefinition;
if (type != null && type.DeclaringType != null && type.Name.StartsWith("<>c__DisplayClass", StringComparison.Ordinal) && type.IsCompilerGenerated())
return true;
if (type != null && type.DeclaringType != null) {
if (settings.AnonymousMethods && type.Name.StartsWith("<>c__DisplayClass", StringComparison.Ordinal) && type.IsCompilerGenerated())
return true;
if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type))
return true;
}
FieldDefinition field = member as FieldDefinition;
if (field != null && field.Name.StartsWith("CS$<>", StringComparison.Ordinal) && field.IsCompilerGenerated())
return true;
if (field != null) {
if (settings.AnonymousMethods && field.Name.StartsWith("CS$<>", StringComparison.Ordinal) && field.IsCompilerGenerated())
return true;
}
return false;
}
@ -150,6 +158,8 @@ namespace ICSharpCode.Decompiler.Ast @@ -150,6 +158,8 @@ namespace ICSharpCode.Decompiler.Ast
}
// create type
TypeDefinition oldCurrentType = context.CurrentType;
context.CurrentType = typeDef;
TypeDeclaration astType = new TypeDeclaration();
astType.AddAnnotation(typeDef);
astType.Modifiers = ConvertModifiers(typeDef);
@ -176,7 +186,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -176,7 +186,7 @@ namespace ICSharpCode.Decompiler.Ast
// Nested types
foreach(TypeDefinition nestedTypeDef in typeDef.NestedTypes) {
if (MemberIsHidden(nestedTypeDef))
if (MemberIsHidden(nestedTypeDef, context.Settings))
continue;
astType.AddChild(CreateType(nestedTypeDef), TypeDeclaration.MemberRole);
}
@ -215,6 +225,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -215,6 +225,7 @@ namespace ICSharpCode.Decompiler.Ast
}
ConvertAttributes(astType, typeDef);
context.CurrentType = oldCurrentType;
return astType;
}
@ -492,7 +503,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -492,7 +503,7 @@ namespace ICSharpCode.Decompiler.Ast
{
// Add fields
foreach(FieldDefinition fieldDef in typeDef.Fields) {
if (MemberIsHidden(fieldDef)) continue;
if (MemberIsHidden(fieldDef, context.Settings)) continue;
astType.AddChild(CreateField(fieldDef), TypeDeclaration.MemberRole);
}
@ -515,7 +526,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -515,7 +526,7 @@ namespace ICSharpCode.Decompiler.Ast
// Add methods
foreach(MethodDefinition methodDef in typeDef.Methods) {
if (methodDef.IsConstructor || MemberIsHidden(methodDef)) continue;
if (methodDef.IsConstructor || MemberIsHidden(methodDef, context.Settings)) continue;
astType.AddChild(CreateMethod(methodDef), TypeDeclaration.MemberRole);
}

9
ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs

@ -63,7 +63,8 @@ namespace ICSharpCode.Decompiler.Ast @@ -63,7 +63,8 @@ namespace ICSharpCode.Decompiler.Ast
bodyGraph.Optimize(context, ilMethod);
context.CancellationToken.ThrowIfCancellationRequested();
NameVariables.AssignNamesToVariables(methodDef.Parameters.Select(p => p.Name), astBuilder.Variables, ilMethod);
var allVariables = ilMethod.GetSelfAndChildrenRecursive<ILExpression>().Select(e => e.Operand as ILVariable).Where(v => v != null && !v.IsGenerated).Distinct();
NameVariables.AssignNamesToVariables(methodDef.Parameters.Select(p => p.Name), allVariables, ilMethod);
context.CancellationToken.ThrowIfCancellationRequested();
Ast.BlockStatement astBlock = TransformBlock(ilMethod);
@ -75,7 +76,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -75,7 +76,7 @@ namespace ICSharpCode.Decompiler.Ast
// store the variables - used for debugger
int token = methodDef.MetadataToken.ToInt32();
ILAstBuilder.MemberLocalVariables.AddOrUpdate(
token, astBuilder.Variables, (key, oldValue) => astBuilder.Variables);
token, allVariables, (key, oldValue) => allVariables);
return astBlock;
}
@ -499,6 +500,10 @@ namespace ICSharpCode.Decompiler.Ast @@ -499,6 +500,10 @@ namespace ICSharpCode.Decompiler.Ast
case ILCode.Throw: return new Ast.ThrowStatement { Expression = arg1 };
case ILCode.Unaligned: return InlineAssembly(byteCode, args);
case ILCode.Volatile: return InlineAssembly(byteCode, args);
case ILCode.YieldBreak:
return new Ast.YieldBreakStatement();
case ILCode.YieldReturn:
return new Ast.YieldStatement { Expression = arg1 };
default: throw new Exception("Unknown OpCode: " + byteCode.Code);
}
}

1
ICSharpCode.Decompiler/Ast/DecompilerContext.cs

@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler @@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler
public CancellationToken CancellationToken;
public TypeDefinition CurrentType;
public MethodDefinition CurrentMethod;
public DecompilerSettings Settings = new DecompilerSettings();
public DecompilerContext Clone()
{

3
ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs

@ -105,6 +105,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -105,6 +105,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef)
{
if (!context.Settings.AnonymousMethods)
return false; // anonymous method decompilation is disabled
// Anonymous methods are defined in the same assembly, so there's no need to Resolve().
MethodDefinition method = methodRef as MethodDefinition;
if (!IsAnonymousMethod(context, method))

55
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.ComponentModel;
namespace ICSharpCode.Decompiler
{
/// <summary>
/// Settings for the decompiler.
/// </summary>
public class DecompilerSettings : INotifyPropertyChanged
{
bool anonymousMethods = true;
/// <summary>
/// Decompile anonymous methods/lambdas.
/// </summary>
public bool AnonymousMethods {
get { return anonymousMethods; }
set {
if (anonymousMethods != value) {
anonymousMethods = value;
OnPropertyChanged("AnonymousMethods");
}
}
}
bool yieldReturn = true;
/// <summary>
/// Decompile enumerators.
/// </summary>
public bool YieldReturn {
get { return yieldReturn; }
set {
if (yieldReturn != value) {
yieldReturn = value;
OnPropertyChanged("YieldReturn");
}
}
}
public event EventHandler YieldReturnChanged;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

3
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -68,6 +68,7 @@ @@ -68,6 +68,7 @@
<Compile Include="CecilExtensions.cs" />
<Compile Include="CodeMappings.cs" />
<Compile Include="DecompilerException.cs" />
<Compile Include="DecompilerSettings.cs" />
<Compile Include="Disassembler\DisassemblerHelpers.cs" />
<Compile Include="Disassembler\ILCodeMapping.cs" />
<Compile Include="Disassembler\ILStructure.cs" />
@ -89,6 +90,7 @@ @@ -89,6 +90,7 @@
<Compile Include="FlowAnalysis\TransformToSsa.cs" />
<Compile Include="GraphVizGraph.cs" />
<Compile Include="ILAst\ArrayInitializers.cs" />
<Compile Include="ILAst\DefaultDictionary.cs" />
<Compile Include="ILAst\GotoRemoval.cs" />
<Compile Include="ILAst\ILAstBuilder.cs" />
<Compile Include="ILAst\ILAstOptimizer.cs" />
@ -98,6 +100,7 @@ @@ -98,6 +100,7 @@
<Compile Include="ILAst\Pattern.cs" />
<Compile Include="ILAst\PeepholeTransform.cs" />
<Compile Include="ILAst\TypeAnalysis.cs" />
<Compile Include="ILAst\YieldReturnDecompiler.cs" />
<Compile Include="ITextOutput.cs" />
<Compile Include="PlainTextOutput.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

117
ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs

@ -0,0 +1,117 @@ @@ -0,0 +1,117 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections;
using System.Collections.Generic;
namespace ICSharpCode.Decompiler.ILAst
{
/// <summary>
/// Dictionary with default values.
/// </summary>
sealed class DefaultDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
readonly IDictionary<TKey, TValue> dict;
readonly Func<TKey, TValue> defaultProvider;
public DefaultDictionary(TValue defaultValue, IDictionary<TKey, TValue> dictionary = null)
: this(key => defaultValue, dictionary)
{
}
public DefaultDictionary(Func<TKey, TValue> defaultProvider = null, IDictionary<TKey, TValue> dictionary = null)
{
this.dict = dictionary ?? new Dictionary<TKey, TValue>();
this.defaultProvider = defaultProvider ?? (key => default(TValue));
}
public TValue this[TKey key] {
get {
TValue val;
if (dict.TryGetValue(key, out val))
return val;
else
return dict[key] = defaultProvider(key);
}
set {
dict[key] = value;
}
}
public ICollection<TKey> Keys {
get { return dict.Keys; }
}
public ICollection<TValue> Values {
get { return dict.Values; }
}
public int Count {
get { return dict.Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
get { return false; }
}
public bool ContainsKey(TKey key)
{
return dict.ContainsKey(key);
}
public void Add(TKey key, TValue value)
{
dict.Add(key, value);
}
public bool Remove(TKey key)
{
return dict.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return dict.TryGetValue(key, out value);
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
dict.Add(item);
}
public void Clear()
{
dict.Clear();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
return dict.Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
dict.CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
return dict.Remove(item);
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return dict.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return dict.GetEnumerator();
}
}
}

20
ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs

@ -15,7 +15,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -15,7 +15,7 @@ namespace ICSharpCode.Decompiler.ILAst
/// <summary>
///
/// </summary>
public static ConcurrentDictionary<int, List<ILVariable>> MemberLocalVariables = new ConcurrentDictionary<int, List<ILVariable>>();
public static ConcurrentDictionary<int, IEnumerable<ILVariable>> MemberLocalVariables = new ConcurrentDictionary<int, IEnumerable<ILVariable>>();
static ByteCode[] EmptyByteCodeArray = new ByteCode[] {};
@ -214,8 +214,6 @@ namespace ICSharpCode.Decompiler.ILAst @@ -214,8 +214,6 @@ namespace ICSharpCode.Decompiler.ILAst
// Virtual instructions to load exception on stack
Dictionary<ExceptionHandler, ByteCode> ldexceptions = new Dictionary<ExceptionHandler, ILAstBuilder.ByteCode>();
public List<ILVariable> Variables;
public List<ILNode> Build(MethodDefinition methodDef, bool optimize)
{
this.methodDef = methodDef;
@ -456,7 +454,6 @@ namespace ICSharpCode.Decompiler.ILAst @@ -456,7 +454,6 @@ namespace ICSharpCode.Decompiler.ILAst
{
if (optimize) {
int varCount = methodDef.Body.Variables.Count;
this.Variables = new List<ILVariable>(varCount * 2);
for(int variableIndex = 0; variableIndex < varCount; variableIndex++) {
// Find all stores and loads for this variable
@ -528,16 +525,13 @@ namespace ICSharpCode.Decompiler.ILAst @@ -528,16 +525,13 @@ namespace ICSharpCode.Decompiler.ILAst
load.Operand = newVar.Variable;
}
}
// Record new variables to global list
this.Variables.AddRange(newVars.Select(v => v.Variable));
}
} else {
this.Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList();
var variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList();
foreach(ByteCode byteCode in body) {
if (byteCode.Code == ILCode.Ldloc || byteCode.Code == ILCode.Stloc || byteCode.Code == ILCode.Ldloca) {
int index = ((VariableDefinition)byteCode.Operand).Index;
byteCode.Operand = this.Variables[index];
byteCode.Operand = variables[index];
}
}
}
@ -616,9 +610,10 @@ namespace ICSharpCode.Decompiler.ILAst @@ -616,9 +610,10 @@ namespace ICSharpCode.Decompiler.ILAst
tryCatchBlock.CatchBlocks.Add(catchBlock);
} else if (eh.HandlerType == ExceptionHandlerType.Finally) {
tryCatchBlock.FinallyBlock = new ILBlock(handlerAst);
// TODO: ldexception
} else if (eh.HandlerType == ExceptionHandlerType.Fault) {
tryCatchBlock.FaultBlock = new ILBlock(handlerAst);
} else {
// TODO
// TODO: ExceptionHandlerType.Filter
}
}
@ -710,9 +705,6 @@ namespace ICSharpCode.Decompiler.ILAst @@ -710,9 +705,6 @@ namespace ICSharpCode.Decompiler.ILAst
currExpr.Arguments[0].ILRanges.AddRange(currExpr.ILRanges);
currExpr.Arguments[0].ILRanges.AddRange(nextExpr.Arguments[j].ILRanges);
// Remove from global list, if present
this.Variables.Remove((ILVariable)arg.Operand);
ast.RemoveAt(i);
nextExpr.Arguments[j] = currExpr.Arguments[0]; // Inline the stloc body
i -= 2; // Try the same index again

120
ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs

@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler.ILAst
public enum ILAstOptimizationStep
{
ReduceBranchInstructionSet,
YieldReturn,
SplitToMovableBlocks,
PeepholeOptimizations,
FindLoops,
@ -38,6 +39,9 @@ namespace ICSharpCode.Decompiler.ILAst @@ -38,6 +39,9 @@ namespace ICSharpCode.Decompiler.ILAst
ReduceBranchInstructionSet(block);
}
if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return;
YieldReturnDecompiler.Run(context, method);
if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return;
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>().ToList()) {
SplitToBasicBlocks(block);
@ -106,17 +110,17 @@ namespace ICSharpCode.Decompiler.ILAst @@ -106,17 +110,17 @@ namespace ICSharpCode.Decompiler.ILAst
expr.Arguments.Single().ILRanges.AddRange(expr.ILRanges);
expr.ILRanges.Clear();
continue;
case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break;
case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break;
case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break;
case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break;
case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break;
case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break;
case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break;
case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break;
case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break;
case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break;
case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break;
case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break;
case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break;
case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break;
case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break;
case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break;
case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break;
case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break;
case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break;
case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break;
case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break;
case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break;
default:
continue;
}
@ -176,7 +180,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -176,7 +180,7 @@ namespace ICSharpCode.Decompiler.ILAst
lastBlock.FallthoughGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel);
}
} else {
basicBlock.Body.Add(currNode);
basicBlock.Body.Add(currNode);
}
}
}
@ -226,7 +230,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -226,7 +230,7 @@ namespace ICSharpCode.Decompiler.ILAst
if (TrySimplifyShortCircuit(block.Body, bb)) {
modified = true;
continue;
}
}
if (TrySimplifyTernaryOperator(block.Body, bb)) {
modified = true;
continue;
@ -462,7 +466,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -462,7 +466,7 @@ namespace ICSharpCode.Decompiler.ILAst
labelToCfNode.TryGetValue(trueLabel, out trueTarget);
ControlFlowNode falseTarget;
labelToCfNode.TryGetValue(falseLabel, out falseTarget);
// If one point inside the loop and the other outside
if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) ||
(loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) )
@ -491,36 +495,36 @@ namespace ICSharpCode.Decompiler.ILAst @@ -491,36 +495,36 @@ namespace ICSharpCode.Decompiler.ILAst
// Use loop to implement the condition
result.Add(new ILBasicBlock() {
EntryLabel = basicBlock.EntryLabel,
Body = new List<ILNode>() {
new ILWhileLoop() {
Condition = condExpr,
BodyBlock = new ILBlock() {
EntryGoto = new ILExpression(ILCode.Br, trueLabel),
Body = FindLoops(loopContents, node, true)
}
},
new ILExpression(ILCode.Br, falseLabel)
},
FallthoughGoto = null
});
EntryLabel = basicBlock.EntryLabel,
Body = new List<ILNode>() {
new ILWhileLoop() {
Condition = condExpr,
BodyBlock = new ILBlock() {
EntryGoto = new ILExpression(ILCode.Br, trueLabel),
Body = FindLoops(loopContents, node, true)
}
},
new ILExpression(ILCode.Br, falseLabel)
},
FallthoughGoto = null
});
}
}
// Fallback method: while(true)
if (scope.Contains(node)) {
result.Add(new ILBasicBlock() {
EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) },
Body = new List<ILNode>() {
new ILWhileLoop() {
BodyBlock = new ILBlock() {
EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel),
Body = FindLoops(loopContents, node, true)
}
},
},
FallthoughGoto = null
});
EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) },
Body = new List<ILNode>() {
new ILWhileLoop() {
BodyBlock = new ILBlock() {
EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel),
Body = FindLoops(loopContents, node, true)
}
},
},
FallthoughGoto = null
});
}
// Move the content into loop block
@ -639,7 +643,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -639,7 +643,7 @@ namespace ICSharpCode.Decompiler.ILAst
var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) };
ilSwitch.CaseBlocks.Add(caseBlock);
newBB.FallthoughGoto = null;
scope.ExceptWith(content);
caseBlock.Body.AddRange(FindConditions(content, fallTarget));
// Add explicit break which should not be used by default, but the goto removal might decide to use it
@ -667,9 +671,9 @@ namespace ICSharpCode.Decompiler.ILAst @@ -667,9 +671,9 @@ namespace ICSharpCode.Decompiler.ILAst
FalseBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, falseLabel) }
};
result.Add(new ILBasicBlock() {
EntryLabel = block.EntryLabel, // Keep the entry label
Body = { ilCond }
});
EntryLabel = block.EntryLabel, // Keep the entry label
Body = { ilCond }
});
// Remove the item immediately so that it is not picked up as content
scope.RemoveOrThrow(node);
@ -742,8 +746,8 @@ namespace ICSharpCode.Decompiler.ILAst @@ -742,8 +746,8 @@ namespace ICSharpCode.Decompiler.ILAst
static HashSet<ControlFlowNode> FindLoopContent(HashSet<ControlFlowNode> scope, ControlFlowNode head)
{
var exitNodes = head.DominanceFrontier.SelectMany(n => n.Predecessors);
HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(exitNodes);
var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p));
HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(viaBackEdges);
HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>();
while(agenda.Count > 0) {
@ -866,6 +870,17 @@ namespace ICSharpCode.Decompiler.ILAst @@ -866,6 +870,17 @@ namespace ICSharpCode.Decompiler.ILAst
return false;
}
public static bool Match(this ILNode node, ILCode code, out ILExpression arg)
{
List<ILExpression> args;
if (node.Match(code, out args) && args.Count == 1) {
arg = args[0];
return true;
}
arg = null;
return false;
}
public static bool Match<T>(this ILNode node, ILCode code, out T operand, out List<ILExpression> args)
{
ILExpression expr = node as ILExpression;
@ -882,14 +897,27 @@ namespace ICSharpCode.Decompiler.ILAst @@ -882,14 +897,27 @@ namespace ICSharpCode.Decompiler.ILAst
public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg)
{
List<ILExpression> args;
if (node.Match(code, out operand, out args)) {
arg = args.Single();
if (node.Match(code, out operand, out args) && args.Count == 1) {
arg = args[0];
return true;
}
arg = null;
return false;
}
public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg1, out ILExpression arg2)
{
List<ILExpression> args;
if (node.Match(code, out operand, out args) && args.Count == 2) {
arg1 = args[0];
arg2 = args[1];
return true;
}
arg1 = null;
arg2 = null;
return false;
}
public static bool Match<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel fallLabel)
{
if (bb.Body.Count == 1) {

17
ICSharpCode.Decompiler/ILAst/ILAstTypes.cs

@ -151,6 +151,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -151,6 +151,7 @@ namespace ICSharpCode.Decompiler.ILAst
public ILBlock TryBlock;
public List<CatchBlock> CatchBlocks;
public ILBlock FinallyBlock;
public ILBlock FaultBlock;
public override IEnumerable<ILNode> GetChildren()
{
@ -159,6 +160,8 @@ namespace ICSharpCode.Decompiler.ILAst @@ -159,6 +160,8 @@ namespace ICSharpCode.Decompiler.ILAst
foreach (var catchBlock in this.CatchBlocks) {
yield return catchBlock;
}
if (this.FaultBlock != null)
yield return this.FaultBlock;
if (this.FinallyBlock != null)
yield return this.FinallyBlock;
}
@ -173,6 +176,13 @@ namespace ICSharpCode.Decompiler.ILAst @@ -173,6 +176,13 @@ namespace ICSharpCode.Decompiler.ILAst
foreach (CatchBlock block in CatchBlocks) {
block.WriteTo(output);
}
if (FaultBlock != null) {
output.WriteLine("fault {");
output.Indent();
FaultBlock.WriteTo(output);
output.Unindent();
output.WriteLine("}");
}
if (FinallyBlock != null) {
output.WriteLine("finally {");
output.Indent();
@ -354,6 +364,13 @@ namespace ICSharpCode.Decompiler.ILAst @@ -354,6 +364,13 @@ namespace ICSharpCode.Decompiler.ILAst
if (Operand != null) {
if (Operand is ILLabel) {
output.WriteReference(((ILLabel)Operand).Name, Operand);
} else if (Operand is ILLabel[]) {
ILLabel[] labels = (ILLabel[])Operand;
for (int i = 0; i < labels.Length; i++) {
if (i > 0)
output.Write(", ");
output.WriteReference(labels[i].Name, labels[i]);
}
} else if (Operand is MethodReference) {
MethodReference method = (MethodReference)Operand;
method.DeclaringType.WriteTo(output, true, true);

3
ICSharpCode.Decompiler/ILAst/ILCodes.cs

@ -263,6 +263,8 @@ namespace ICSharpCode.Decompiler.ILAst @@ -263,6 +263,8 @@ namespace ICSharpCode.Decompiler.ILAst
LoopOrSwitchBreak,
LoopContinue,
Ldc_Decimal,
YieldBreak,
YieldReturn,
Pattern // used for ILAst pattern nodes
}
@ -288,6 +290,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -288,6 +290,7 @@ namespace ICSharpCode.Decompiler.ILAst
case ILCode.Rethrow:
case ILCode.LoopContinue:
case ILCode.LoopOrSwitchBreak:
case ILCode.YieldBreak:
return false;
default:
return true;

19
ICSharpCode.Decompiler/ILAst/Pattern.cs

@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.ILAst
public class LoadFromVariable : ILExpression
{
IVariablePattern v;
readonly IVariablePattern v;
public LoadFromVariable(IVariablePattern v) : base(ILCode.Pattern, null)
{
@ -59,6 +59,23 @@ namespace ICSharpCode.Decompiler.ILAst @@ -59,6 +59,23 @@ namespace ICSharpCode.Decompiler.ILAst
}
}
public class LoadFromArgument : ILExpression
{
int index;
public static readonly LoadFromArgument This = new LoadFromArgument(-1);
public LoadFromArgument(int index) : base(ILCode.Pattern, null)
{
this.index = index;
}
public override bool Match(ILNode other)
{
ILExpression expr = other as ILExpression;
return expr != null && expr.Code == ILCode.Ldarg && ((ParameterDefinition)expr.Operand).Index == index;
}
}
public class AnyILExpression : ILExpression
{
public ILExpression LastMatch;

942
ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs

@ -0,0 +1,942 @@ @@ -0,0 +1,942 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Mono.Cecil;
namespace ICSharpCode.Decompiler.ILAst
{
public class YieldReturnDecompiler
{
// 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.
/// <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 YieldAnalysisFailedException : Exception {}
DecompilerContext context;
TypeDefinition enumeratorType;
MethodDefinition enumeratorCtor;
MethodDefinition disposeMethod;
FieldDefinition stateField;
FieldDefinition currentField;
Dictionary<FieldDefinition, ParameterDefinition> fieldToParameterMap = new Dictionary<FieldDefinition, ParameterDefinition>();
List<ILNode> newBody;
#region Run() method
public static void Run(DecompilerContext context, ILBlock method)
{
if (!context.Settings.YieldReturn)
return; // abort if enumerator decompilation is disabled
var yrd = new YieldReturnDecompiler();
yrd.context = context;
if (!yrd.MatchEnumeratorCreationPattern(method))
return;
yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType;
#if DEBUG
if (Debugger.IsAttached) {
yrd.Run();
} else {
#endif
try {
yrd.Run();
} catch (YieldAnalysisFailedException) {
return;
}
#if DEBUG
}
#endif
method.Body.Clear();
method.EntryGoto = null;
method.Body.AddRange(yrd.newBody);
}
void Run()
{
AnalyzeCtor();
AnalyzeCurrentProperty();
ResolveIEnumerableIEnumeratorFieldMapping();
ConstructExceptionTable();
AnalyzeMoveNext();
TranslateFieldsToLocalAccess();
}
#endregion
#region Match the enumerator creation pattern
bool MatchEnumeratorCreationPattern(ILBlock method)
{
if (method.Body.Count == 0)
return false;
ILExpression newObj;
if (method.Body.Count == 1) {
// ret(newobj(...))
if (method.Body[0].Match(ILCode.Ret, out newObj))
return MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor);
else
return false;
}
// stloc(var_1, newobj(..)
ILVariable var1;
if (!method.Body[0].Match(ILCode.Stloc, out var1, out newObj))
return false;
if (!MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor))
return false;
int i;
for (i = 1; i < method.Body.Count; i++) {
// stfld(..., ldloc(var_1), ldarg(...))
FieldReference storedField;
ILExpression ldloc, ldarg;
if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out ldarg))
break;
if (ldloc.Code != ILCode.Ldloc || ldarg.Code != ILCode.Ldarg)
return false;
storedField = GetFieldDefinition(storedField);
if (ldloc.Operand != var1 || storedField == null)
return false;
fieldToParameterMap[(FieldDefinition)storedField] = (ParameterDefinition)ldarg.Operand;
}
ILVariable var2;
ILExpression ldlocForStloc2;
if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out var2, out ldlocForStloc2)) {
// stloc(var_2, ldloc(var_1))
if (ldlocForStloc2.Code != ILCode.Ldloc || ldlocForStloc2.Operand != var1)
return false;
i++;
} else {
// the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand
var2 = var1;
}
if (!SkipDummyBr(method, ref i))
return false;
ILExpression retArg;
if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out retArg)) {
// ret(ldloc(var_2))
if (retArg.Code == ILCode.Ldloc && retArg.Operand == var2) {
return true;
}
}
return false;
}
static FieldDefinition GetFieldDefinition(FieldReference field)
{
if (field != null && field.DeclaringType.IsGenericInstance)
return field.Resolve();
else
return field as FieldDefinition;
}
static MethodDefinition GetMethodDefinition(MethodReference method)
{
if (method != null && method.DeclaringType.IsGenericInstance)
return method.Resolve();
else
return method as MethodDefinition;
}
bool SkipDummyBr(ILBlock method, ref int i)
{
ILLabel target;
if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out target)) {
if (target != method.Body[i + 1])
return false;
i += 2;
}
return true;
}
bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor)
{
// newobj(CurrentType/...::.ctor, ldc.i4(-2))
ctor = null;
if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1)
return false;
if (expr.Arguments[0].Code != ILCode.Ldc_I4)
return false;
int initialState = (int)expr.Arguments[0].Operand;
if (!(initialState == -2 || initialState == 0))
return false;
ctor = GetMethodDefinition(expr.Operand as MethodReference);
if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType)
return false;
return IsCompilerGeneratorEnumerator(ctor.DeclaringType);
}
public static bool IsCompilerGeneratorEnumerator(TypeDefinition type)
{
if (!(type.Name.StartsWith("<", StringComparison.Ordinal) && type.IsCompilerGenerated()))
return false;
foreach (TypeReference i in type.Interfaces) {
if (i.Namespace == "System.Collections" && i.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()
{
ILBlock method = CreateILAst(enumeratorCtor);
ILExpression stfldPattern = new ILExpression(ILCode.Stfld, ILExpression.AnyOperand, LoadFromArgument.This, new LoadFromArgument(0));
foreach (ILNode node in method.Body) {
if (stfldPattern.Match(node)) {
stateField = GetFieldDefinition(((ILExpression)node).Operand as FieldReference);
}
}
if (stateField == null)
throw new YieldAnalysisFailedException();
}
/// <summary>
/// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
/// </summary>
ILBlock CreateILAst(MethodDefinition method)
{
if (method == null || !method.HasBody)
throw new YieldAnalysisFailedException();
ILBlock ilMethod = new ILBlock();
ILAstBuilder astBuilder = new ILAstBuilder();
ilMethod.Body = astBuilder.Build(method, true);
ILAstOptimizer optimizer = new ILAstOptimizer();
optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn);
return ilMethod;
}
#endregion
#region Figure out what the 'current' field is (analysis of get_Current())
static readonly ILExpression returnFieldFromThisPattern = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This));
/// <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));
ILBlock method = CreateILAst(getCurrentMethod);
if (method.Body.Count == 1) {
// release builds directly return the current field
if (returnFieldFromThisPattern.Match(method.Body[0])) {
currentField = GetFieldDefinition(((ILExpression)method.Body[0]).Arguments[0].Operand as FieldReference);
}
} else {
StoreToVariable v = new StoreToVariable(new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This));
if (v.Match(method.Body[0])) {
int i = 1;
if (SkipDummyBr(method, ref i) && i == method.Body.Count - 1) {
if (new ILExpression(ILCode.Ret, null, new LoadFromVariable(v)).Match(method.Body[i])) {
currentField = GetFieldDefinition(((ILExpression)method.Body[0]).Arguments[0].Operand as FieldReference);
}
}
}
}
if (currentField == null)
throw new YieldAnalysisFailedException();
}
#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?)
ILExpression mappingPattern = new ILExpression(
ILCode.Stfld, ILExpression.AnyOperand, new AnyILExpression(),
new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromArgument.This));
ILBlock method = CreateILAst(getEnumeratorMethod);
foreach (ILNode node in method.Body) {
if (mappingPattern.Match(node)) {
ILExpression stfld = (ILExpression)node;
FieldDefinition storedField = GetFieldDefinition(stfld.Operand as FieldReference);
FieldDefinition loadedField = GetFieldDefinition(stfld.Arguments[1].Operand as FieldReference);
if (storedField != null && loadedField != null) {
ParameterDefinition mappedParameter;
if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter))
fieldToParameterMap[storedField] = mappedParameter;
}
}
}
}
#endregion
#region Construction of the exception table (analysis of Dispose())
// We construct the exception table by analyzing the enumerator's Dispose() method.
// 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 instruction, we maintain a list of value ranges for state for which the instruction 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.
Dictionary<MethodDefinition, Interval> finallyMethodToStateInterval;
void ConstructExceptionTable()
{
disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose");
ILBlock ilMethod = CreateILAst(disposeMethod);
finallyMethodToStateInterval = new Dictionary<MethodDefinition, Interval>();
InitStateRanges(ilMethod.Body[0]);
AssignStateRanges(ilMethod.Body, ilMethod.Body.Count, forDispose: true);
// Now look at the finally blocks:
foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive<ILTryCatchBlock>()) {
Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToEnclosingInterval();
var finallyBody = tryFinally.FinallyBlock.Body;
if (!(finallyBody.Count == 2 || finallyBody.Count == 3))
throw new YieldAnalysisFailedException();
ILExpression call = finallyBody[0] as ILExpression;
if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1)
throw new YieldAnalysisFailedException();
if (call.Arguments[0].Code != ILCode.Ldarg || ((ParameterDefinition)call.Arguments[0].Operand).Index >= 0)
throw new YieldAnalysisFailedException();
if (finallyBody.Count == 3 && !finallyBody[1].Match(ILCode.Nop))
throw new YieldAnalysisFailedException();
if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally))
throw new YieldAnalysisFailedException();
MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference);
if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef))
throw new YieldAnalysisFailedException();
finallyMethodToStateInterval.Add(mdef, interval);
}
ranges = null;
}
#endregion
#region Assign StateRanges / Symbolic Execution (used for analysis of Dispose() and MoveNext())
#region struct Interval / class StateRange
struct Interval
{
public readonly int Start, End;
public Interval(int start, int end)
{
Debug.Assert(start <= end || (start == 0 && end == -1));
this.Start = start;
this.End = end;
}
public override string ToString()
{
return string.Format("({0} to {1})", Start, End);
}
}
class StateRange
{
readonly List<Interval> data = new List<Interval>();
public StateRange()
{
}
public StateRange(int start, int end)
{
this.data.Add(new Interval(start, end));
}
public bool Contains(int val)
{
foreach (Interval v in data) {
if (v.Start <= val && val <= v.End)
return true;
}
return false;
}
public void UnionWith(StateRange other)
{
data.AddRange(other.data);
}
/// <summary>
/// Unions this state range with (other intersect (minVal to maxVal))
/// </summary>
public void UnionWith(StateRange other, int minVal, int maxVal)
{
foreach (Interval v in other.data) {
int start = Math.Max(v.Start, minVal);
int end = Math.Min(v.End, maxVal);
if (start <= end)
data.Add(new Interval(start, end));
}
}
/// <summary>
/// Merges overlapping interval ranges.
/// </summary>
public void Simplify()
{
if (data.Count < 2)
return;
data.Sort((a, b) => a.Start.CompareTo(b.Start));
Interval prev = data[0];
int prevIndex = 0;
for (int i = 1; i < data.Count; i++) {
Interval next = data[i];
Debug.Assert(prev.Start <= next.Start);
if (next.Start <= prev.End + 1) { // intervals overlapping or touching
prev = new Interval(prev.Start, Math.Max(prev.End, next.End));
data[prevIndex] = prev;
data[i] = new Interval(0, -1); // mark as deleted
} else {
prev = next;
prevIndex = i;
}
}
data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted
}
public override string ToString()
{
return string.Join(",", data);
}
public Interval ToEnclosingInterval()
{
if (data.Count == 0)
throw new YieldAnalysisFailedException();
return new Interval(data[0].Start, data[data.Count - 1].End);
}
}
#endregion
DefaultDictionary<ILNode, StateRange> ranges;
ILVariable rangeAnalysisStateVariable;
/// <summary>
/// Initializes the state range logic:
/// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue)
/// </summary>
void InitStateRanges(ILNode entryPoint)
{
ranges = new DefaultDictionary<ILNode, StateRange>(n => new StateRange());
ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue);
rangeAnalysisStateVariable = null;
}
int AssignStateRanges(List<ILNode> body, int bodyLength, bool forDispose)
{
if (bodyLength == 0)
return 0;
for (int i = 0; i < bodyLength; i++) {
StateRange nodeRange = ranges[body[i]];
nodeRange.Simplify();
ILLabel label = body[i] as ILLabel;
if (label != null) {
ranges[body[i + 1]].UnionWith(nodeRange);
continue;
}
ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock;
if (tryFinally != null) {
if (!forDispose || tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null)
throw new YieldAnalysisFailedException();
ranges[tryFinally.TryBlock].UnionWith(nodeRange);
AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count, forDispose);
continue;
}
ILExpression expr = body[i] as ILExpression;
if (expr == null)
throw new YieldAnalysisFailedException();
switch (expr.Code) {
case ILCode.Switch:
{
SymbolicValue val = Eval(expr.Arguments[0]);
if (val.Type != SymbolicValueType.State)
throw new YieldAnalysisFailedException();
ILLabel[] targetLabels = (ILLabel[])expr.Operand;
for (int j = 0; j < targetLabels.Length; j++) {
int state = j - val.Constant;
ranges[targetLabels[j]].UnionWith(nodeRange, state, state);
}
StateRange nextRange = ranges[body[i + 1]];
nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant);
nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue);
break;
}
case ILCode.Br:
case ILCode.Leave:
ranges[(ILLabel)expr.Operand].UnionWith(nodeRange);
break;
case ILCode.Brtrue:
{
SymbolicValue val = Eval(expr.Arguments[0]);
if (val.Type == SymbolicValueType.StateEquals) {
ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant);
StateRange nextRange = ranges[body[i + 1]];
nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1);
nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue);
} else if (val.Type == SymbolicValueType.StateInEquals) {
ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant);
StateRange targetRange = ranges[(ILLabel)expr.Operand];
targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1);
targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue);
} else {
throw new YieldAnalysisFailedException();
}
break;
}
case ILCode.Nop:
ranges[body[i + 1]].UnionWith(nodeRange);
break;
case ILCode.Ret:
break;
case ILCode.Stloc:
{
SymbolicValue val = Eval(expr.Arguments[0]);
if (val.Type == SymbolicValueType.State && val.Constant == 0 && rangeAnalysisStateVariable == null)
rangeAnalysisStateVariable = (ILVariable)expr.Operand;
else
throw new YieldAnalysisFailedException();
goto case ILCode.Nop;
}
case ILCode.Call:
// in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks
if (forDispose) {
MethodDefinition mdef = GetMethodDefinition(expr.Operand as MethodReference);
if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef))
throw new YieldAnalysisFailedException();
finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval());
} else {
throw new YieldAnalysisFailedException();
}
break;
default:
if (forDispose)
throw new YieldAnalysisFailedException();
else
return i;
}
}
return bodyLength;
}
enum SymbolicValueType
{
/// <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 override string ToString()
{
return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant);
}
}
SymbolicValue Eval(ILExpression expr)
{
SymbolicValue left, right;
switch (expr.Code) {
case ILCode.Sub:
left = Eval(expr.Arguments[0]);
right = Eval(expr.Arguments[1]);
if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant)
throw new YieldAnalysisFailedException();
if (right.Type != SymbolicValueType.IntegerConstant)
throw new YieldAnalysisFailedException();
return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant ));
case ILCode.Ldfld:
if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This)
throw new YieldAnalysisFailedException();
if (GetFieldDefinition(expr.Operand as FieldReference) != stateField)
throw new YieldAnalysisFailedException();
return new SymbolicValue(SymbolicValueType.State);
case ILCode.Ldloc:
if (expr.Operand == rangeAnalysisStateVariable)
return new SymbolicValue(SymbolicValueType.State);
else
throw new YieldAnalysisFailedException();
case ILCode.Ldarg:
if (((ParameterDefinition)expr.Operand).Index < 0)
return new SymbolicValue(SymbolicValueType.This);
else
throw new YieldAnalysisFailedException();
case ILCode.Ldc_I4:
return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand);
case ILCode.Ceq:
left = Eval(expr.Arguments[0]);
right = Eval(expr.Arguments[1]);
if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant)
throw new YieldAnalysisFailedException();
// bool: (state + left.Constant == right.Constant)
// bool: (state == right.Constant - left.Constant)
return new SymbolicValue(SymbolicValueType.StateEquals, unchecked ( right.Constant - left.Constant ));
case ILCode.LogicNot:
SymbolicValue val = Eval(expr.Arguments[0]);
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
throw new YieldAnalysisFailedException();
default:
throw new YieldAnalysisFailedException();
}
}
#endregion
#region Analysis of MoveNext()
ILVariable returnVariable;
ILLabel returnLabel;
ILLabel returnFalseLabel;
void AnalyzeMoveNext()
{
MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext");
ILBlock ilMethod = CreateILAst(moveNextMethod);
if (ilMethod.Body.Count == 0)
throw new YieldAnalysisFailedException();
ILExpression lastReturnArg;
if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg))
throw new YieldAnalysisFailedException();
ilMethod.Body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops
// There are two possibilities:
if (lastReturnArg.Code == ILCode.Ldloc) {
// a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks)
returnVariable = (ILVariable)lastReturnArg.Operand;
returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel;
if (returnLabel == null)
throw new YieldAnalysisFailedException();
} else {
// b) the compiler directly returns constants
returnVariable = null;
returnLabel = null;
// In this case, the last return must return false.
if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0)
throw new YieldAnalysisFailedException();
}
ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock;
List<ILNode> body;
int bodyLength;
if (tryFaultBlock != null) {
// there are try-finally blocks
if (returnVariable == null) // in this case, we must use a return variable
throw new YieldAnalysisFailedException();
// must be a try-fault block:
if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null)
throw new YieldAnalysisFailedException();
ILBlock faultBlock = tryFaultBlock.FaultBlock;
// Ensure the fault block contains the call to Dispose().
if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3))
throw new YieldAnalysisFailedException();
MethodReference disposeMethodRef;
ILExpression disposeArg;
if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg))
throw new YieldAnalysisFailedException();
if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !LoadFromArgument.This.Match(disposeArg))
throw new YieldAnalysisFailedException();
if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop))
throw new YieldAnalysisFailedException();
if (!faultBlock.Body[faultBlock.Body.Count - 1].Match(ILCode.Endfinally))
throw new YieldAnalysisFailedException();
body = tryFaultBlock.TryBlock.Body;
body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops
bodyLength = body.Count;
} else {
// no try-finally blocks
body = ilMethod.Body;
if (returnVariable == null)
bodyLength = body.Count - 1; // all except for the return statement
else
bodyLength = body.Count - 2; // all except for the return label and statement
}
// Now verify that the last instruction in the body is 'ret(false)'
if (returnVariable != null) {
// If we don't have a return variable, we already verified that above.
if (bodyLength < 2)
throw new YieldAnalysisFailedException();
ILExpression leave = body[bodyLength - 1] as ILExpression;
if (leave == null || leave.Operand != returnLabel || !(leave.Code == ILCode.Br || leave.Code == ILCode.Leave))
throw new YieldAnalysisFailedException();
ILExpression store0 = body[bodyLength - 2] as ILExpression;
if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable)
throw new YieldAnalysisFailedException();
if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0)
throw new YieldAnalysisFailedException();
bodyLength -= 2; // don't conside the 'ret(false)' part of the body
}
// verify that the last element in the body is a label pointing to the 'ret(false)'
returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel;
if (returnFalseLabel == null)
throw new YieldAnalysisFailedException();
InitStateRanges(body[0]);
int pos = AssignStateRanges(body, bodyLength, forDispose: false);
if (pos > 0 && body[pos - 1] is ILLabel) {
pos--;
} else {
// ensure that the first element at body[pos] is a label:
ILLabel newLabel = new ILLabel();
newLabel.Name = "YieldReturnEntryPoint";
ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos]
body.Insert(pos, newLabel);
bodyLength++;
}
List<KeyValuePair<ILLabel, StateRange>> labels = new List<KeyValuePair<ILLabel, StateRange>>();
for (int i = pos; i < bodyLength; i++) {
ILLabel label = body[i] as ILLabel;
if (label != null) {
labels.Add(new KeyValuePair<ILLabel, StateRange>(label, ranges[label]));
}
}
ConvertBody(body, pos, bodyLength, labels);
}
#endregion
#region ConvertBody
struct SetState
{
public readonly int NewBodyPos;
public readonly int NewState;
public SetState(int newBodyPos, int newState)
{
this.NewBodyPos = newBodyPos;
this.NewState = newState;
}
}
void ConvertBody(List<ILNode> body, int startPos, int bodyLength, List<KeyValuePair<ILLabel, StateRange>> labels)
{
newBody = new List<ILNode>();
newBody.Add(MakeGoTo(labels, 0));
List<SetState> stateChanges = new List<SetState>();
int currentState = -1;
// Copy all instructions from the old body to newBody.
for (int pos = startPos; pos < bodyLength; pos++) {
ILExpression expr = body[pos] as ILExpression;
if (expr != null && expr.Code == ILCode.Stfld && LoadFromArgument.This.Match(expr.Arguments[0])) {
// Handle stores to 'state' or 'current'
if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) {
if (expr.Arguments[1].Code != ILCode.Ldc_I4)
throw new YieldAnalysisFailedException();
currentState = (int)expr.Arguments[1].Operand;
stateChanges.Add(new SetState(newBody.Count, currentState));
} else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) {
newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1]));
} else {
newBody.Add(body[pos]);
}
} else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) {
// handle store+branch to the returnVariable
ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression;
if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4)
throw new YieldAnalysisFailedException();
int val = (int)expr.Arguments[0].Operand;
if (val == 0) {
newBody.Add(MakeGoTo(returnFalseLabel));
} else if (val == 1) {
newBody.Add(MakeGoTo(labels, currentState));
} else {
throw new YieldAnalysisFailedException();
}
} else if (expr != null && expr.Code == ILCode.Ret) {
if (expr.Arguments.Count != 1 || expr.Arguments[0].Code != ILCode.Ldc_I4)
throw new YieldAnalysisFailedException();
// handle direct return (e.g. in release builds)
int val = (int)expr.Arguments[0].Operand;
if (val == 0) {
newBody.Add(MakeGoTo(returnFalseLabel));
} else if (val == 1) {
newBody.Add(MakeGoTo(labels, currentState));
} else {
throw new YieldAnalysisFailedException();
}
} else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromArgument.This.Match(expr.Arguments[0])) {
MethodDefinition method = GetMethodDefinition(expr.Operand as MethodReference);
if (method == null)
throw new YieldAnalysisFailedException();
Interval interval;
if (method == disposeMethod) {
// Explicit call to dispose is used for "yield break;" within the method.
ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression;
if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel)
throw new YieldAnalysisFailedException();
newBody.Add(MakeGoTo(returnFalseLabel));
} else if (finallyMethodToStateInterval.TryGetValue(method, out interval)) {
// Call to Finally-method
int index = stateChanges.FindIndex(ss => ss.NewState >= interval.Start && ss.NewState <= interval.End);
if (index < 0)
throw new YieldAnalysisFailedException();
ILLabel label = new ILLabel();
label.Name = "JumpOutOfTryFinally" + interval.Start + "_" + interval.End;
newBody.Add(new ILExpression(ILCode.Leave, label));
SetState stateChange = stateChanges[index];
// Move all instructions from stateChange.Pos to newBody.Count into a try-block
stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found
ILTryCatchBlock tryFinally = new ILTryCatchBlock();
tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos));
newBody.RemoveRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos); // remove all nodes that we just moved into the try block
tryFinally.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>();
tryFinally.FinallyBlock = ConvertFinallyBlock(method);
newBody.Add(tryFinally);
newBody.Add(label);
}
} else {
newBody.Add(body[pos]);
}
}
newBody.Add(new ILExpression(ILCode.YieldBreak, null));
}
ILExpression MakeGoTo(ILLabel targetLabel)
{
if (targetLabel == returnFalseLabel)
return new ILExpression(ILCode.YieldBreak, null);
else
return new ILExpression(ILCode.Br, targetLabel);
}
ILExpression MakeGoTo(List<KeyValuePair<ILLabel, StateRange>> labels, int state)
{
foreach (var pair in labels) {
if (pair.Value.Contains(state))
return MakeGoTo(pair.Key);
}
throw new YieldAnalysisFailedException();
}
ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod)
{
ILBlock block = CreateILAst(finallyMethod);
block.Body.RemoveAll(n => n.Match(ILCode.Nop));
// Get rid of assignment to state
FieldReference stfld;
List<ILExpression> args;
if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) {
if (GetFieldDefinition(stfld) == stateField && LoadFromArgument.This.Match(args[0]))
block.Body.RemoveAt(0);
}
// Convert ret to endfinally
foreach (ILExpression expr in block.GetSelfAndChildrenRecursive<ILExpression>()) {
if (expr.Code == ILCode.Ret)
expr.Code = ILCode.Endfinally;
}
return block;
}
#endregion
#region TranslateFieldsToLocalAccess
void TranslateFieldsToLocalAccess()
{
var fieldToLocalMap = new DefaultDictionary<FieldDefinition, ILVariable>(f => new ILVariable { Name = f.Name, Type = f.FieldType });
foreach (ILNode node in newBody) {
foreach (ILExpression expr in node.GetSelfAndChildrenRecursive<ILExpression>()) {
FieldDefinition field = GetFieldDefinition(expr.Operand as FieldReference);
if (field != null) {
switch (expr.Code) {
case ILCode.Ldfld:
if (LoadFromArgument.This.Match(expr.Arguments[0])) {
if (fieldToParameterMap.ContainsKey(field)) {
expr.Code = ILCode.Ldarg;
expr.Operand = fieldToParameterMap[field];
} else {
expr.Code = ILCode.Ldloc;
expr.Operand = fieldToLocalMap[field];
}
expr.Arguments.Clear();
}
break;
case ILCode.Stfld:
if (LoadFromArgument.This.Match(expr.Arguments[0])) {
if (fieldToParameterMap.ContainsKey(field)) {
expr.Code = ILCode.Starg;
expr.Operand = fieldToParameterMap[field];
} else {
expr.Code = ILCode.Stloc;
expr.Operand = fieldToLocalMap[field];
}
expr.Arguments.RemoveAt(0);
}
break;
case ILCode.Ldflda:
if (fieldToParameterMap.ContainsKey(field)) {
expr.Code = ILCode.Ldarga;
expr.Operand = fieldToParameterMap[field];
} else {
expr.Code = ILCode.Ldloca;
expr.Operand = fieldToLocalMap[field];
}
expr.Arguments.Clear();
break;
}
}
}
}
}
#endregion
}
}

1
ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj

@ -53,6 +53,7 @@ @@ -53,6 +53,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="YieldReturn.cs" />
<None Include="Types\S_EnumSamples.cs" />
<None Include="CustomAttributes\S_AssemblyCustomAttribute.cs" />
<Compile Include="Helpers\RemoveCompilerAttribute.cs" />

103
ICSharpCode.Decompiler/Tests/YieldReturn.cs

@ -0,0 +1,103 @@ @@ -0,0 +1,103 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
public static class YieldReturn
{
public static IEnumerable<string> SimpleYieldReturn()
{
yield return "A";
yield return "B";
yield return "C";
}
public static IEnumerable<int> YieldReturnInLoop()
{
for (int i = 0; i < 100; i++) {
yield return i;
}
}
public static IEnumerable<int> YieldReturnWithTryFinally()
{
yield return 0;
try {
yield return 1;
} finally {
Console.WriteLine("Finally!");
}
yield return 2;
}
public static IEnumerable<string> YieldReturnWithNestedTryFinally(bool breakInMiddle)
{
Console.WriteLine("Start of method - 1");
yield return "Start of method";
Console.WriteLine("Start of method - 2");
try {
Console.WriteLine("Within outer try - 1");
yield return "Within outer try";
Console.WriteLine("Within outer try - 2");
try {
Console.WriteLine("Within inner try - 1");
yield return "Within inner try";
Console.WriteLine("Within inner try - 2");
if (breakInMiddle)
yield break;
Console.WriteLine("End of inner try - 1");
yield return "End of inner try";
Console.WriteLine("End of inner try - 2");
} finally {
Console.WriteLine("Inner Finally");
}
Console.WriteLine("End of outer try - 1");
yield return "End of outer try";
Console.WriteLine("End of outer try - 2");
} finally {
Console.WriteLine("Outer Finally");
}
Console.WriteLine("End of method - 1");
yield return "End of method";
Console.WriteLine("End of method - 2");
}
public static IEnumerable<string> YieldReturnWithTwoNonNestedFinallyBlocks(IEnumerable<string> input)
{
// outer try-finally block
foreach (string line in input) {
// nested try-finally block
try {
yield return line;
} finally {
Console.WriteLine("Processed " + line);
}
}
yield return "A";
yield return "B";
yield return "C";
yield return "D";
yield return "E";
yield return "F";
// outer try-finally block
foreach (string line in input)
yield return line.ToUpper();
}
public static IEnumerable<Func<string>> YieldReturnWithAnonymousMethods1(IEnumerable<string> input)
{
foreach (string line in input) {
yield return () => line;
}
}
public static IEnumerable<Func<string>> YieldReturnWithAnonymousMethods2(IEnumerable<string> input)
{
foreach (string line in input) {
string copy = line;
yield return () => copy;
}
}
}

5
ILSpy/CSharpLanguage.cs

@ -376,7 +376,8 @@ namespace ICSharpCode.ILSpy @@ -376,7 +376,8 @@ namespace ICSharpCode.ILSpy
return new AstBuilder(
new DecompilerContext {
CancellationToken = options.CancellationToken,
CurrentType = currentType
CurrentType = currentType,
Settings = options.DecompilerSettings
});
}
@ -419,7 +420,7 @@ namespace ICSharpCode.ILSpy @@ -419,7 +420,7 @@ namespace ICSharpCode.ILSpy
public override bool ShowMember(MemberReference member)
{
return showAllMembers || !AstBuilder.MemberIsHidden(member);
return showAllMembers || !AstBuilder.MemberIsHidden(member, new DecompilationOptions().DecompilerSettings);
}
}
}

11
ILSpy/DecompilationOptions.cs

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
using System;
using System.Threading;
using ICSharpCode.Decompiler;
namespace ICSharpCode.ILSpy
{
@ -45,5 +46,15 @@ namespace ICSharpCode.ILSpy @@ -45,5 +46,15 @@ namespace ICSharpCode.ILSpy
/// to allow for cooperative cancellation of the decompilation task.
/// </remarks>
public CancellationToken CancellationToken { get; set; }
/// <summary>
/// Gets the settings for the decompiler.
/// </summary>
public DecompilerSettings DecompilerSettings { get; set; }
public DecompilationOptions()
{
this.DecompilerSettings = DecompilerSettingsPanel.CurrentDecompilerSettings;
}
}
}

9
ILSpy/DecompilerSettingsPanel.xaml

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
<UserControl x:Class="ICSharpCode.ILSpy.DecompilerSettingsPanel"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Margin="10">
<CheckBox IsChecked="{Binding AnonymousMethods}">Decompile anonymous methods/lambdas</CheckBox>
<CheckBox IsChecked="{Binding YieldReturn}">Decompile enumerators (yield return)</CheckBox>
</StackPanel>
</UserControl>

67
ILSpy/DecompilerSettingsPanel.xaml.cs

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Linq;
using ICSharpCode.Decompiler;
namespace ICSharpCode.ILSpy
{
/// <summary>
/// Interaction logic for DecompilerSettingsPanel.xaml
/// </summary>
[ExportOptionPage("Decompiler")]
partial class DecompilerSettingsPanel : UserControl, IOptionPage
{
public DecompilerSettingsPanel()
{
InitializeComponent();
}
public void Load(ILSpySettings settings)
{
this.DataContext = LoadDecompilerSettings(settings);
}
static DecompilerSettings currentDecompilerSettings;
public static DecompilerSettings CurrentDecompilerSettings {
get {
return currentDecompilerSettings ?? (currentDecompilerSettings = LoadDecompilerSettings(ILSpySettings.Load()));
}
}
public static DecompilerSettings LoadDecompilerSettings(ILSpySettings settings)
{
XElement e = settings["DecompilerSettings"];
DecompilerSettings s = new DecompilerSettings();
s.AnonymousMethods = (bool?)e.Attribute("anonymousMethods") ?? s.AnonymousMethods;
s.YieldReturn = (bool?)e.Attribute("yieldReturn") ?? s.YieldReturn;
return s;
}
public void Save(XElement root)
{
DecompilerSettings s = (DecompilerSettings)this.DataContext;
XElement section = new XElement("DecompilerSettings");
section.SetAttributeValue("anonymousMethods", s.AnonymousMethods);
section.SetAttributeValue("yieldReturn", s.YieldReturn);
XElement existingElement = root.Element("DecompilerSettings");
if (existingElement != null)
existingElement.ReplaceWith(section);
else
root.Add(section);
currentDecompilerSettings = null; // invalidate cached settings
}
}
}

3
ILSpy/ILAstLanguage.cs

@ -60,8 +60,7 @@ namespace ICSharpCode.ILSpy @@ -60,8 +60,7 @@ namespace ICSharpCode.ILSpy
new ILAstOptimizer().Optimize(context, ilMethod, abortBeforeStep.Value);
}
var allVariables = astBuilder.Variables
.Concat(ilMethod.GetSelfAndChildrenRecursive<ILExpression>().Select(e => e.Operand as ILVariable).Where(v => v != null)).Distinct();
var allVariables = ilMethod.GetSelfAndChildrenRecursive<ILExpression>().Select(e => e.Operand as ILVariable).Where(v => v != null).Distinct();
foreach (ILVariable v in allVariables) {
output.WriteDefinition(v.Name, v);
if (v.Type != null) {

10
ILSpy/ILSpy.csproj

@ -91,6 +91,10 @@ @@ -91,6 +91,10 @@
<Compile Include="BamlDecompiler.cs" />
<Compile Include="Commands.cs" />
<Compile Include="Commands\DebuggerCommands.cs" />
<Compile Include="DecompilerSettingsPanel.xaml.cs">
<DependentUpon>DecompilerSettingsPanel.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Controls\SearchBox.cs" />
<Compile Include="Controls\SortableGridViewColumn.cs" />
<Compile Include="CSharpLanguage.cs" />
@ -113,6 +117,10 @@ @@ -113,6 +117,10 @@
<DependentUpon>OpenFromGacDialog.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="OptionsDialog.xaml.cs">
<DependentUpon>OptionsDialog.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="..\README.txt">
<Link>README.txt</Link>
@ -171,8 +179,10 @@ @@ -171,8 +179,10 @@
<Page Include="Controls\SearchBoxStyle.xaml">
<DependentUpon>SearchBox.cs</DependentUpon>
</Page>
<Page Include="DecompilerSettingsPanel.xaml" />
<Page Include="MainWindow.xaml" />
<Page Include="OpenFromGacDialog.xaml" />
<Page Include="OptionsDialog.xaml" />
<Page Include="TextView\DecompilerTextView.xaml">
<DependentUpon>DecompilerTextView.cs</DependentUpon>
</Page>

16
ILSpy/MainWindow.xaml.cs

@ -291,15 +291,20 @@ namespace ICSharpCode.ILSpy @@ -291,15 +291,20 @@ namespace ICSharpCode.ILSpy
}
void filterSettings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RefreshTreeViewFilter();
if (e.PropertyName == "Language") {
TreeView_SelectionChanged(null, null);
}
}
public void RefreshTreeViewFilter()
{
// filterSettings is mutable; but the ILSpyTreeNode filtering assumes that filter settings are immutable.
// Thus, the main window will use one mutable instance (for data-binding), and assign a new clone to the ILSpyTreeNodes whenever the main
// mutable instance changes.
if (assemblyListTreeNode != null)
assemblyListTreeNode.FilterSettings = sessionSettings.FilterSettings.Clone();
if (e.PropertyName == "Language") {
TreeView_SelectionChanged(null, null);
}
}
internal AssemblyList AssemblyList {
@ -445,6 +450,11 @@ namespace ICSharpCode.ILSpy @@ -445,6 +450,11 @@ namespace ICSharpCode.ILSpy
decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions());
}
public void RefreshDecompiledView()
{
TreeView_SelectionChanged(null, null);
}
public DecompilerTextView TextView {
get { return decompilerTextView; }
}

21
ILSpy/OptionsDialog.xaml

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<Window x:Class="ICSharpCode.ILSpy.OptionsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Style="{DynamicResource DialogWindow}"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResizeWithGrip"
Title="Options" Height="400" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="1*" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<TabControl Name="tabControl" />
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="12,8">
<Button IsDefault="True" Margin="2,0" Name="okButton" Click="OKButton_Click">OK</Button>
<Button IsCancel="True" Margin="2,0">Cancel</Button>
</StackPanel>
</Grid>
</Window>

95
ILSpy/OptionsDialog.xaml.cs

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Linq;
namespace ICSharpCode.ILSpy
{
/// <summary>
/// Interaction logic for OptionsDialog.xaml
/// </summary>
public partial class OptionsDialog : Window
{
[ImportMany("OptionPages", typeof(UIElement), RequiredCreationPolicy = CreationPolicy.NonShared)]
Lazy<UIElement, IOptionsMetadata>[] optionPages = null;
public OptionsDialog()
{
InitializeComponent();
App.CompositionContainer.ComposeParts(this);
ILSpySettings settings = ILSpySettings.Load();
foreach (var optionPage in optionPages) {
TabItem tabItem = new TabItem();
tabItem.Header = optionPage.Metadata.Title;
tabItem.Content = optionPage.Value;
tabControl.Items.Add(tabItem);
IOptionPage page = optionPage.Value as IOptionPage;
if (page != null)
page.Load(settings);
}
}
void OKButton_Click(object sender, RoutedEventArgs e)
{
ILSpySettings.Update(
delegate (XElement root) {
foreach (var optionPage in optionPages) {
IOptionPage page = optionPage.Value as IOptionPage;
if (page != null)
page.Save(root);
}
});
this.DialogResult = true;
Close();
}
}
public interface IOptionsMetadata
{
string Title { get; }
}
public interface IOptionPage
{
void Load(ILSpySettings settings);
void Save(XElement root);
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class ExportOptionPageAttribute : ExportAttribute
{
public ExportOptionPageAttribute(string title)
: base("OptionPages", typeof(UIElement))
{
this.Title = title;
}
public string Title { get; private set; }
}
[ExportMainMenuCommand(Menu = "_View", Header = "_Options", MenuCategory = "Options", MenuOrder = 999)]
sealed class ShowOptionsCommand : SimpleCommand
{
public override void Execute(object parameter)
{
OptionsDialog dlg = new OptionsDialog();
dlg.Owner = MainWindow.Instance;
if (dlg.ShowDialog() == true) {
MainWindow.Instance.RefreshTreeViewFilter();
MainWindow.Instance.RefreshDecompiledView();
}
}
}
}
Loading…
Cancel
Save