Browse Source

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

pull/191/merge
Eusebiu Marcu 15 years ago
parent
commit
3b7af8f717
  1. 112
      ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
  2. 8
      ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs
  3. 24
      ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs
  4. 13
      ICSharpCode.Decompiler/ILAst/ILAstTypes.cs
  5. 14
      ICSharpCode.Decompiler/ILAst/ILInlining.cs
  6. 9
      ICSharpCode.Decompiler/ILAst/PatternMatching.cs
  7. 125
      ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs
  8. 323
      ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
  9. 18
      ICSharpCode.Decompiler/Tests/UnsafeCode.cs
  10. 14
      ILSpy/Commands.cs
  11. 3
      ILSpy/MainWindow.xaml
  12. 11
      ILSpy/MainWindow.xaml.cs

112
ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs

@ -4,7 +4,7 @@ using System.Collections.Generic; @@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using ICSharpCode.Decompiler.Ast.Transforms;
using ICSharpCode.Decompiler.ILAst;
using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.NRefactory.Utils;
@ -183,17 +183,17 @@ namespace ICSharpCode.Decompiler.Ast @@ -183,17 +183,17 @@ namespace ICSharpCode.Decompiler.Ast
yield return tryCatchStmt;
} else if (node is ILFixedStatement) {
ILFixedStatement fixedNode = (ILFixedStatement)node;
ILVariable v;
ILExpression init;
if (!fixedNode.Initializer.Match(ILCode.Stloc, out v, out init))
throw new InvalidOperationException("Fixed initializer must be an assignment to a local variable");
FixedStatement fixedStatement = new FixedStatement();
fixedStatement.Type = AstBuilder.ConvertType(v.Type);
fixedStatement.Variables.Add(
new VariableInitializer {
Name = v.Name,
Initializer = (Expression)TransformExpression(init)
}.WithAnnotation(v));
foreach (ILExpression initializer in fixedNode.Initializers) {
Debug.Assert(initializer.Code == ILCode.Stloc);
ILVariable v = (ILVariable)initializer.Operand;
fixedStatement.Variables.Add(
new VariableInitializer {
Name = v.Name,
Initializer = (Expression)TransformExpression(initializer.Arguments[0])
}.WithAnnotation(v));
}
fixedStatement.Type = AstBuilder.ConvertType(((ILVariable)fixedNode.Initializers[0].Operand).Type);
fixedStatement.EmbeddedStatement = TransformBlock(fixedNode.BodyBlock);
yield return fixedStatement;
} else if (node is ILBlock) {
@ -240,9 +240,36 @@ namespace ICSharpCode.Decompiler.Ast @@ -240,9 +240,36 @@ namespace ICSharpCode.Decompiler.Ast
switch(byteCode.Code) {
#region Arithmetic
case ILCode.Add: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2);
case ILCode.Add_Ovf: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2);
case ILCode.Add_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2);
case ILCode.Add:
case ILCode.Add_Ovf:
case ILCode.Add_Ovf_Un:
{
if (byteCode.InferredType is PointerType) {
if (byteCode.Arguments[0].ExpectedType is PointerType) {
arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType);
return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2)
.WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation);
} else if (byteCode.Arguments[1].ExpectedType is PointerType) {
arg1 = DivideBySize(arg1, ((PointerType)byteCode.InferredType).ElementType);
return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2)
.WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation);
}
}
return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2);
}
case ILCode.Sub:
case ILCode.Sub_Ovf:
case ILCode.Sub_Ovf_Un:
{
if (byteCode.InferredType is PointerType) {
if (byteCode.Arguments[0].ExpectedType is PointerType) {
arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType);
return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2)
.WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation);
}
}
return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2);
}
case ILCode.Div: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2);
case ILCode.Div_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2);
case ILCode.Mul: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2);
@ -250,9 +277,6 @@ namespace ICSharpCode.Decompiler.Ast @@ -250,9 +277,6 @@ namespace ICSharpCode.Decompiler.Ast
case ILCode.Mul_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2);
case ILCode.Rem: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2);
case ILCode.Rem_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2);
case ILCode.Sub: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2);
case ILCode.Sub_Ovf: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2);
case ILCode.Sub_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2);
case ILCode.And: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseAnd, arg2);
case ILCode.Or: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseOr, arg2);
case ILCode.Xor: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ExclusiveOr, arg2);
@ -487,7 +511,20 @@ namespace ICSharpCode.Decompiler.Ast @@ -487,7 +511,20 @@ namespace ICSharpCode.Decompiler.Ast
return InlineAssembly(byteCode, args);
}
case ILCode.Leave: return new GotoStatement() { Label = ((ILLabel)operand).Name };
case ILCode.Localloc: return InlineAssembly(byteCode, args);
case ILCode.Localloc:
{
PointerType ptrType = byteCode.InferredType as PointerType;
TypeReference type;
if (ptrType != null) {
type = ptrType.ElementType;
} else {
type = typeSystem.Byte;
}
return new StackAllocExpression {
Type = AstBuilder.ConvertType(type),
CountExpression = DivideBySize(arg1, type)
};
}
case ILCode.Mkrefany: return InlineAssembly(byteCode, args);
case ILCode.Newobj: {
Cecil.TypeReference declaringType = ((MethodReference)operand).DeclaringType;
@ -557,6 +594,45 @@ namespace ICSharpCode.Decompiler.Ast @@ -557,6 +594,45 @@ namespace ICSharpCode.Decompiler.Ast
}
}
/// <summary>
/// Divides expr by the size of 'type'.
/// </summary>
Expression DivideBySize(Expression expr, TypeReference type)
{
CastExpression cast = expr as CastExpression;
if (cast != null && cast.Type is PrimitiveType && ((PrimitiveType)cast.Type).Keyword == "int")
expr = cast.Expression.Detach();
Expression sizeOfExpression;
switch (TypeAnalysis.GetInformationAmount(type)) {
case 1:
case 8:
sizeOfExpression = new PrimitiveExpression(1);
break;
case 16:
sizeOfExpression = new PrimitiveExpression(2);
break;
case 32:
sizeOfExpression = new PrimitiveExpression(4);
break;
case 64:
sizeOfExpression = new PrimitiveExpression(8);
break;
default:
sizeOfExpression = new SizeOfExpression { Type = AstBuilder.ConvertType(type) };
break;
}
BinaryOperatorExpression boe = expr as BinaryOperatorExpression;
if (boe != null && boe.Operator == BinaryOperatorType.Multiply && sizeOfExpression.Match(boe.Right) != null)
return boe.Left.Detach();
if (sizeOfExpression.Match(expr) != null)
return new PrimitiveExpression(1);
return new BinaryOperatorExpression(expr, BinaryOperatorType.Divide, sizeOfExpression);
}
Expression MakeDefaultValue(TypeReference type)
{
TypeDefinition typeDef = type.Resolve();

8
ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs

@ -55,8 +55,12 @@ namespace ICSharpCode.Decompiler.Ast @@ -55,8 +55,12 @@ namespace ICSharpCode.Decompiler.Ast
return node;
FixedStatement fixedStatement = node as FixedStatement;
if (fixedStatement != null && fixedStatement.Variables.Single().Name == name)
return null; // no need to introduce the variable here
if (fixedStatement != null) {
foreach (VariableInitializer v in fixedStatement.Variables) {
if (v.Name == name)
return null; // no need to introduce the variable here
}
}
AstNode withinCurrent = FindInsertPos(node.FirstChild, name, allowPassIntoLoops);
if (withinCurrent != null) {

24
ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs

@ -8,6 +8,10 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -8,6 +8,10 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
{
public class IntroduceUnsafeModifier : DepthFirstAstVisitor<object, bool>, IAstTransform
{
public static readonly object PointerArithmeticAnnotation = new PointerArithmetic();
sealed class PointerArithmetic {}
public void Run(AstNode compilationUnit)
{
compilationUnit.AcceptVisitor(this, null);
@ -42,17 +46,23 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -42,17 +46,23 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
public override bool VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data)
{
base.VisitUnaryOperatorExpression(unaryOperatorExpression, data);
bool result = base.VisitUnaryOperatorExpression(unaryOperatorExpression, data);
if (unaryOperatorExpression.Operator == UnaryOperatorType.Dereference) {
BinaryOperatorExpression bop = unaryOperatorExpression.Expression as BinaryOperatorExpression;
if (bop != null && bop.Operator == BinaryOperatorType.Add) {
// TODO: transform "*(ptr + int)" to "ptr[int]"
if (bop != null && bop.Operator == BinaryOperatorType.Add && bop.Annotation<PointerArithmetic>() != null) {
// transform "*(ptr + int)" to "ptr[int]"
IndexerExpression indexer = new IndexerExpression();
indexer.Target = bop.Left.Detach();
indexer.Arguments.Add(bop.Right.Detach());
indexer.CopyAnnotationsFrom(unaryOperatorExpression);
indexer.CopyAnnotationsFrom(bop);
unaryOperatorExpression.ReplaceWith(indexer);
}
return true;
} else if (unaryOperatorExpression.Operator == UnaryOperatorType.AddressOf) {
return true;
} else {
return false;
return result;
}
}
@ -71,5 +81,11 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -71,5 +81,11 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
}
return result;
}
public override bool VisitStackAllocExpression(StackAllocExpression stackAllocExpression, object data)
{
base.VisitStackAllocExpression(stackAllocExpression, data);
return true;
}
}
}

13
ICSharpCode.Decompiler/ILAst/ILAstTypes.cs

@ -528,13 +528,13 @@ namespace ICSharpCode.Decompiler.ILAst @@ -528,13 +528,13 @@ namespace ICSharpCode.Decompiler.ILAst
public class ILFixedStatement : ILNode
{
public ILExpression Initializer;
public List<ILExpression> Initializers = new List<ILExpression>();
public ILBlock BodyBlock;
public override IEnumerable<ILNode> GetChildren()
{
if (this.Initializer != null)
yield return this.Initializer;
foreach (ILExpression initializer in this.Initializers)
yield return initializer;
if (this.BodyBlock != null)
yield return this.BodyBlock;
}
@ -542,8 +542,11 @@ namespace ICSharpCode.Decompiler.ILAst @@ -542,8 +542,11 @@ namespace ICSharpCode.Decompiler.ILAst
public override void WriteTo(ITextOutput output)
{
output.Write("fixed (");
if (this.Initializer != null)
this.Initializer.WriteTo(output);
for (int i = 0; i < this.Initializers.Count; i++) {
if (i > 0)
output.Write(", ");
this.Initializers[i].WriteTo(output);
}
output.WriteLine(") {");
output.Indent();
this.BodyBlock.WriteTo(output);

14
ICSharpCode.Decompiler/ILAst/ILInlining.cs

@ -275,7 +275,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -275,7 +275,7 @@ namespace ICSharpCode.Decompiler.ILAst
ILExpression copiedExpr;
if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr)
&& !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0
&& CanPerformCopyPropagation(copiedExpr))
&& CanPerformCopyPropagation(copiedExpr, v))
{
// un-inline the arguments of the ldArg instruction
ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count];
@ -307,7 +307,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -307,7 +307,7 @@ namespace ICSharpCode.Decompiler.ILAst
}
}
bool CanPerformCopyPropagation(ILExpression expr)
bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable)
{
switch (expr.Code) {
case ILCode.Ldloca:
@ -318,9 +318,15 @@ namespace ICSharpCode.Decompiler.ILAst @@ -318,9 +318,15 @@ namespace ICSharpCode.Decompiler.ILAst
// so they can be safely copied.
return true;
case ILCode.Ldloc:
// Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga)
ILVariable v = (ILVariable)expr.Operand;
return v.IsParameter && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0;
if (v.IsParameter) {
// Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga)
return numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0;
} else {
// Variables are be copied only if both they and the target copy variable are generated,
// and if the variable has only a single assignment
return v.IsGenerated && copyVariable.IsGenerated && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1;
}
default:
return false;
}

9
ICSharpCode.Decompiler/ILAst/PatternMatching.cs

@ -20,9 +20,8 @@ namespace ICSharpCode.Decompiler.ILAst @@ -20,9 +20,8 @@ namespace ICSharpCode.Decompiler.ILAst
public static bool Match<T>(this ILNode node, ILCode code, out T operand)
{
ILExpression expr = node as ILExpression;
if (expr != null && expr.Prefixes == null && expr.Code == code) {
if (expr != null && expr.Prefixes == null && expr.Code == code && expr.Arguments.Count == 0) {
operand = (T)expr.Operand;
Debug.Assert(expr.Arguments.Count == 0);
return true;
}
operand = default(T);
@ -108,5 +107,11 @@ namespace ICSharpCode.Decompiler.ILAst @@ -108,5 +107,11 @@ namespace ICSharpCode.Decompiler.ILAst
ILVariable v;
return node.Match(ILCode.Ldloc, out v) && v.IsParameter && v.OriginalParameter.Index == -1;
}
public static bool MatchLdloc(this ILNode node, ILVariable expectedVar)
{
ILVariable v;
return node.Match(ILCode.Ldloc, out v) && v == expectedVar;
}
}
}

125
ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs

@ -2,8 +2,10 @@ @@ -2,8 +2,10 @@
// 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 ICSharpCode.NRefactory.Utils;
using Mono.Cecil;
@ -297,30 +299,46 @@ namespace ICSharpCode.Decompiler.ILAst @@ -297,30 +299,46 @@ namespace ICSharpCode.Decompiler.ILAst
#region IntroduceFixedStatements
void IntroduceFixedStatements(ILBlock block, ref int i)
{
// stloc(pinned_Var, conv.u(ldc.i4(0)))
ILExpression initValue;
ILVariable pinnedVar;
if (!MatchFixedInitializer(block, i, out pinnedVar, out initValue))
int initEndPos;
if (!MatchFixedInitializer(block, i, out pinnedVar, out initValue, out initEndPos))
return;
// find initialization of v:
ILFixedStatement fixedStmt = block.Body.ElementAtOrDefault(initEndPos) as ILFixedStatement;
if (fixedStmt != null) {
ILExpression expr = fixedStmt.BodyBlock.Body.LastOrDefault() as ILExpression;
if (expr != null && expr.Code == ILCode.Stloc && expr.Operand == pinnedVar && IsNullOrZero(expr.Arguments[0])) {
// we found a second initializer for the existing fixed statement
fixedStmt.Initializers.Insert(0, initValue);
block.Body.RemoveRange(i, initEndPos - i);
fixedStmt.BodyBlock.Body.RemoveAt(fixedStmt.BodyBlock.Body.Count - 1);
if (pinnedVar.Type.IsByReference)
pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType);
return;
}
}
// find where pinnedVar is reset to 0:
int j;
for (j = i + 1; j < block.Body.Count; j++) {
for (j = initEndPos; j < block.Body.Count; j++) {
ILVariable v2;
ILExpression storedVal;
// stloc(pinned_Var, conv.u(ldc.i4(0)))
if (block.Body[j].Match(ILCode.Stloc, out v2, out storedVal) && v2 == pinnedVar) {
if (IsNullOrZero(storedVal)) {
// Create fixed statement from i to j
ILFixedStatement stmt = new ILFixedStatement();
stmt.Initializer = initValue;
stmt.BodyBlock = new ILBlock(block.Body.GetRange(i + 1, j - i - 1)); // from i+1 to j-1 (inclusive)
block.Body.RemoveRange(i + 1, j - i); // from j+1 to i (inclusive)
block.Body[i] = stmt;
if (pinnedVar.Type.IsByReference)
pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType);
break;
}
}
}
// Create fixed statement from i to j
fixedStmt = new ILFixedStatement();
fixedStmt.Initializers.Add(initValue);
fixedStmt.BodyBlock = new ILBlock(block.Body.GetRange(initEndPos, j - initEndPos)); // from initEndPos to j-1 (inclusive)
block.Body.RemoveRange(i + 1, Math.Min(j, block.Body.Count - 1) - i); // from i+1 to j (inclusive)
block.Body[i] = fixedStmt;
if (pinnedVar.Type.IsByReference)
pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType);
}
bool IsNullOrZero(ILExpression expr)
@ -330,11 +348,13 @@ namespace ICSharpCode.Decompiler.ILAst @@ -330,11 +348,13 @@ namespace ICSharpCode.Decompiler.ILAst
return (expr.Code == ILCode.Ldc_I4 && (int)expr.Operand == 0) || expr.Code == ILCode.Ldnull;
}
bool MatchFixedInitializer(ILBlock block, int i, out ILVariable pinnedVar, out ILExpression initValue)
bool MatchFixedInitializer(ILBlock block, int i, out ILVariable pinnedVar, out ILExpression initValue, out int nextPos)
{
if (block.Body[i].Match(ILCode.Stloc, out pinnedVar, out initValue)) {
if (block.Body[i].Match(ILCode.Stloc, out pinnedVar, out initValue) && pinnedVar.IsPinned && !IsNullOrZero(initValue)) {
initValue = (ILExpression)block.Body[i];
return pinnedVar.IsPinned;
nextPos = i + 1;
HandleStringFixing(pinnedVar, block.Body, ref nextPos, ref initValue);
return true;
}
ILCondition ifStmt = block.Body[i] as ILCondition;
ILExpression arrayLoadingExpr;
@ -345,23 +365,28 @@ namespace ICSharpCode.Decompiler.ILAst @@ -345,23 +365,28 @@ namespace ICSharpCode.Decompiler.ILAst
&& ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out pinnedVar, out trueValue)
&& pinnedVar.IsPinned && IsNullOrZero(trueValue))
{
ILVariable stlocVar;
ILExpression falseValue;
if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1
&& ifStmt.FalseBlock.Body[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar)
{
ILVariable loadedVariable;
if (falseValue.Code == ILCode.Ldelema
&& falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable
&& IsNullOrZero(falseValue.Arguments[1]))
if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 && ifStmt.FalseBlock.Body[0] is ILFixedStatement) {
ILFixedStatement fixedStmt = (ILFixedStatement)ifStmt.FalseBlock.Body[0];
ILVariable stlocVar;
ILExpression falseValue;
if (fixedStmt.Initializers.Count == 1 && fixedStmt.BodyBlock.Body.Count == 0
&& fixedStmt.Initializers[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar)
{
initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr);
return true;
ILVariable loadedVariable;
if (falseValue.Code == ILCode.Ldelema
&& falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable
&& IsNullOrZero(falseValue.Arguments[1]))
{
initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr);
nextPos = i + 1;
return true;
}
}
}
}
}
initValue = null;
nextPos = -1;
return false;
}
@ -392,6 +417,54 @@ namespace ICSharpCode.Decompiler.ILAst @@ -392,6 +417,54 @@ namespace ICSharpCode.Decompiler.ILAst
else
return expr;
}
bool HandleStringFixing(ILVariable pinnedVar, List<ILNode> body, ref int pos, ref ILExpression fixedStmtInitializer)
{
// fixed (stloc(pinnedVar, ldloc(text))) {
// var1 = var2 = conv.i(ldloc(pinnedVar))
// if (logicnot(logicnot(var1))) {
// var2 = add(var1, call(RuntimeHelpers::get_OffsetToStringData))
// }
// stloc(ptrVar, var2)
// ...
if (pos >= body.Count)
return false;
ILVariable var1, var2;
ILExpression varAssignment, ptrInitialization;
if (!(body[pos].Match(ILCode.Stloc, out var1, out varAssignment) && varAssignment.Match(ILCode.Stloc, out var2, out ptrInitialization)))
return false;
if (!(var1.IsGenerated && var2.IsGenerated))
return false;
if (ptrInitialization.Code == ILCode.Conv_I || ptrInitialization.Code == ILCode.Conv_U)
ptrInitialization = ptrInitialization.Arguments[0];
if (!ptrInitialization.MatchLdloc(pinnedVar))
return false;
ILCondition ifStmt = body[pos + 1] as ILCondition;
if (!(ifStmt != null && ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && (ifStmt.FalseBlock == null || ifStmt.FalseBlock.Body.Count == 0)))
return false;
if (!UnpackDoubleNegation(ifStmt.Condition).MatchLdloc(var1))
return false;
ILVariable assignedVar;
ILExpression assignedExpr;
if (!(ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out assignedVar, out assignedExpr) && assignedVar == var2 && assignedExpr.Code == ILCode.Add))
return false;
MethodReference calledMethod;
if (!(assignedExpr.Arguments[0].MatchLdloc(var1) && assignedExpr.Arguments[1].Match(ILCode.Call, out calledMethod)))
return false;
if (!(calledMethod.Name == "get_OffsetToStringData" && calledMethod.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers"))
return false;
ILVariable pointerVar;
if (body[pos + 2].Match(ILCode.Stloc, out pointerVar, out assignedExpr) && assignedExpr.MatchLdloc(var2)) {
pos += 3;
fixedStmtInitializer.Operand = pointerVar;
return true;
}
return false;
}
#endregion
}
}

323
ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs

@ -26,22 +26,54 @@ namespace ICSharpCode.Decompiler.ILAst @@ -26,22 +26,54 @@ namespace ICSharpCode.Decompiler.ILAst
ta.module = context.CurrentMethod.Module;
ta.typeSystem = ta.module.TypeSystem;
ta.method = method;
ta.InferTypes(method);
ta.InferRemainingStores();
// Now that stores were inferred, we can infer the remaining instructions that depended on those stored
// (but which didn't provide an expected type for the store)
// For example, this is necessary to make a switch() over a generated variable work correctly.
ta.InferTypes(method);
ta.CreateDependencyGraph(method);
ta.IdentifySingleLoadVariables();
ta.RunInference();
}
sealed class ExpressionToInfer
{
public ILExpression Expression;
public bool Done;
/// <summary>
/// Set for assignment expressions that should wait until the variable type is available
/// from the context where the variable is used.
/// </summary>
public ILVariable DependsOnSingleLoad;
/// <summary>
/// The list variables that are read by this expression.
/// </summary>
public List<ILVariable> Dependencies = new List<ILVariable>();
public override string ToString()
{
if (Done)
return "[Done] " + Expression.ToString();
else
return Expression.ToString();
}
}
DecompilerContext context;
TypeSystem typeSystem;
ILBlock method;
ModuleDefinition module;
List<ILExpression> storedToGeneratedVariables = new List<ILExpression>();
HashSet<ILVariable> inferredVariables = new HashSet<ILVariable>();
List<ExpressionToInfer> allExpressions = new List<ExpressionToInfer>();
DefaultDictionary<ILVariable, List<ExpressionToInfer>> assignmentExpressions = new DefaultDictionary<ILVariable, List<ExpressionToInfer>>(_ => new List<ExpressionToInfer>());
HashSet<ILVariable> singleLoadVariables = new HashSet<ILVariable>();
void InferTypes(ILNode node)
#region CreateDependencyGraph
/// <summary>
/// Creates the "ExpressionToInfer" instances (=nodes in dependency graph)
/// </summary>
/// <remarks>
/// We are using a dependency graph to ensure that expressions are analyzed in the correct order.
/// </remarks>
void CreateDependencyGraph(ILNode node)
{
ILCondition cond = node as ILCondition;
if (cond != null) {
@ -51,59 +83,142 @@ namespace ICSharpCode.Decompiler.ILAst @@ -51,59 +83,142 @@ namespace ICSharpCode.Decompiler.ILAst
if (loop != null && loop.Condition != null) {
loop.Condition.ExpectedType = typeSystem.Boolean;
}
ILTryCatchBlock.CatchBlock catchBlock = node as ILTryCatchBlock.CatchBlock;
if (catchBlock != null && catchBlock.ExceptionVariable != null && catchBlock.ExceptionType != null && catchBlock.ExceptionVariable.Type == null) {
catchBlock.ExceptionVariable.Type = catchBlock.ExceptionType;
}
ILExpression expr = node as ILExpression;
if (expr != null) {
foreach (ILExpression store in expr.GetSelfAndChildrenRecursive<ILExpression>(e => e.Code == ILCode.Stloc)) {
ILVariable v = (ILVariable)store.Operand;
if (v.IsGenerated && v.Type == null && !inferredVariables.Contains(v) && HasSingleLoad(v)) {
// Don't deal with this node or its children yet,
// wait for the expected type to be inferred first.
// This happens with the arg_... variables introduced by the ILAst - we skip inferring the whole statement,
// and first infer the statement that reads from the arg_... variable.
// The ldloc inference will write the expected type to the variable, and the next InferRemainingStores() pass
// will then infer this statement with the correct expected type.
storedToGeneratedVariables.Add(expr);
// However, it is possible that this statement both writes to and reads from the variable (think inlined assignments).
if (expr.GetSelfAndChildrenRecursive<ILExpression>(e => e.Code == ILCode.Ldlen && e.Operand == v).Any()) {
// In this case, we analyze it now anyways, and will re-evaluate it later
break;
} else {
return;
}
ExpressionToInfer expressionToInfer = new ExpressionToInfer();
expressionToInfer.Expression = expr;
allExpressions.Add(expressionToInfer);
FindNestedAssignments(expr, expressionToInfer);
if (expr.Code == ILCode.Stloc && ((ILVariable)expr.Operand).Type == null)
assignmentExpressions[(ILVariable)expr.Operand].Add(expressionToInfer);
return;
}
foreach (ILNode child in node.GetChildren()) {
CreateDependencyGraph(child);
}
}
void FindNestedAssignments(ILExpression expr, ExpressionToInfer parent)
{
foreach (ILExpression arg in expr.Arguments) {
if (arg.Code == ILCode.Stloc) {
ExpressionToInfer expressionToInfer = new ExpressionToInfer();
expressionToInfer.Expression = arg;
allExpressions.Add(expressionToInfer);
FindNestedAssignments(arg, expressionToInfer);
ILVariable v = (ILVariable)arg.Operand;
if (v.Type == null) {
assignmentExpressions[v].Add(expressionToInfer);
// the instruction that consumes the stloc result is handled as if it was reading the variable
parent.Dependencies.Add(v);
}
} else {
ILVariable v;
if (arg.Match(ILCode.Ldloc, out v) && v.Type == null) {
parent.Dependencies.Add(v);
}
FindNestedAssignments(arg, parent);
}
bool anyArgumentIsMissingType = expr.Arguments.Any(a => a.InferredType == null);
if (expr.InferredType == null || anyArgumentIsMissingType)
expr.InferredType = InferTypeForExpression(expr, expr.ExpectedType, forceInferChildren: anyArgumentIsMissingType);
}
foreach (ILNode child in node.GetChildren()) {
InferTypes(child);
}
#endregion
void IdentifySingleLoadVariables()
{
// Find all variables that are assigned to exactly a single time:
var q = from expr in allExpressions
from v in expr.Dependencies
group expr by v;
foreach (var g in q.ToArray()) {
ILVariable v = g.Key;
if (g.Count() == 1 && g.Single().Expression.GetSelfAndChildrenRecursive<ILExpression>().Count(e => e.Operand == v) == 1) {
singleLoadVariables.Add(v);
// Mark the assignments as dependent on the type from the single load:
foreach (var assignment in assignmentExpressions[v]) {
assignment.DependsOnSingleLoad = v;
}
}
}
}
bool HasSingleLoad(ILVariable v)
void RunInference()
{
int loads = 0;
foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) {
if (expr.Operand == v) {
if (expr.Code == ILCode.Ldloc)
loads++;
else if (expr.Code != ILCode.Stloc)
return false;
int numberOfExpressionsAlreadyInferred = 0;
// Two flags that allow resolving cycles:
bool ignoreSingleLoadDependencies = false;
bool assignVariableTypesBasedOnPartialInformation = false;
while (numberOfExpressionsAlreadyInferred < allExpressions.Count) {
int oldCount = numberOfExpressionsAlreadyInferred;
foreach (ExpressionToInfer expr in allExpressions) {
if (!expr.Done && expr.Dependencies.TrueForAll(v => v.Type != null || singleLoadVariables.Contains(v))
&& (expr.DependsOnSingleLoad == null || expr.DependsOnSingleLoad.Type != null || ignoreSingleLoadDependencies))
{
RunInference(expr.Expression);
expr.Done = true;
numberOfExpressionsAlreadyInferred++;
}
}
if (numberOfExpressionsAlreadyInferred == oldCount) {
if (ignoreSingleLoadDependencies) {
if (assignVariableTypesBasedOnPartialInformation)
throw new InvalidOperationException("Could not infer any expression");
else
assignVariableTypesBasedOnPartialInformation = true;
} else {
// We have a cyclic dependency; we'll try if we can resolve it by ignoring single-load dependencies.
// This can happen if the variable was not actually assigned an expected type by the single-load instruction.
ignoreSingleLoadDependencies = true;
continue;
}
} else {
assignVariableTypesBasedOnPartialInformation = false;
ignoreSingleLoadDependencies = false;
}
// Now infer types for variables:
foreach (var pair in assignmentExpressions) {
ILVariable v = pair.Key;
if (v.Type == null && (assignVariableTypesBasedOnPartialInformation ? pair.Value.Any(e => e.Done) : pair.Value.All(e => e.Done))) {
TypeReference inferredType = null;
foreach (ExpressionToInfer expr in pair.Value) {
Debug.Assert(expr.Expression.Code == ILCode.Stloc);
ILExpression assignedValue = expr.Expression.Arguments.Single();
if (assignedValue.InferredType != null) {
if (inferredType == null) {
inferredType = assignedValue.InferredType;
} else {
// pick the common base type
inferredType = TypeWithMoreInformation(inferredType, assignedValue.InferredType);
}
}
}
if (inferredType == null)
inferredType = typeSystem.Object;
v.Type = inferredType;
// Assign inferred type to all the assignments (in case they used different inferred types):
foreach (ExpressionToInfer expr in pair.Value) {
expr.Expression.InferredType = inferredType;
// re-infer if the expected type has changed
InferTypeForExpression(expr.Expression.Arguments.Single(), inferredType);
}
}
}
}
return loads == 1;
}
void InferRemainingStores()
void RunInference(ILExpression expr)
{
while (storedToGeneratedVariables.Count > 0) {
List<ILExpression> stored = storedToGeneratedVariables;
storedToGeneratedVariables = new List<ILExpression>();
foreach (ILExpression expr in stored)
InferTypes(expr);
if (!(storedToGeneratedVariables.Count < stored.Count))
throw new InvalidOperationException("Infinite loop in type analysis detected.");
bool anyArgumentIsMissingExpectedType = expr.Arguments.Any(a => a.ExpectedType == null);
if (expr.InferredType == null || anyArgumentIsMissingExpectedType)
InferTypeForExpression(expr, expr.ExpectedType, forceInferChildren: anyArgumentIsMissingExpectedType);
foreach (var arg in expr.Arguments) {
if (arg.Code != ILCode.Stloc) {
RunInference(arg);
}
}
}
@ -118,7 +233,8 @@ namespace ICSharpCode.Decompiler.ILAst @@ -118,7 +233,8 @@ namespace ICSharpCode.Decompiler.ILAst
{
if (expectedType != null && expr.ExpectedType != expectedType) {
expr.ExpectedType = expectedType;
forceInferChildren = true;
if (expr.Code != ILCode.Stloc) // stloc is special case and never gets re-evaluated
forceInferChildren = true;
}
if (forceInferChildren || expr.InferredType == null)
expr.InferredType = DoInferTypeForExpression(expr, expectedType, forceInferChildren);
@ -159,22 +275,17 @@ namespace ICSharpCode.Decompiler.ILAst @@ -159,22 +275,17 @@ namespace ICSharpCode.Decompiler.ILAst
case ILCode.Stloc:
{
ILVariable v = (ILVariable)expr.Operand;
if (forceInferChildren || v.Type == null) {
TypeReference t = InferTypeForExpression(expr.Arguments.Single(), ((ILVariable)expr.Operand).Type);
if (v.Type == null)
v.Type = t;
if (forceInferChildren) {
// do not use 'expectedType' in here!
InferTypeForExpression(expr.Arguments.Single(), v.Type);
}
return v.Type;
}
case ILCode.Ldloc:
{
ILVariable v = (ILVariable)expr.Operand;
if (v.Type == null) {
if (v.Type == null && singleLoadVariables.Contains(v)) {
v.Type = expectedType;
// Mark the variable as inferred. This is necessary because expectedType might be null
// (e.g. the only use of an arg_*-Variable is a pop statement),
// so we can't tell from v.Type whether it was already inferred.
inferredVariables.Add(v);
}
return v.Type;
}
@ -197,7 +308,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -197,7 +308,7 @@ namespace ICSharpCode.Decompiler.ILAst
else
InferTypeForExpression(expr.Arguments[i], method.DeclaringType);
} else {
InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(method.Parameters[method.HasThis ? i - 1: i].ParameterType, method));
InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(method.Parameters[method.HasThis ? i - 1 : i].ParameterType, method));
}
}
}
@ -292,8 +403,9 @@ namespace ICSharpCode.Decompiler.ILAst @@ -292,8 +403,9 @@ namespace ICSharpCode.Decompiler.ILAst
if (elementType != null) {
// An integer can be stored in any other integer of the same size.
int infoAmount = GetInformationAmount(elementType);
if (infoAmount == 1) infoAmount = 8;
if (infoAmount == GetInformationAmount(operandType) && IsSigned(elementType) != null && IsSigned(operandType) != null)
if (infoAmount == 1 && GetInformationAmount(operandType) == 8)
operandType = elementType;
else if (infoAmount == GetInformationAmount(operandType) && IsSigned(elementType) != null && IsSigned(operandType) != null)
operandType = elementType;
}
if (forceInferChildren) {
@ -325,20 +437,26 @@ namespace ICSharpCode.Decompiler.ILAst @@ -325,20 +437,26 @@ namespace ICSharpCode.Decompiler.ILAst
case ILCode.Neg:
return InferTypeForExpression(expr.Arguments.Single(), expectedType);
case ILCode.Add:
return InferArgumentsInAddition(expr, null, expectedType);
case ILCode.Sub:
return InferArgumentsInSubtraction(expr, null, expectedType);
case ILCode.Mul:
case ILCode.Or:
case ILCode.And:
case ILCode.Xor:
return InferArgumentsInBinaryOperator(expr, null, expectedType);
case ILCode.Add_Ovf:
return InferArgumentsInAddition(expr, true, expectedType);
case ILCode.Sub_Ovf:
return InferArgumentsInSubtraction(expr, true, expectedType);
case ILCode.Mul_Ovf:
case ILCode.Div:
case ILCode.Rem:
return InferArgumentsInBinaryOperator(expr, true, expectedType);
case ILCode.Add_Ovf_Un:
return InferArgumentsInAddition(expr, false, expectedType);
case ILCode.Sub_Ovf_Un:
return InferArgumentsInSubtraction(expr, false, expectedType);
case ILCode.Mul_Ovf_Un:
case ILCode.Div_Un:
case ILCode.Rem_Un:
@ -481,11 +599,11 @@ namespace ICSharpCode.Decompiler.ILAst @@ -481,11 +599,11 @@ namespace ICSharpCode.Decompiler.ILAst
case ILCode.Conv_I:
case ILCode.Conv_Ovf_I:
case ILCode.Conv_Ovf_I_Un:
return HandleConversion(nativeInt, true, expr.Arguments[0], expectedType, typeSystem.IntPtr);
return HandleConversion(NativeInt, true, expr.Arguments[0], expectedType, typeSystem.IntPtr);
case ILCode.Conv_U:
case ILCode.Conv_Ovf_U:
case ILCode.Conv_Ovf_U_Un:
return HandleConversion(nativeInt, false, expr.Arguments[0], expectedType, typeSystem.UIntPtr);
return HandleConversion(NativeInt, false, expr.Arguments[0], expectedType, typeSystem.UIntPtr);
case ILCode.Conv_R4:
return typeSystem.Single;
case ILCode.Conv_R8:
@ -559,17 +677,17 @@ namespace ICSharpCode.Decompiler.ILAst @@ -559,17 +677,17 @@ namespace ICSharpCode.Decompiler.ILAst
TypeReference HandleConversion(int targetBitSize, bool targetSigned, ILExpression arg, TypeReference expectedType, TypeReference targetType)
{
if (targetBitSize >= nativeInt && expectedType is PointerType) {
if (targetBitSize >= NativeInt && expectedType is PointerType) {
InferTypeForExpression(arg, expectedType);
return expectedType;
}
TypeReference argType = InferTypeForExpression(arg, null);
if (targetBitSize >= nativeInt && argType is ByReferenceType) {
if (targetBitSize >= NativeInt && argType is ByReferenceType) {
// conv instructions on managed references mean that the GC should stop tracking them, so they become pointers:
PointerType ptrType = new PointerType(((ByReferenceType)argType).ElementType);
InferTypeForExpression(arg, ptrType);
return ptrType;
} else if (targetBitSize >= nativeInt && argType is PointerType) {
} else if (targetBitSize >= NativeInt && argType is PointerType) {
return argType;
}
return (GetInformationAmount(expectedType) == targetBitSize && IsSigned(expectedType) == targetSigned) ? expectedType : targetType;
@ -679,19 +797,82 @@ namespace ICSharpCode.Decompiler.ILAst @@ -679,19 +797,82 @@ namespace ICSharpCode.Decompiler.ILAst
}
}
TypeReference InferArgumentsInAddition(ILExpression expr, bool? isSigned, TypeReference expectedType)
{
ILExpression left = expr.Arguments[0];
ILExpression right = expr.Arguments[1];
TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType);
if (leftPreferred is PointerType) {
left.InferredType = left.ExpectedType = leftPreferred;
InferTypeForExpression(right, typeSystem.IntPtr);
return leftPreferred;
} else {
TypeReference rightPreferred = DoInferTypeForExpression(right, expectedType);
if (rightPreferred is PointerType) {
InferTypeForExpression(left, typeSystem.IntPtr);
right.InferredType = right.ExpectedType = rightPreferred;
return rightPreferred;
} else if (leftPreferred == rightPreferred) {
return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred;
} else if (rightPreferred == DoInferTypeForExpression(left, rightPreferred)) {
return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = rightPreferred;
} else if (leftPreferred == DoInferTypeForExpression(right, leftPreferred)) {
return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred;
} else {
left.ExpectedType = right.ExpectedType = TypeWithMoreInformation(leftPreferred, rightPreferred);
left.InferredType = DoInferTypeForExpression(left, left.ExpectedType);
right.InferredType = DoInferTypeForExpression(right, right.ExpectedType);
return left.ExpectedType;
}
}
}
TypeReference InferArgumentsInSubtraction(ILExpression expr, bool? isSigned, TypeReference expectedType)
{
ILExpression left = expr.Arguments[0];
ILExpression right = expr.Arguments[1];
TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType);
if (leftPreferred is PointerType) {
left.InferredType = left.ExpectedType = leftPreferred;
InferTypeForExpression(right, typeSystem.IntPtr);
return leftPreferred;
} else {
TypeReference rightPreferred = DoInferTypeForExpression(right, expectedType);
if (leftPreferred == rightPreferred) {
return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred;
} else if (rightPreferred == DoInferTypeForExpression(left, rightPreferred)) {
return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = rightPreferred;
} else if (leftPreferred == DoInferTypeForExpression(right, leftPreferred)) {
return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred;
} else {
left.ExpectedType = right.ExpectedType = TypeWithMoreInformation(leftPreferred, rightPreferred);
left.InferredType = DoInferTypeForExpression(left, left.ExpectedType);
right.InferredType = DoInferTypeForExpression(right, right.ExpectedType);
return left.ExpectedType;
}
}
}
TypeReference TypeWithMoreInformation(TypeReference leftPreferred, TypeReference rightPreferred)
{
int left = GetInformationAmount(leftPreferred);
int right = GetInformationAmount(rightPreferred);
if (left < right)
if (left < right) {
return rightPreferred;
else
} else if (left > right) {
return leftPreferred;
} else {
// TODO
return leftPreferred;
}
}
const int nativeInt = 33; // treat native int as between int32 and int64
/// <summary>
/// Information amount used for IntPtr.
/// </summary>
public const int NativeInt = 33; // treat native int as between int32 and int64
static int GetInformationAmount(TypeReference type)
public static int GetInformationAmount(TypeReference type)
{
if (type == null)
return 0;
@ -725,7 +906,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -725,7 +906,7 @@ namespace ICSharpCode.Decompiler.ILAst
return 64;
case MetadataType.IntPtr:
case MetadataType.UIntPtr:
return nativeInt;
return NativeInt;
default:
return 100; // we consider structs/objects to have more information than any primitives
}

18
ICSharpCode.Decompiler/Tests/UnsafeCode.cs

@ -56,4 +56,22 @@ public class UnsafeCode @@ -56,4 +56,22 @@ public class UnsafeCode
{
return d->ToString();
}
public unsafe void FixMultipleStrings(string text)
{
fixed (char* c = text, d = Environment.UserName, e = text) {
*c = 'c';
*d = 'd';
*e = 'e';
}
}
public unsafe string StackAlloc(int count)
{
char* a = stackalloc char[count];
for (int i = 0; i < count; i++) {
a[i] = (char)i;
}
return PointerReferenceExpression((double*)a);
}
}

14
ILSpy/Commands.cs

@ -56,19 +56,9 @@ namespace ICSharpCode.ILSpy @@ -56,19 +56,9 @@ namespace ICSharpCode.ILSpy
}
[ExportMainMenuCommand(Menu = "_File", Header = "_Save Code...", MenuIcon = "Images/SaveFile.png", MenuCategory = "Save", MenuOrder = 0)]
sealed class SaveCommand : SimpleCommand
sealed class SaveCommand : CommandWrapper
{
public override void Execute(object parameter)
{
MainWindow mainWindow = MainWindow.Instance;
if (mainWindow.SelectedNodes.Count() == 1) {
if (mainWindow.SelectedNodes.Single().Save(mainWindow.TextView))
return;
}
mainWindow.TextView.SaveToDisk(mainWindow.CurrentLanguage,
mainWindow.SelectedNodes,
new DecompilationOptions() { FullDecompilation = true });
}
public SaveCommand() : base(ApplicationCommands.Save) {}
}
class CommandWrapper : ICommand

3
ILSpy/MainWindow.xaml

@ -22,6 +22,9 @@ @@ -22,6 +22,9 @@
<CommandBinding
Command="Refresh"
Executed="RefreshCommandExecuted" />
<CommandBinding
Command="Save"
Executed="SaveCommandExecuted" />
<CommandBinding
Command="BrowseBack"
CanExecute="BackCommandCanExecute"

11
ILSpy/MainWindow.xaml.cs

@ -445,6 +445,17 @@ namespace ICSharpCode.ILSpy @@ -445,6 +445,17 @@ namespace ICSharpCode.ILSpy
decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions());
}
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
if (this.SelectedNodes.Count() == 1) {
if (this.SelectedNodes.Single().Save(this.TextView))
return;
}
this.TextView.SaveToDisk(this.CurrentLanguage,
this.SelectedNodes,
new DecompilationOptions() { FullDecompilation = true });
}
public void RefreshDecompiledView()
{
TreeView_SelectionChanged(null, null);

Loading…
Cancel
Save