Comparative analysis of some Java decompilers


In this article, four decompilers โ€” Fernflower , CFR , Procyon, and jadx โ€” will be examined and compared in several ways.


: . , ( 2019) Java-.


โ€” (brontozyablik), Solar appScreener



โ€” Solar appScreener โ€” . Java-. : , . " 147- - -". , - .


: , ? : , , , , .


, :


  • ;
  • .

: (UPD: Fernflower') , . , . , , .


: , .



. , , Fernflower โ€” (analytical) . , , . ( , ). , .


:


  • ( ) ;
  • (foreach, try-with-resources, etc).

, , , .



( โ€” 2019 ). .


  • Fernflower โ€” , JetBrains. GitHub.
  • CFR (0.146) โ€” , , , , , "for fun". GitHub. .
  • Procyon (0.5.36) โ€” , Java. . Bitbucket.
  • jadx (1.0.0) โ€” , Dalvik, JVM. GitHub.

, , ,


.
  • JD-Core ( JD Project) โ€” , ( gui).
    UPD: GitHub.
  • Krakatau โ€” , , Java 8.
  • JAD โ€” ( Java 5).

, Windows: Cavaj, DJ Java Decompiler, JBVD, AndroChef. , - - , . , , ...


:


FernflowerCFRProcyonjadx
Apache 2.0MITApache 2.0Apache 2.0
Maven: org.benf.cfrMaven: org.bitbucket.mstrobelBintray
Java8, 988
Java 8Java 6Java 7Java 8
!README

, jadx Android. , jvm, dx. , jadx , jadx .


jadx DEX 37 , - , , .



Fernflower ( 16.09.19), CFR (0.146), Procyon (0.5.36) jadx (1.0.0). jadx .


, , โ€” Fernflower, , Java 8. . Java โ€” Procyon Java 9 , CFR ( Fernflower ).


java -jar fernflower.jar -dgs=1 -asc=1 -ind="    " <input-jar> <output-dir>
java -jar cfr-0.146.jar <input-jar> --outputpath <output-dir>
java -jar procyon-decompiler-0.5.36.jar -jar <input-jar> -o <output-dir>
./bin/jadx -d <output-dir> <input-jar> --show-bad-code

, .



  • .
  • .
  • .
  • .


Fernflower


, Intellij IDEA, .


, Fernflower โ€” Intellij IDEA. ( , โ€” Fernflower ).


, . master 3 ( 2019). , , , Intellij IDEA.


CFR


, ( ). , , -. (0.147), .


, .


Procyon


, (2019) . , . , , , .


jadx


, - . 20 2019 1.0.0. DVM .



, , . , .


jadx , 39 fernflower.jar , , .


, : ( , CFR ); , ( , , ), .


, , , :


  • ;
  • , ;
  • .

, , , ( CFR - ).


, .


Fernflower01016536
CFR082802
Procyon0796116

, Fernflower, , 34 36 โ€” variable <var> is already defined. CFR . Procyon' (10 16) - , boolean . - ( ).


, CFR โ€” , 4 . 10 , 72 โ€” . , "" CFR , , , .



: , - .


: , .


100 JAR- 5.2M (JAR-, , .class ).


Fernflower74
CFR43
Procyon74

โ€” 15 JAR- 14M.


Fernflower939
CFR128
Procyon573

, . CFR , Fernflower . , 14M โ€” .class .



, .


. , , jadx, . jadx , Android-.



, .


for-each


FullInstructionSequence.java


for (ExceptionHandler handler : handlers) {
    handler.from_instr = this.getPointerByAbsOffset(handler.from);
    handler.to_instr = this.getPointerByAbsOffset(handler.to);
    handler.handler_instr = this.getPointerByAbsOffset(handler.handler);
}

Fernflower for-each . .


, handler , - . , var3 , unchecked cast :


ExceptionHandler handler;
for (Iterator var3 = handlers.iterator(); var3.hasNext(); handler.handler_instr = this.getPointerByAbsOffset(handler.handler)) {
    handler = (ExceptionHandler)var3.next();
    handler.from_instr = this.getPointerByAbsOffset(handler.from);
    handler.to_instr = this.getPointerByAbsOffset(handler.to);
}


SSAConstructorSparseEx.java


varmaparr[varmaparr[1] == null ? 0 : 1]

Procyon'. , , , โ€” , :


varmaparr[varmaparr[1] != null];


IFernflowerPreferences.java


public interface IFernflowerPreferences {
    Map<String, Object> DEFAULTS = getDefaults();

    static Map<String, Object> getDefaults() { ... }
}

, Procyon. default, static getDefaults(), :


public interface IFernflowerPreferences {
    public static final Map<String, Object> DEFAULTS = getDefaults();

    // Error: non-static method getDefaults() 
    // cannot be referenced from a static context.    
    default Map<String, Object> getDefaults() { return ... }
}


, .


, .

unboxing


VarVersionPair.java


VarVersionsProcessor.java


public class VarVersionPair {   
    public final int var;
    public final int version;

    public VarVersionPair(int var, int version) {
        this.var = var;
        this.version = version;
    }

    public VarVersionPair(Integer var, Integer version) {
        this.var = var;
        this.version = version;
    }
}

////////////////// 

new VarVersionPair(ent.getKey().var /* int */, version.intValue() /* int */);

Fernflower


.


public class VarVersionPair {
    public final int var;
    public final int version;

    public VarVersionPair(int var, int version) {
        this.var = var;
        this.version = version;
    }

    public VarVersionPair(Integer var, Integer version) {
        this.var = var;
        this.version = version;
    }
}

////////////////////////

new VarVersionPair(((VarVersionPair)ent.getKey()).var/* int */, version/* Integer */);

Procyon


public class VarVersionPair {
    public final int var;
    public final int version;

    public VarVersionPair(final int var, final int version) {
        this.var = var;
        this.version = version;
    }

    public VarVersionPair(final Integer var, final Integer version) {
        this.var = var;
        this.version = version;
    }
}

////////////////////////

new VarVersionPair(ent.getKey().var /* int */, (int)version /* int */);

CFR


public class VarVersionPair {
    public final int var;
    public final int version;

    public VarVersionPair(int var, int version) {
        this.var = var;
        this.version = version;
    }

    public VarVersionPair(Integer var, Integer version) {
        this.var = var;
        this.version = version;
    }
}

////////////////////////

new VarVersionPair(ent.getKey().var /* int */, (int)version /* int */);

dx + jadx


, .


public class VarVersionPair {
    public final int var;
    public final int version;

    public VarVersionPair(int var, int version) {
        this.var = var2;
        this.version = version2;
    }

    public VarVersionPair(Integer var, Integer version) {
        this.var = var.intValue();
        this.version = version.intValue();
    }
}

////////////////////////

new VarVersionPair(((VarVersionPair) ent.getKey()).var /* int */, ((Integer) it.next()).intValue() /* int */);

Try-with-resources


ConsoleDecompiler.java


try (Writer out = new OutputStreamWriter(...)) {
    <try-body>
}
catch (IOException ex) {
   <catch-body>
}

Fernflower


, try-with-resources . , try-catch. (:


try {
    Writer out = new OutputStreamWriter(...);
    Throwable var8 = null;
    try {
        <try-body>
    } catch (Throwable var18) {
        var8 = var18;
        throw var18;
    } finally {
        if (out != null) {
            if (var8 != null) {
                try {
                    out.close();
                } catch (Throwable var17) {
                    var8.addSuppressed(var17);
                }
            } else {
                out.close();
            }
        }
    }
} catch (IOException var20) {
    <catch-body>
}

CFR


: 0.142 try-with-resources , 0.146 try.


UPD: 0.147.


try {
    try (OutputStreamWriter out = new OutputStreamWriter(...);){
        out.write(content);
    }
}
catch (IOException ex) {
    <catch-body>
}

Procyon


try (final Writer out = new OutputStreamWriter(...)) {
    out.write(content);
}
catch (IOException ex) {
    <catch-body>
}

dx + jadx (with --show-bad-code option)


jadx .


/* JADX WARNING: Code restructure failed: missing block: B:20:0x0048, code lost:
    r3 = move-exception;
 */
/* JADX WARNING: Code restructure failed: missing block: B:21:0x0049, code lost:
    if (r2 != null) goto L_0x004b;
 */
/* JADX WARNING: Code restructure failed: missing block: B:22:0x004b, code lost:
    if (r4 != null) goto L_0x004d;
 */
/* JADX WARNING: Code restructure failed: missing block: B:24:?, code lost:
    r2.close();
 */
/* JADX WARNING: Code restructure failed: missing block: B:26:?, code lost:
    throw r3;
 */
/* JADX WARNING: Code restructure failed: missing block: B:29:0x0056, code lost:
    r2.close();
 */
...
try {
    Writer out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
    Throwable th = null;
    <try-body>
    if (out == null) {
        return;
    }
    if (th != null) {
        try {
            out.close();
        } catch (Throwable th2) {
            th.addSuppressed(th2);
        }
    } else {
        out.close();
    }
} catch (IOException ex) {
    <catch-body>
} catch (Throwable th3) {
    r4.addSuppressed(th3);
}


ClassReference14Processor.java


graph.iterateExprents(exprent -> {
    for (Entry<ClassWrapper, MethodWrapper> ent : mapClassMeths.entrySet()) {
        <body>
    }
    return 0;
});

Fernflower


. for-each ( for-each).


graph.iterateExprents((exprentx) -> {
    Iterator var3 = mapClassMeths.entrySet().iterator();
    while(var3.hasNext()) {
        Entry<ClassWrapper, MethodWrapper> ent = (Entry)var3.next();
        <body>
    }
    return 0;
});

Procyon


for-each ( for-each Procyon ). ent iterator2 , , ent effectively final .


final Iterator<Map.Entry<ClassWrapper, MethodWrapper>> iterator2;
Map.Entry<ClassWrapper, MethodWrapper> ent;

graph.iterateExprents(exprent -> {
    // it probably tried to initialize iterator here but it failed miserably...
    mapClassMeths.entrySet().iterator();   
        while (iterator2.hasNext()) {
        ent = iterator2.next();
        <body>
    }
    return 0;
});

CFR


graph.iterateExprents(exprent -> {
    for (Map.Entry ent : mapClassMeths.entrySet()) {
        <body>
    }
    return 0;
});

dx + jadx


jadx . .


/*
// Can't load method instructions: Load method exception: Unknown instruction: 'invoke-custom/range' in method: 
    org.jetbrains.java.decompiler.main.ClassReference14Processor.processClassRec(org.jetbrains.java.decompiler.main.ClassesProcessor$ClassNode, java.util.Map, java.util.Set):void, dex: classes.dex
*/
throw new UnsupportedOperationException("Method not decompiled: org.jetbrains.java.decompiler.main.ClassReference14Processor.processClassRec(org.jetbrains.java.decompiler.main.ClassesProcessor$ClassNode, java.util.Map, java.util.Set):void");

for


SwitchInstruction.java


for (int i = 0, k = 0; i < len; i++, k++) {
    if (<condition>) {
        ...
        k++;
    }
    ...
}

Fernflower


i .


int i = 0;
for(int k = 0; i < len; ++k) {
    if (<condition>) {
        ...
        ++k;
    }
    ...
    ++i;
}

Procyon


for (int i = 0, k = 0; i < len; ++i, ++k) {
    if (<condition>) {
        ...
        ++k;
    }
    ...
}

CFR


( ).


int i = 0;
int k = 0;
while (i < len) {
    if (<condition>) {
        ...
        ++k;
    }
    ...
    ++i;
    ++k;
}

dx + jadx


, CFR.


int i = 0;
int k = 0;
while (i < len) {
    if (<condition>) {
        ...
        k++;
    }
    ...
    i++;
    k++;
}

Generics


.




ConcatenationHelper.java


List<Exprent> lstOperands = new ArrayList<>();

Fernflower


ArrayList lstOperands = new ArrayList();

Procyon


final List<Exprent> lstOperands = new ArrayList<Exprent>();

CFR


ArrayList<Exprent> lstOperands = new ArrayList<Exprent>();

dx + jadx


List<Exprent> lstOperands = new ArrayList<>();



VarTypeProcessor.java


LinkedList<Statement> stack = new LinkedList<>();
stack.add(root);                // root : RootStatement
stack.addAll(stat.getStats());  // stat.getStats() : Collection<Statements>

Fernflower


unchecked assignment .


LinkedList<Statement> stack = new LinkedList();
stack.add(root);
stack.addAll(stat.getStats())

Procyon


, .


final LinkedList<Statement> stack = new LinkedList<Statement>();
stack.add(root);
stack.addAll((Collection<? extends Statement>)stat.getStats());

CFR


, Statement <RootStatement>.


LinkedList<RootStatement> stack = new LinkedList<RootStatement>();
stack.add(root);
stack.addAll(stat.getStats()); 

dx + jadx


LinkedList<Statement> stack = new LinkedList<>();
stack.add(root);
stack.addAll(stat.getStats()); 



Statement.java


protected HashSet<Statement> continueSet = new HashSet<>();
...
continueSet.addAll(st.buildContinueSet());

Fernflower


protected HashSet<Statement> continueSet = new HashSet<>();
...
this.continueSet.addAll(st.buildContinueSet());

Procyon


.


protected HashSet<Statement> continueSet;
...
public Statement() {
    this.continueSet = new HashSet<Statement>();
    ...
}
...
this.continueSet.addAll((Collection<?>)st.buildContinueSet());

CFR


protected HashSet<Statement> continueSet = new HashSet<>();;
...
this.continueSet.addAll(st.buildContinueSet());

dx + jadx


protected HashSet<Statement> continueSet;
...
this.continueSet.addAll(st.buildContinueSet());

jadx dex


, jadx Android โ€” AntennaPod ( ).


.


jadx . , , :


private static Context context;

public static void init(Context context) {
  UpdateManager.context = context;
  ...
}

:


private static Context context;

public static void init(Context context) {
  context = context;
  ...
}


jadx , , :


(item1, item2) -> compareLong(item1.timePlayed, item2.timePlayed)

:


/* compiled from: lambda */
/* renamed from: de.danoeh.antennapod.core.storage.-$$Lambda$DBReader$J14FiokVfxZ2H5XUZEtHQOEEq_0 */
public final /* synthetic */ class $$Lambda$DBReader$J14FiokVfxZ2H5XUZEtHQOEEq_0 implements Comparator {
  public static final /* synthetic */ 
  $$Lambda$DBReader$J14FiokVfxZ2H5XUZEtHQOEEq_0 INSTANCE 
            = new $$Lambda$DBReader$J14FiokVfxZ2H5XUZEtHQOEEq_0();

  private /* synthetic */ $$Lambda$DBReader$J14FiokVfxZ2H5XUZEtHQOEEq_0() { }

  public final int compare(Object obj, Object obj2) {
    return DBReader.compareLong(((StatisticsItem)obj).timePlayed, 
                                ((StatisticsItem)obj2).timePlayed);
  }
}


. :


public Feed(...) {
    this(id, lastUpdate, title, null, link, 
         description, paymentLink, author, 
         language, type, feedIdentifier, imageUrl,
         fileUrl, downloadUrl, downloaded, 
         new FlattrStatus(), false, null, null, false);
}

:


public Feed(...) {
    long j = id;
    String str = lastUpdate;
    String str2 = title;
    String str3 = link;
    String str4 = description;
    String str5 = paymentLink;
    String str6 = author;
    String str7 = language;
    String str8 = type;
    String str9 = feedIdentifier;
    String str10 = imageUrl;
    String str11 = fileUrl;
    String str12 = downloadUrl;
    boolean z = downloaded;
    FlattrStatus flattrStatus = r5;
    FlattrStatus flattrStatus2 = new FlattrStatus();
    this(j, str, str2, null, str3, 
         str4, str5, str6, 
         str7, str8, str9, str10, 
         str11, str12, z, 
         flattrStatus, false, null, null, false);
}



, , jadx . :


URL url = new URI(BASE_SCHEME, BASE_HOST, 
                  String.format("/api/2/tags/%d.json", count), null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
JSONArray jsonTagList = new JSONArray(response);

:


JSONArray jsonTagList 
    = new JSONArray(executeRequest(new Builder().url(
        new URI(BASE_SCHEME, 
                this.BASE_HOST,
                String.format("/api/2/tags/%d.json", 
                new Object[]{Integer.valueOf(count)}), null).toURL())));


, jadx :


final String action = intent.getStringExtra(ARG_ACTION);
if (action != null) {
  switch(action) {
    case ACTION_SYNC:
      <code1>
    case ACTION_SYNC_SUBSCRIPTIONS:
      <code2>
    case ACTION_SYNC_ACTIONS:
      <code3>
    default:
      <code4>
  }
}

:


String action = intent.getStringExtra(ARG_ACTION);
        if (action != null) {
            Object obj = -1;
            int hashCode = action.hashCode();
            if (hashCode != -1744995379) {
                if (hashCode != 29421060) {
                    if (hashCode == 1497029227 && action.equals(ACTION_SYNC_ACTIONS)) {
                        obj = 2;
                    }
                } else if (action.equals(ACTION_SYNC_SUBSCRIPTIONS)) {
                    obj = 1;
                }
            } else if (action.equals(ACTION_SYNC)) {
                obj = null;
            }
  switch (obj) {
    case null:
      <code1>
    case 1:
      <code2>
    case 2:
      <code3>
    default:
      <code4>
  }
}


if(item != null) {
    return item.getId() == id;
}

...


FeedItem feedItem = this.item;
boolean z = true;
if (feedItem != null) {
    if (feedItem.getId() != id) {
        z = false;
    }
    return z;
}

, , , jadx, , . , , jadx 15 switch-case if-else , .



:


CFR


( for-each, try-with-resources , ), ( ). CFR .


โ€” , , , ( , , , ; ).


Procyon


, . - CFR Java 9 . Procyon ( ).


Fernflower


. ( ). , Fernflower Intellij IDEA, , .


jadx


( ) , Android. , ( , ). (, try-with-resources) DVM 37 . JAR .


P.S. , : . , , , , .


All Articles