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

Option to not inline inner classes? #139

Closed
andrewleech opened this issue Apr 12, 2020 · 5 comments
Closed

Option to not inline inner classes? #139

andrewleech opened this issue Apr 12, 2020 · 5 comments

Comments

@andrewleech
Copy link

Hi,
I'm the dexpatcher user that started the discussion in DexPatcher/dexpatcher-tool#33

Thanks so much for the patch made in response to that!

I'm testing out a build from master with that, hoping to compare the difference with the new checks vs --usesignatures however I'm running into some decompilation failures with other parts of the app.

The new issue seems to be caused by an inner class (obfuscated to the name a06$a) being inlined.
One of this inner class' functions then tries to create an instance of itself and cfr responds with ig62 = new /* invalid duplicate definition of identical inner class */;

I more often work with jadx with the arg --no-inline-anonymous to keep inline classes in their separate named form. The decompilation from that looks a little like:

public final class a06 extends td {
    public static final class a extends sf6 implements ig6<il6, xe6<? super cd6>, Object> {
        public final xe6<cd6> create(Object obj, xe6<?> xe6) {
            a aVar = new a(this.this$0, this.$block, xe6);
        }
    }
}

However cfr is inlining the inner class a into the outer class function that uses it.

public final class a06 extends td {
    public final rm6 a(hg6<? super xe6<? super List<DebugFirmwareData>>, ? extends Object> hg62) {
        return gk6.b((il6)jl6.a((af6)zl6.a()), null, null, (ig6)new ig6<il6, xe6<? super cd6>, Object>((a06)this, hg62, null){

            public final xe6<cd6> create(Object object, xe6<?> ig62) {
                ig62 = new /* invalid duplicate definition of identical inner class */;
            }
        }
    }
}

Is there an arg for CFR to not inline inner classes? I've tried a lot from --help but haven't found anything that works.

@leibnitz27
Copy link
Owner

Hey @andrewleech (sorry, I'm a little backed up on issues, heh).

Do you have an example case? I don't have any current flags to stop this (because it's not the sort of thing that I've seen in the wild) - however I can certainly imagine it happening....

A synthetic example to add to the test cases (i.e. not something with copyright attached) would help!

Cheers!

@andrewleech
Copy link
Author

Hi, no pressure on backlog I know exactly what it's like!

I seem to have crafted a test case that represents it, I think the key features are the inner class with a function that needs to create another instance of the same inner class.
jadx:

package com.test;

public final class outer {

    public static final class inner {
        public final Object create(Object obj) {
            return new inner(this.this$0);
        }
    }

    public final Object func() {
        return new inner(this);
    }
}

cfr:

/*
 * Decompiled with CFR 0.150-SNAPSHOT.
 */
package com.test;

public final class outer {
    public final Object func() {
        return new Object(this){

            public final Object create(Object object) {
                return new /* invalid duplicate definition of identical inner class */;
            }
        };
    }
}

jar attached :-)
testcase-inner-recursive.zip

@leibnitz27
Copy link
Owner

leibnitz27 commented May 16, 2020

Ahhhhhhhh - I see - so - this SHOULD be totally impossible - this is an anonymous class, i.e. it can only be created at the call site.

So I guess at this point, it's necessary to detect that an anonymous inner class is being used 'illegally', and instead give it an explicit name..... ( i.e. your point about --no-inline-anonymous above)

@leibnitz27
Copy link
Owner

K - so I'm not a huge fan of options that can't be automatically set by recovery passes - I think it's possible to detect anonymous classes being abused, but it's suprisingly impractical.

Also, because of the propagation of the hidden instance variables, this is never going to generate "nice" code ;).

So I've added a couple of new options, but they WON'T be used by recovery passes - they are explicit.

--forbidmethodscopedclasses and --forbidanonymousclasses.

These generate

// Forbidding anonymous classes
/*
 * Decompiled with CFR 0.150-SNAPSHOT (51596b3).
 */
package com.test;

public final class outer {
    public final Object func() {
        public final class Inner {
            public final Object create(Object object) {
                return new Inner(this.this$0);
            }
        }
        return new Inner(this);
    }
}

and

// forbidding method scoped classes altogether.
/*
 * Decompiled with CFR 0.150-SNAPSHOT (51596b3).
 */
package com.test;

public final class outer {
    public final Object func() {
        return new inner(this);
    }

    public static final class inner {
        public final Object create(Object object) {
            return new inner(this.this$0);
        }
    }
}

@andrewleech
Copy link
Author

Very interesting, I wonder how the original code was generated this way! Thanks for the added options though, that sounds perfect. I'll give them a try next time I rework my project!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants