Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vs 2015 C# Anonymouse & Lambda Methods decompile bugg Fixing #637

Closed
AlaaAlHallaq opened this issue Aug 6, 2015 · 7 comments
Closed

Vs 2015 C# Anonymouse & Lambda Methods decompile bugg Fixing #637

AlaaAlHallaq opened this issue Aug 6, 2015 · 7 comments

Comments

@AlaaAlHallaq
Copy link

[Solution is Attached]

I noticted that vs 2015 C# compiler changed the way that they build the anonymouse methods, which lead to that non of any decompiler decompiles them okey, for Example

        class R{
            static string v; 
            public static void Main(string [] r){
                var x=r[0].Zip("",(a,b)=> v);
            }
        }

It's Decompiled to :

        class R{
            static string v; 
            public static void Main(string[] r)
            {
                IEnumerable<char> arg_27_0 = r[0];
                IEnumerable<char> arg_27_1 = "";
                Func<char, char, string> arg_27_2;
                if ((arg_27_2 = R.<>c.<>9__1_0) == null)
                {
                    arg_27_2 = (R.<>c.<>9__1_0 = new Func<char, char, string>(R.<>c.<>9.<Main>b__1_0));
                }
                arg_27_0.Zip(arg_27_1, arg_27_2);
            }
        }

So I tried hard to fix it, by:
1- remove unnececary checking for the delegate init.
2- remove all decompiler generated variables (arg_27_0,...) and replace their usage with their init Values.
I attached a Picture that demonstate the issue and the fixed version.
I noticed another buggy thing in the picture, i applied the patch on ILSpy2.2 and dnSpy1.1.1
the condition [where tag != 0] in the Linq Expression in ILSpy2.2 became
[where tag > 0] but in dnSpy It decompiled ok.

My patch is :

1- modifying
ilspy2.2src\ICSharpCode.Decompiler\Ast\Transforms\DelegateConstruction.cs:HandleAnonymousMethod

Fist four Lines of HandleAnonymousMethod Function I repalced them with

        if (!context.Settings.AnonymousMethods)
            return false; // anonymous method decompilation is disabled

        //Handle <>c vs2015 generated types
        MemberReferenceExpression mrefExp = target as MemberReferenceExpression;
        bool needHandling = false;
        if (mrefExp != null && mrefExp.MemberName.StartsWith("<>"))
        {
            var fchild = mrefExp.FirstChild as TypeReferenceExpression;
            if (fchild != null && fchild.ToString().EndsWith("<>c"))
            {
                needHandling = true;
            }
        }

        if (target != null && !(needHandling || target is IdentifierExpression || target is ThisReferenceExpression || target is NullReferenceExpression))
            return false; // don't copy arbitrary expressions, deal with identifiers only

2- Define new IAstTransform [VS2015Anon] and add it to the PipeLine after DelegateConstruction

ilspy2.2src\ICSharpCode.Decompiler\Ast\Transforms\TransformationPipeline.cs

public static IAstTransform[] CreatePipeline(DecompilerContext context)
    {
        return new IAstTransform[] {
            new PushNegation(),
            new DelegateConstruction(context),
            new VS2015Anon(context),.....

3- VS2015AnonTransform Code

    using System;
    using System.Threading;
    using ICSharpCode.NRefactory.CSharp;
    using System.Collections.Generic;
    using System.Linq;
    using System.Diagnostics;

    namespace ICSharpCode.Decompiler.Ast.Transforms{
        class VS2015Anon : IAstTransform
        {
            DecompilerContext context;
            public VS2015Anon(DecompilerContext context)
            {
                this.context = context;
            }
            public void Run(AstNode compilationUnit)
            {
                HandleVS2015CLambdas(compilationUnit);
            }

            #region Vs2015
            class ToDel
            {
                public IfElseStatement If;
                public IdentifierExpression Id;
                public AstNode Lambda;
            }

            void HandleVS2015CLambdas(AstNode syntax)
            {
                if (!context.Settings.AnonymousMethods)
                    return;
                //TODO::Extra Checking wheter fields and ids are delegates

                //EnumNodes(ast);
                var q = new Queue<AstNode>();

                q.Enqueue(syntax);
                var lst = new List<ToDel>();
                while (q.Count > 0)
                {
                    var r = q.Dequeue();
                    if (r is IfElseStatement)
                    {
                        IdentifierExpression id = null;
                        AstNode lambda;

                        if (isInitIF(r as IfElseStatement, out id, out lambda))
                        {
                            lst.Add(new ToDel { If = r as IfElseStatement, Id = id, Lambda = lambda });
                            //continue;
                        }
                    }
                    else if (r is BlockStatement)
                    {
                    }
                    foreach (var c in r.Children)
                    {
                        q.Enqueue(c);
                    }
                }
                DeleteThem(lst);
            }
            class Ref<T>
            {
                public T Value { get; set; }
                public static implicit operator T(Ref<T> v)
                {
                    return v.Value;
                }
            }
            AstNode FindId(IdentifierExpression id, ref AstNode searchStart)
            {
                var rf = new Ref<AstNode> { Value = searchStart };
                var fr = FindIds(id, rf, true).FirstOrDefault();
                searchStart = rf.Value;
                return fr;
            }
            IEnumerable<AstNode> FindAllIds(AstNode searchStart)
            {
                var q = new Queue<AstNode>();
                q.Enqueue(searchStart);
                while (q.Count > 0)
                {
                    var r = q.Dequeue();
                    if (r is IdentifierExpression)
                    {
                        var rid = r as IdentifierExpression;
                        yield return rid;
                    }
                    foreach (var c in r.Children)
                    {
                        q.Enqueue(c);
                    }
                }
                yield break;
            }
            IEnumerable<AstNode> FindIds(IdentifierExpression id, Ref<AstNode> searchStart, bool skipLambdas)
            {
                var q = new Queue<AstNode>();

                for (AstNode cur = searchStart; cur != null; cur = cur.NextSibling)
                {
                    q.Enqueue(cur);

                    var lst = new List<ToDel>();
                    while (q.Count > 0)
                    {
                        var r = q.Dequeue();
                        if (skipLambdas && (r is LambdaExpression || r is AnonymousMethodExpression))
                        {
                            continue;
                        }
                        if (r is IdentifierExpression)
                        {
                            var rid = r as IdentifierExpression;
                            if (rid.Identifier == id.Identifier)
                            {
                                searchStart.Value = cur;
                                yield return rid;
                                continue;
                            }
                        }
                        else if (r is Identifier)
                        {
                            var rid = r as Identifier;
                            if (rid.Name == id.Identifier)
                            {
                                searchStart.Value = cur;
                                yield return rid;
                                continue;
                            }
                        }
                        foreach (var c in r.Children)
                        {
                            q.Enqueue(c);
                        }
                    }
                }
                yield break;
            }
            class StatmentNode
            {
                public AstNode Statment;
                public AstNode Id;
            }
            class SingleUsage
            {
                public AstNode Target;
                public List<StatmentNode> Statents;
                public SingleUsage(AstNode Target, List<StatmentNode> Statents)
                {
                    this.Target = Target;
                    this.Statents = Statents;
                }
            }
            void FixSingleUsage(AstNode node, List<SingleUsage> toRemList)
            {

                var pr = node.Parent;
                //var nds=EnumNodes(pr);

                var ids = FindAllIds(node).ToList();
                foreach (var r in ids)
                {
                    var rName = (r as Identifier)?.Name ?? (r as IdentifierExpression)?.Identifier;

                    var nds2 = EnumNodes(pr);
                    Ref<AstNode> statment = new Ref<AstNode> { Value = pr.FirstChild };
                    var locs = FindIds(r as IdentifierExpression, statment, true).Select(x => new StatmentNode { Statment = statment, Id = x }).ToList();
                    if (locs.Count < 4 && locs.Count > 1)
                    {
                        if (locs.Count == 3)
                        {
                            //factor when-> (decl , assign , use)-> use
                            AstNode target;
                            if (NeedFactor3(locs, out target))
                            {
                                toRemList.Add(new SingleUsage(target, locs));
                            }
                        }
                        else if (locs.Count == 2)
                        {
                            //var n1 = EnumNodes(locs[0].Statment);
                            //var n2 = EnumNodes(locs[01].Statment);
                            //Test node1
                            AstNode target = null;
                            bool isFirstInit = false;
                            {
                                var n = locs[0].Statment as ExpressionStatement;
                                if (n != null)
                                {
                                    var nfr = n.FirstChild as AssignmentExpression;
                                    //TODO:: double-check Ids
                                    if (nfr != null && nfr.Left is IdentifierExpression)
                                    {
                                        isFirstInit = (nfr.Left as IdentifierExpression).Identifier == rName;
                                        target = nfr.Right;
                                        //
                                    }
                                }
                            }
                            if (isFirstInit && target != null)
                            {
                                toRemList.Add(new SingleUsage(target, locs));


                            }
                        }
                    }
                }

            }

            private bool NeedFactor3(List<StatmentNode> locs, out AstNode Target)
            {
                Target = null;
                bool isFrstDecl = false;
                var fr = locs[0].Statment as VariableDeclarationStatement;
                if (fr != null)
                {
                    if (fr.FirstChild is SimpleType && fr.FirstChild.NextSibling is VariableInitializer)
                    {
                        isFrstDecl = (fr.FirstChild.NextSibling as VariableInitializer).FirstChild is Identifier;
                    }
                }
                if (isFrstDecl)
                {
                    var sc = locs[1].Statment as ExpressionStatement;
                    if (sc != null)
                    {
                        if (sc.FirstChild is AssignmentExpression)
                        {
                            var assign = sc.FirstChild as AssignmentExpression;
                            if (assign.Left is IdentifierExpression)
                            {
                                Target = assign.Right;
                                return true;
                            }
                        }
                    }
                }
                return false;
            }

            class Singco : IComparer<SingleUsage>
            {
                bool test(SingleUsage x, SingleUsage y)
                {
                    var xS = x.Statents.Last().Statment;
                    return xS == y.Target || xS.Ancestors.Contains(y.Target);
                }
                public int Compare(SingleUsage x, SingleUsage y)
                {
                    if (test(x, y))
                        return -1;
                    else if (test(y, x))
                        return 1;
                    return 0;
                }
            }
            void DeleteThem(List<ToDel> dels)
            {

                var toRemList = new List<SingleUsage>();
                foreach (var d in dels)
                {
                    if (d.Id == null || d.If == null || d.Lambda == null)
                        continue;

                    var pBlock = d.If.Parent;
                    var ss = d.If.NextSibling;
                    var usage = FindId(d.Id, ref ss);


                    if (usage != null)
                    {
                        d.Lambda.Remove();
                        usage.ReplaceWith(d.Lambda);
                    }
                    //TODO:: check wheter [usage] is needed
                    var prev = d.If.PrevSibling as VariableDeclarationStatement;
                    if (prev != null)
                    {
                        if (prev.FirstChild is SimpleType && prev.FirstChild.NextSibling is VariableInitializer)
                        {
                            var vi = prev.FirstChild.NextSibling as VariableInitializer;
                            var did = vi.FirstChild as Identifier;
                            if (did.Name == d.Id.Identifier)
                            {
                                prev.Remove();
                            }
                        }
                    }
                    d.If.Remove();


                    if (usage != null && ss != null)
                    {
                        FixSingleUsage(ss, toRemList);
                    }


                }

                toRemList.Sort(new Singco());
                foreach (var rm in toRemList)
                {
                    var target = rm.Target;
                    var locs = rm.Statents;
                    try
                    {
                        if (locs.Count == 3)
                        {
                            if (locs[2].Id.Parent != null)
                            {
                                target.Remove();
                                locs[2].Id.ReplaceWith(target);
                                locs[0].Statment.Remove();
                                locs[1].Statment.Remove();
                            }
                        }
                        else if (locs.Count == 2)
                        {
                            if (locs[1].Id.Parent != null)
                            {
                                target.Remove();
                                locs[1].Id.ReplaceWith(target);
                                locs[0].Statment.Remove();
                            }

                        }
                    }
                    catch
                    {

                    }
                }

            }


            bool isInitIF(IfElseStatement exp, out IdentifierExpression id, out AstNode lambda)
            {
                Predicate<MemberReferenceExpression> isVs2015Field = (rf) =>
                {
                    var fr = rf.FirstChild;
                    while (fr.LastChild != null)
                        fr = fr.LastChild;
                    var c = fr as Identifier;
                    return c != null && c.Name == "<>c";
                };

                id = null; lambda = null;
                bool CondIsOk = false;
                var cond = exp.FirstChild as BinaryOperatorExpression;
                if (cond != null && cond.Operator == BinaryOperatorType.Equality)
                {
                    if (cond.Left is AssignmentExpression && cond.Right is NullReferenceExpression)
                    {
                        var assign = cond.Left as AssignmentExpression;
                        if (assign.Left is IdentifierExpression && assign.Right is MemberReferenceExpression)
                        {
                            id = assign.Left as IdentifierExpression;
                            CondIsOk = isVs2015Field(assign.Right as MemberReferenceExpression);
                        }
                    }
                }
                if (CondIsOk)
                {
                    var scnd = exp.FirstChild.NextSibling as BlockStatement;
                    if (scnd != null)
                    {
                        var fr = scnd.FirstChild as ExpressionStatement;
                        if (fr != null)
                        {
                            var assign = fr.FirstChild as AssignmentExpression;
                            if (assign != null && assign.Left is IdentifierExpression && assign.Right is AssignmentExpression)
                            {
                                assign = assign.Right as AssignmentExpression;
                                if (assign.Left is MemberReferenceExpression &&

                                    (assign.Right is LambdaExpression || assign.Right is AnonymousMethodExpression))
                                {
                                    lambda = assign.Right;
                                    return isVs2015Field(assign.Left as MemberReferenceExpression); ;
                                }
                            }
                        }
                    }
                }
                return false;
            }
            class Node<T>
            {
                public T Value;
                public List<Node<T>> Children = new List<Node<T>>();
                public override string ToString()
                {
                    try
                    {
                        return "{" + Value.GetType() + "} " + Value;
                    }
                    catch { }
                    return Value + "";
                }
            }
            //for debugging purpos
            Node<AstNode> EnumNodes(AstNode syntax)
            {

                var lst = new List<Node<AstNode>>();
                var q = new Queue<Node<AstNode>>();
                var start = new Node<AstNode> { Value = syntax };
                q.Enqueue(start);
                while (q.Count > 0)
                {
                    var r = q.Dequeue();
                    foreach (var c in r.Value.Children)
                    {
                        var n = new Node<AstNode> { Value = c };
                        r.Children.Add(n);
                        q.Enqueue(n);
                    }
                }
                return start;
            }
            #endregion
        }
    }

2015-8-6-9-57-22

@lextm
Copy link
Contributor

lextm commented Aug 6, 2015

Why not send a pull request?

@weltkante
Copy link
Contributor

This is a known problem of ILSpy, see issue #502, nobody officially working on supporting it though, I think most of their work is on the new decompiler branch

If you are going to do work on VS2015/Roslyn please make sure that you don't break decompilation of older assemblies, people will still need to decompile code from before VS2015/Roslyn.

I've been myself hotfixing things here and there to be able to read VS 2015 code, you can see it over at my fork - its far from complete, but it helps

I also made a fix to the delegate decompiler, which is different from yours, so you may want to compare your solution to this commit to check how much the results differ.

(And yes I'm planning to make a pull request and push back my fixes if the ilspy maintainers are interested, but I still want to get some more fixes working first.)

@heroboy
Copy link

heroboy commented Feb 19, 2016

Hi
Decompile anonymouse type is so hard. Actually ilspy doesn't correctly decompile the Unity's anonymouse type. Why not make the anonymouse type name a valid name that can be compiled. Maybe relate to #408

@dgrunwald
Copy link
Member

I think most of their work is on the new decompiler branch

I have some experimental ideas on that branch, but I'm no longer working on them.

@linquize
Copy link
Contributor

Note: dnSpy is a fork of ILSpy.
I have tried dnSpy. It is a lot of improvements over ILSpy.
However, dnSpy is licensed under GPL but ILSpy is licensed under MIT.
dnSpy can merge from ILSpy but not vice-versa.

@AlaaAlHallaq
Copy link
Author

I wrote this code to work around the fact that none of the current decompilers handles Roslyn code generation correctly,First I wrote to code for dnSpy, but because I'm too familiar to using ILSpy I applied the patch to it, I went to fixed the code generation for (async , await) methods but, It was a bit harder to understand how the code generation is done, in order to reverse it , so I "let it go".
I hope the above solution works fine, and help every one,
when I wrote the code I was not familiar with pull requests, so soon enough I will optimize the code, and make pull requests to the github repo.

@dgrunwald
Copy link
Member

The newdecompiler branch was merged to master recently.
It fixes this bug, and in general should handle Roslyn codegen just as well as pre-Roslyn. (we run our testcases with both compilers).

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants