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

StackOverflow while decompiling with 0.150 #191

Closed
andrewleech opened this issue Jun 24, 2020 · 7 comments
Closed

StackOverflow while decompiling with 0.150 #191

andrewleech opened this issue Jun 24, 2020 · 7 comments
Labels

Comments

@andrewleech
Copy link

CFR version

Using official released cfr-0.150.jar

java -jar cfr-0.150.jar --outputdir src\\java --jarfilter "com.<snip>.ed1" classes.jar

Description

Trying to decompile an obscure jar file (same as #139) I'm getting a reproducible failure on one of the classes:

Processing com.<snip>.ed1
Exception in thread "main" java.lang.StackOverflowError
        at java.util.HashMap.hash(Unknown Source)
        at java.util.HashMap.get(Unknown Source)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:515)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        <continues to repeat this line>

Re-running with the command filtered to only that particular class gives a slightly different traceback

Processing com.<snip>.ed1
Exception in thread "main" java.lang.StackOverflowError
        at org.benf.cfr.reader.util.collections.LazyMap.get(LazyMap.java:40)
        at org.benf.cfr.reader.util.collections.LazyExceptionRetainingMap.get(LazyExceptionRetainingMap.java:19)
        at org.benf.cfr.reader.state.DCCommonState.getClassFile(DCCommonState.java:179)
        at org.benf.cfr.reader.state.DCCommonState.getClassFile(DCCommonState.java:194)
        at org.benf.cfr.reader.bytecode.analysis.types.JavaRefTypeInstance.getClassFile(JavaRefTypeInstance.java:514)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:522)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:524)
        <continues to repeat this line>

I'm not personally blocked by this, I 'm working around the issue by filtering around the problematic class, eg --jarfilter "com.<snip>.(?!ed1)"

Example

The class causing this is not suitable for sharing here.

@andrewleech
Copy link
Author

I've just re-tested with a build from master ( 7b97993 ) and get this traceback with slightly updated file lines instead:

Exception in thread "main" java.lang.StackOverflowError
        at java.util.HashMap.hash(Unknown Source)
        at java.util.HashMap.get(Unknown Source)
        at org.benf.cfr.reader.util.collections.LazyMap.get(LazyMap.java:40)
        at org.benf.cfr.reader.util.collections.LazyExceptionRetainingMap.get(LazyExceptionRetainingMap.java:19)
        at org.benf.cfr.reader.state.DCCommonState.getClassFile(DCCommonState.java:179)
        at org.benf.cfr.reader.state.DCCommonState.getClassFile(DCCommonState.java:194)
        at org.benf.cfr.reader.bytecode.analysis.types.JavaRefTypeInstance.getClassFile(JavaRefTypeInstance.java:514)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:529)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)
        at org.benf.cfr.reader.entities.ClassFile.hasAccessibleField(ClassFile.java:531)

@leibnitz27
Copy link
Owner

Oh ouch. The only way I can see of that happening is if the classfile is its own base class (Or I've really messed up and somehow inferred that).

Pretty cute if someone's released one of those.....

Would be very handy to get an example, if not, can you check the class hierarchy (with, say, javap)?

@andrewleech
Copy link
Author

Hi, I was hoping it'd be an obvious/simple issue once pointed to the lines in the traceback. Turns out not, it's again something strange with the obfuscator used on this codebase. There's a few other classes in the same app that do the same thing.

I've taken a look at some of them in Bytecode Viewer.

Turns out the same class files make procyon fail too.

Fernflower gives me

public abstract class ed1 {
   public final w40 a;
   public final q11 b;
   public final w31 c;

   public ed1(w31 var1) {
      w40 var2 = new w40((byte)1, (byte)0);
      super();
      this.a = var2;
      this.c = var1;
      this.b = q11.a;
   }

   public ed1(w31 var1, w40 var2) {
      this.a = var2;
      this.c = var1;
      this.b = q11.a;
   }

However, the older cfr-0.143.jar included there gives

public abstract class ed1<T>
extends ed1<T> {
    public final w40 a;
    public final q11 b;
    public final w31 c;

    public ed1(w31 w312) {
        w40 w402 = new w40(1, 0);
        this.a = w402;
        this.c = w312;
        this.b = q11.a;
    }

    public ed1(w31 w312, w40 w402) {
        this.a = w402;
        this.c = w312;
        this.b = q11.a;
    }

so, yeah, there's that.

Based on the smali of the failing class above I've written a new failing example

.class public abstract Lcom/dead/foo;
.super Ljava/lang/Object;

.annotation system Ldalvik/annotation/Signature;
  value = {
    "<T:",
    "Ljava/lang/Object;",
    ">",
    "Lcom/dead/foo",
    "<TT;>;"
  }
.end annotation

.method public constructor <init>(Lcom/dead/bar;)V
  .registers 5
    new-instance v0, Lcom/dead/abc;
    const/4 v1, 1
    const/4 v2, 0
    invoke-direct { v0, v1, v2 }, Lcom/dead/abc;-><init>(BB)V
    invoke-direct { p0 }, Ljava/lang/Object;-><init>()V
    iput-object v0, p0, Lcom/dead/foo;->a:Lcom/dead/abc;
    iput-object p1, p0, Lcom/dead/foo;->c:Lcom/dead/bar;
    sget-object v0, Lcom/dead/efg;->a:Lcom/dead/efg;
    iput-object v0, p0, Lcom/dead/foo;->b:Lcom/dead/efg;
    return-void
.end method

jar attached, renamed to zip
out.zip

@leibnitz27
Copy link
Owner

Great example, thanks.

I try so hard not to trust (or, as Reagan said, "Trust, but verify" :) ) non-vital info - sneaking an illegal super in via a signature is crafty - hadn't considered that!

We now get

/*
 * Signature claims super is com.dead.foo<T>, not java.lang.Object - discarding signature.
 */
public abstract class foo {
    public foo(bar bar2) {
        abc abc2 = new abc(1, 0);
        this.a = abc2;
        this.c = bar2;
        this.b = efg.a;
    }
}

@leibnitz27
Copy link
Owner

Nicely, this didn't fire once on my entire corpus of other tests ;)

@andrewleech
Copy link
Author

Awesome, nice work thanks! Yeah the problem with unit testing is no matter how good you think your coverage is, you can still only test for usages and bugs you know of, but all to often there's someone trying to use/trip you up in a new and weird way.

PS please don't feel any pressure to fix issues like this when they seem to be only caused by this weird obfuscator, but if they're interesting I'm more than happy to give as much info as possible!

@leibnitz27
Copy link
Owner

Hah - weird problems like this are why I do this ;)

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

No branches or pull requests

2 participants