GithubHelp home page GithubHelp logo

leibnitz27 / cfr Goto Github PK

View Code? Open in Web Editor NEW
1.9K 51.0 248.0 5.17 MB

This is the public repository for the CFR Java decompiler

Home Page: https://www.benf.org/other/cfr

License: MIT License

Java 100.00%

cfr's Introduction

CFR - Another Java Decompiler \o/

This is the public repository for the CFR decompiler, main site hosted at benf.org/other/cfr

CFR will decompile modern Java features - including much of Java 9, 12 & 14, but is written entirely in Java 6, so will work anywhere! (FAQ) - It'll even make a decent go of turning class files from other JVM languages back into java!

To use, simply run the specific version jar, with the class name(s) you want to decompile (either as a path to a class file, or as a fully qualified classname on your classpath). (--help to list arguments).

Alternately, to decompile an entire jar, simply provide the jar path, and if you want to emit files (which you probably do!) add --outputdir /tmp/putithere.

Getting CFR

The main site for CFR is benf.org/other/cfr, where releases are available with a bunch of rambling musings from the author.

Since 0.145, Binaries are published on github along with release tags.

You can also download CFR from your favourite maven repo, though releases are published a few days late usually, to allow for release regret.

Issues

If you have an issue, please DO NOT include copyright materials. I will have to delete your issue.

Building CFR

Dead easy!

Just ensure you have Maven installed. Then mvn compile in the root directory of this project will get you what you need.

Note: If you encounter a maven-compiler-plugin...: Compilation failure error while trying to compile the project then your JAVA_HOME environment variable is probably pointing to a JDK version that doesn't support 6 for the source or target compile options. Fix this by pointing JAVA_HOME to a JDK version that still supports compiling to Java 1.6 such as JDK 11. Also note, the version of Java on your JAVA may need to be greater than 1.6 if you are using Maven version >=3.3.1 which requires Java 1.7. The best solution is to use JDK 8, 9, 10 or 11 for both your PATH and JAVA_HOME.

The main class is org.benf.cfr.reader.Main, so once you've built, you can test it out (from target/classes)

java org.benf.cfr.reader.Main java.lang.Object

to get CFR to decompile java.lang.Object.

Decompilation tests

As part of the Maven build automatic decompilation tests are performed. They verify that the current decompiled output of CFR matches the expected previous output. The test data (Java class and JAR files) are part of a separate Git repository; it is therefore necessary to clone this repository with git clone --recurse-submodules. The expected output and CFR test configuration is however part of this repository to allow altering it without having to modify the corresponding test data. The test data is in the decompilation-test/test-data directory, and the respective expected data and custom configuration is in the decompilation-test/test-data-expected-output directory (with a similar directory structure, see Expected data structure below).

The decompilation tests are also performed by the GitHub workflow, and in case of test failures the unified diff is available in a workflow artifact called "decompilation-test-failures-diff".

The expected output is not the gold standard, it merely describes the currently expected output. There is nothing wrong with adjusting the expected output, if the changes to the decompilation results are reasonable.

The test class is org.benf.cfr.test.DecompilationTest. It can be modified to adjust the test directories, or to ignore certain class files or JARs. Additionally it is possible to directly execute the tests there from the IDE. This usually gives better output than what is shown by Maven, and allows using the built-in IDE functionality for showing differences between the expected and the actual data.

Options file

The decompilation process can be customized by adding an options file. Each line of it specifies a CFR option, with key and value separated by a space. Empty lines and lines starting with # are ignored and can be used for comments.

Example:

# Enable identifier renaming
renameillegalidents true

See Expected data structure below for how to name the file and where to place it.

Expected data structure

Class files

For class files the expected data and custom configuration is in the same respective location under test-data-expected-output, with the file names being based on the class file name.

For example, for the class file test-data/classes/subdir/MyClass.class the following files can be used:

  • test-data-expected-output/classes/subdir/
    • MyClass.expected.java
      Contains the expected decompiled Java output, optionally with decompilation notes.
    • MyClass.options
      An optional options file customizing decompilation.
    • MyClass.expected.summary
      Contains the expected summary reported by the CFR API. Can be omitted when no summary is produced.
    • MyClass.expected.exceptions
      Contains the expected exceptions reported by the CFR API. Can be omitted when no exception is reported.

JAR files

For JAR files the expected data and custom configuration is inside a directory with the name of the JAR file in the respective location under test-data-expected-output. Expected Java output files include the package name in their file name, for example mypackage.MyClass.java. The options file and the expected summary and exceptions file use the "file name" _. For multi-release JARs the directory contains subdirectories for version specific classes. The directory name has the form java-<version>.

For example, for the multi-release JAR file test-data/jars/subdir/MyJar.jar the following files can be used:

  • test-data-expected-output/jars/subdir/MyJar/
    • mypackage.MyClass.java
      Contains the expected decompiled Java output for the class mypackage.MyClass, optionally with decompilation notes.
    • java-11/mypackage.MyClass.java
      Contains the expected decompiled Java output for a class file specific to Java 11 and higher (for multi-release JARs).
    • _.options
      An optional options file customizing decompilation.
    • _.expected.summary
      Contains the expected summary reported by the CFR API. Can be omitted when no summary is produced.
    • _.expected.exceptions
      Contains the expected exceptions reported by the CFR API. Can be omitted when no exception is reported.

Decompilation note comments

The expected Java output files support comments representing decompilation notes. They are ignored during comparison with the actual Java output and can for example be used to indicate incorrect or improvable CFR output. There are two kinds of decompilation notes:

  • Line: Start with //# (optionally prefixed with whitespace)
  • Inline: /*# ... #*/

Line decompilation notes should be used sparingly, especially in large files, because they shift line numbers for the diff files (due to being removed during comparison), which can be confusing.

Example:

//# Line decompilation note
public class MyClass {
    public static void main(String[] stringArray/*# Inline decompilation note #*/) {
        ...
    }
}

Updating / creating expected data

When adding a lot of new classes or JAR files for the decompilation tests or when a change to CFR affects the output for a lot of classes or JAR files, manually creating or updating the expected output can be rather cumbersome. For these cases the following system properties exist which help with this. They can be set with -D<system-property> when running tests. However, when these system properties are set, the respective tests will still fail (but the expected data is updated) to prevent accidentally using them for regular test execution.

  • cfr.decompilation-test.create-expected
    Generates all missing expected test data based on the current CFR output.
  • cfr.decompilation-test.update-expected
    Updates the expected test data to match the actual data produced by CFR. Note that this does not work for expected Java output using decompilation notes because those comments would get lost. The affected tests have to be updated manually.

cfr's People

Contributors

chenggwang avatar col-e avatar coolmineman avatar cubxity avatar hengyunabc avatar ildar-shaimordanov avatar leibnitz27 avatar liach avatar marcono1234 avatar nbauma109 avatar passedbylove avatar skyrising avatar teamworkguy2 avatar x4e avatar yanisbft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cfr's Issues

Invalid decompilation of Scala exception handling

https://ci.appveyor.com/api/buildjobs/gtyiueue5u7joyiv/artifacts/compiler%2Fjvm%2Ftarget%2Fwindows%2Fkaitai-struct-compiler.msi (unpack it for example with lessmsi, contains a bunch of jars inside)

io\kaitai\struct\formats\JavaClassSpecs$.java

Original:

https://github.com/kaitai-io/kaitai_struct_compiler/blob/564aa4b5616a16a6c1598f622655b371b4393ddf/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala#L55L79

cfr:

public Option<ClassSpec> cached(List<String> path, Option<String> inFile, Map<String, ClassSpec> cacheMap, String name, Function1<String, ClassSpec> importOp) {
	Option option;
	Option option2 = cacheMap.get(name);
	if (option2 instanceof Some) {
		Log$.MODULE$.importOps().info((Function0<String> & java.io.Serializable & Serializable)() -> new StringContext(Predef$.MODULE$.wrapRefArray((Object[])new String[] {".... cached"})).s(Nil$.MODULE$));
		option = None$.MODULE$;
	} else if (None$.MODULE$.equals(option2)) {
		try {
		} catch (Throwable err) {
			throw new ErrorInInput(err, path, inFile);
		}
		{
			ClassSpec spec = importOp.apply(name);
			cacheMap.update(name, spec);
			option = new Some<ClassSpec>(spec);
		}
	} else {
		throw new MatchError(option2);
	}
	return option;
}

krakatau:

public scala.Option cached(scala.collection.immutable.List a, scala.Option a0, scala.collection.mutable.Map a1, String s, scala.Function1 a2) {
	scala.Option a3 = null;
	scala.Option a4 = a1.get((Object)s);
	boolean b = a4 instanceof scala.Some;
	label1: {
		Throwable a5 = null;
		label0: if (b) {
			io.kaitai.struct.Log$.MODULE$.importOps().info(/*invokedynamic*/null);
			a3 = scala.None$.MODULE$;
			break label1;
		} else {
			scala.Some a6 = null;
			if (!((Object)scala.None$.MODULE$).equals((Object)a4)) {
				throw new scala.MatchError((Object)a4);
			}
			try {
				io.kaitai.struct.format.ClassSpec a7 = (io.kaitai.struct.format.ClassSpec)a2.apply((Object)s);
				a1.update((Object)s, (Object)a7);
				a6 = new scala.Some((Object)a7);
			} catch(Throwable a8) {
				a5 = a8;
				break label0;
			}
			a3 = a6;
			break label1;
		}
		throw new io.kaitai.struct.precompile.ErrorInInput(a5, a, a0);
	}
	return a3;
}

Local variable order is inconsistent

Description

It appears CFR is not writing local variable declarations in a consistent order. When decompiling the same jar file multiple times it is very likely that for some methods local variable declarations moved to different lines, sometimes also causing their names to change (if CFR is choosing the names) since their declaration order changed.

This is quite irritating when decompiling different versions of a jar and seeing changes where there aren't actually any.

Potential cause

It appears the following line is the cause for this:

Map<ScopeKey, List<ScopeDefinition>> definitionsByType = Functional.groupToMapBy(discoveredCreations, new UnaryFunction<ScopeDefinition, ScopeKey>() {

It creates a HashMap which is not using the insertion order when iterating over its entries.
However, I am not familiar enough with the project to be completely sure.

Thank you so much !!!!!

Thank you so much for finally releasing the source to CFR !!!
I know i am a bit late to the party but this is amazing news anyway.

CFR is an excellent piece of software and very important for software freedom.

THANK YOU !!!

Force structuring fail using trap fall-through (v145)

We can force CFR into being unable to structure a try-catch block by falling through a trap as so

.version 49 0
.class public Test
.super java/lang/Object

.method public static main : ([Ljava/lang/String;)V 
    .code stack 10 locals 5
        .catch java/lang/RuntimeException from L0 to L1 using L2
L0:
        aconst_null
L1:
        getstatic java/lang/System out Ljava/io/PrintStream;
        ldc 'o hi'
        invokevirtual java/io/PrintStream println (Ljava/lang/String;)V
        pop
        goto L3
L2:
        pop
        getstatic java/lang/System out Ljava/io/PrintStream;
        ldc 'this is not supposed to be run'
        invokevirtual java/io/PrintStream println (Ljava/lang/String;)V
L3:
        return
    .end code
.end method 
.end class 

Output:

public class Test {
    /*
     * Unable to fully structure code
     * Enabled aggressive block sorting
     * Enabled unnecessary exception pruning
     * Enabled aggressive exception aggregation
     * Lifted jumps to return sites
     */
    public static void main(String[] var0) {
        ** try [egrp 0[TRYBLOCK] [0 : 0->1)] { 
lbl2: // 1 sources:
        System.out.println("o hi");
        return;
lbl5: // 1 sources:
        catch (RuntimeException v0) {
            System.out.println("this is not supposed to be run");
        }
    }
}

Test.zip

Edit: Something I didn't notice while I was writing this up - CFR seemingly is merging stuff outside of the trap into the trap range.

Decompilation failed

Hello,

I have the following issue:

Original Java sources

    public void traverseJFIF(final ByteSource byteSource, final Visitor visitor)
            throws ImageReadException,
            IOException {
        InputStream is = null;
        boolean canThrow = false;
        try {
            is = byteSource.getInputStream();

            readAndVerifyBytes(is, JpegConstants.SOI,
                    "Not a Valid JPEG File: doesn't begin with 0xffd8");

            int markerCount;
            for (markerCount = 0; true; markerCount++) {
                final byte[] markerBytes = new byte[2];
                do {
                    markerBytes[0] = markerBytes[1];
                    markerBytes[1] = readByte("marker", is,
                            "Could not read marker");
                } while ((0xff & markerBytes[0]) != 0xff
                        || (0xff & markerBytes[1]) == 0xff);
                final int marker = ((0xff & markerBytes[0]) << 8)
                        | (0xff & markerBytes[1]);

                if (marker == JpegConstants.EOI_MARKER || marker == JpegConstants.SOS_MARKER) {
                    if (!visitor.beginSOS()) {
                        canThrow = true;
                        return;
                    }

                    final byte[] imageData = getStreamBytes(is);
                    visitor.visitSOS(marker, markerBytes, imageData);
                    break;
                }

                final byte[] segmentLengthBytes = readBytes("segmentLengthBytes", is, 2, "segmentLengthBytes");
                final int segmentLength = ByteConversions.toUInt16(segmentLengthBytes, getByteOrder());

                final byte[] segmentData = readBytes("Segment Data",
                        is, segmentLength - 2,
                        "Invalid Segment: insufficient data");

                if (!visitor.visitSegment(marker, markerBytes, segmentLength, segmentLengthBytes, segmentData)) {
                    canThrow = true;
                    return;
                }
            }
            
            Debug.debug(Integer.toString(markerCount) + " markers");
            canThrow = true;
        } finally {
            IoUtils.closeQuietly(canThrow, is);
        }
    }

when compiled with javac, failed to decompile (with CFR 0.144)

    public void traverseJFIF(ByteSource byteSource, Visitor visitor) throws ImageReadException, IOException {
        // This method has failed to decompile.  When submitting a bug report, please provide this stack trace, and (if you hold appropriate legal rights) the relevant class file.
        // org.benf.cfr.reader.util.ConfusedCFRException: Tried to end blocks [4[UNCONDITIONALDOLOOP]], but top level block is 2[TRYBLOCK]
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.processEndingBlocks(Op04StructuredStatement.java:427)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.buildNestedBlocks(Op04StructuredStatement.java:479)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op03SimpleStatement.createInitialStructuredBlock(Op03SimpleStatement.java:607)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:696)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:184)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:129)
        // org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:96)
        // org.benf.cfr.reader.entities.Method.analyse(Method.java:397)
        // org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:890)
        // org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:792)
        // org.benf.cfr.reader.Driver.doClass(Driver.java:52)
        // org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:65)
        // org.benf.cfr.reader.Main.main(Main.java:48)
        throw new IllegalStateException("Decompilation failed");
    }

Bytecode is available here:
JpegUtils$1
JpegUtils$Visitor
JpegUtils

Original sources are from Apache commons-imaging.

CFR adds not needed imports

CFR version

0.149-SNAPSHOT (commit bfb754c)

Description

CFR adds not needed imports when the return type of a method is not referenced or for lambdas.

Example code

import java.util.regex.Pattern;

public class LambdaImportTest {
    public void test() {
        /*
         * Unnecessarily imports:
         * - java.util.regex.Matcher
         * - java.util.function.Function;
         * - java.util.regex.MatchResult
         */
        Pattern.compile("test").matcher("test").replaceFirst(result -> "");
    }
}

Out of memory

CFR version: cfr-0.145.jar

Failed call stack :
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:704)
at java.util.HashMap.putVal(HashMap.java:629)
at java.util.HashMap.put(HashMap.java:612)
at org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs.mapSSASlots(Op02WithProcessedDataAndRefs.java:2009)
at org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs.discoverStorageLiveness(Op02WithProcessedDataAndRefs.java:1996)
at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:334)
at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:184)
at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:122)
at org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:96)
at org.benf.cfr.reader.entities.Method.analyse(Method.java:397)
at org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:906)
at org.benf.cfr.reader.entities.ClassFile.analyseInnerClassesPass1(ClassFile.java:778)
at org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:886)
at org.benf.cfr.reader.entities.ClassFile.analyseInnerClassesPass1(ClassFile.java:778)
at org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:886)
at org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:797)
at org.benf.cfr.reader.Driver.doJarVersionTypes(Driver.java:225)
at org.benf.cfr.reader.Driver.doJar(Driver.java:109)
at org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:65)
at org.benf.cfr.reader.Main.main(Main.java:48)

Incorrect decompilation for anonymous class with parameter

Hello,
I might have found a bug where cfr-0.148 decompiles a synthetic field in an anonymous class a bit too literally:

Original java code (from junit4)

    public static Request runner(Runner runner) {
        return new Request(){
            @Overrude
            public Runner getRunner() {
                return runner;
            }
        };
    }

is decompiled (by cfr-0.148) as

    public static Request runner(Runner runner) {
        return new Request(){
            public Runner getRunner() {
                return Runner.this;
            }
        };
    }

When compiling the original sources with javac, the parameter runner is put in a synthetic field in the anonymous class.

Disassembled bytecode of the anonymous class:

  private final org.junit.runner.Runner val$runner;
    descriptor: Lorg/junit/runner/Runner;
    flags: ACC_PRIVATE, ACC_FINAL, ACC_SYNTHETIC

  org.junit.runner.Request$1(org.junit.runner.Runner);
    descriptor: (Lorg/junit/runner/Runner;)V
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #10                 // Field val$runner:Lorg/junit/runner/Runner;
         5: aload_0
         6: invokespecial #12                 // Method org/junit/runner/Request."<init>":()V
         9: return
      LineNumberTable:
        line 1: 0
        line 108: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lorg/junit/runner/Request$1;

  public org.junit.runner.Runner getRunner();
    descriptor: ()Lorg/junit/runner/Runner;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #10                 // Field val$runner:Lorg/junit/runner/Runner;
         4: areturn
      LineNumberTable:
        line 111: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/junit/runner/Request$1;

Bytecode beeing decompiled:
Request.zip

Support for custom output

I am currently working on a RE tool that would like to use CFR as a decompilation backend. I would like support for custom output for writing type references, method references, etc. This would be useful for making hyperlinks in the frontend, etc.

An example could be found here: ITextOutput.java

A few decompiled oddities

Consider the following class:

class A {
	public static int a() {
		int a = 4;
		a *= 2;
		a *= 2;
		a *= 2;
		if(a < 7) return 1;
		else return 0;
	}

	public static void b() {
		int[] a = new int[] {4};
		a[0] *= 2;
		a[0] *= 2;
		a[0] *= 2;
	}
}

Compiled with javac -g (javac version 10.0.2), the class is decompiled with cfr 0.146 into:

class A {
    A() {
    }

    public static int a() {
        int a = 4;
        a *= 2;
        a *= 2;
        return (a *= 2) < 7;
    }

    public static void b() {
        int[] a;
        int[] arrn = a = new int[]{4};
        arrn[0] = arrn[0] * 2;
        int[] arrn2 = a;
        arrn2[0] = arrn2[0] * 2;
        int[] arrn3 = a;
        arrn3[0] = arrn3[0] * 2;
    }
}

In a, the last assignment is for some reason used as an expression. Not a big deal in this case, but I've seen other cases with quite large assignments being stuffed into already big expressions. It does not only happen with compound assignments, but seems to be more common.

Also in a, the decompiled function returns a boolean, not an int.

I don't think I need to point out what's wrong with b.

(Also Github's code highlighter doesn't seem to like the decompiled code for whatever reason.)

ConfusedCFRException

Well, cfr got stuck on this method. Sorry, I can't provide the class file.

EDIT: this is cfr 0.148.

    /*
     * Exception decompiling
     */
    @Override
    public ResourceObject getObject(String objectType, String identity, Map<String, Object> options) throws SchemaNotDefinedException, ObjectNotFoundException, ConnectionFailedException, ConnectorException {
        // This method has failed to decompile.  When submitting a bug report, please provide this stack trace, and (if you hold appropriate legal rights) the relevant class file.
        // org.benf.cfr.reader.util.ConfusedCFRException: Tried to end blocks [1[TRYBLOCK]], but top level block is 5[TRYBLOCK]
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.processEndingBlocks(Op04StructuredStatement.java:427)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.buildNestedBlocks(Op04StructuredStatement.java:479)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op03SimpleStatement.createInitialStructuredBlock(Op03SimpleStatement.java:619)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:750)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:238)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:183)
        // org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:96)
        // org.benf.cfr.reader.entities.Method.analyse(Method.java:397)
        // org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:964)
        // org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:855)
        // org.benf.cfr.reader.Driver.doJarVersionTypes(Driver.java:242)
        // org.benf.cfr.reader.Driver.doJar(Driver.java:125)
        // org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:65)
        // org.benf.cfr.reader.Main.main(Main.java:48)
        throw new IllegalStateException("Decompilation failed");
    }

Unable to use CFR

โžœ java -jar ~/Downloads/cfr-0.148.jar <jar file to be decompiled>
no main manifest attribute, in /Users/<username>/Downloads/cfr-0.148.jar
โžœ

Simple usage guide in README or project documentation would help.

CFR incorrectly uses `true` for binary operators on ints

CFR version

0.149-SNAPSHOT (commit bfb754c)

Description

CFR incorrectly uses true for binary operators on ints (and possibly longs as well) in some cases, e.g.:

int i = 0;
i |= true;

Example code

public class CompoundAssignmentTest {
    public int test() {
        int i = 0;
        int h = 1;
        i = h & 1;
        
        return i;
    }
    
    public int test2() {
        int i = 0;
        i |= 1;
        
        return i;
    }
}

Decompiling with --obfuscationpath uses obfuscated names for method references

CFR version

0.147

Description

When decompiling with the newly introduced --obfuscationpath option, method names of method references (i.e. MyClass::consumeInt) are not deobfuscated.

package mypackage;

import java.util.function.Consumer;

public class MyClass {
    public static void consume(int n) {
    }

    public static void main(String[] arrstring) {
        // BUG: Uses obfuscated method name
        Consumer<Integer> consumer = MyClass::a;
        consumer = n -> MyClass.consume((int)n);
    }
}

Reproduction steps

  1. Download

  2. Run CFR

    java -jar ./cfr-0.147.jar ./method-ref-obf.jar --obfuscationpath mappings.txt
    

Decompiling with --obfuscationpath writes nested classes as separate source files

CFR version

0.147

Description

When decompiling with the newly introduced --obfuscationpath option, nested classes are created as separate source files producing invalid Java code.

import mypackage.MyClass;

// Constains a period (`.`), not a valid class name
public class MyClass.Nested {
}

Reproduction steps

  1. Download

  2. Run CFR

    java -jar ./cfr-0.147.jar nested-obf.jar --obfuscationpath mappings.txt
    

Decompiling (some) nested try-with-resources statements fails

CFR version

0.149-SNAPSHOT (commit bfb754c)

Description

It appears CFR is not able to create try-with-resources statements and under certain circumstances also fails decompiling them completely:

/*
 * This method has failed to decompile.  When submitting a bug report, please provide this stack trace, and (if you hold appropriate legal rights) the relevant class file.
 * org.benf.cfr.reader.util.ConfusedCFRException: Started 2 blocks at once
 * org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.getStartingBlocks(Op04StructuredStatement.java:396)
 * org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.buildNestedBlocks(Op04StructuredStatement.java:471)
 * org.benf.cfr.reader.bytecode.analysis.opgraph.Op03SimpleStatement.createInitialStructuredBlock(Op03SimpleStatement.java:619)
 * org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:750)
 * org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:238)
 * org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:183)
 * org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:96)
 * org.benf.cfr.reader.entities.Method.analyse(Method.java:397)
 * org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:964)
 * org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:855)
 * org.benf.cfr.reader.Driver.doClass(Driver.java:71)
 * org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:67)
 * org.benf.cfr.reader.Main.main(Main.java:49)
 */

Example code

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class TryWithResourcesTest {
    public int test() {
        try (InputStream is = getClass().getResourceAsStream("file")) {
            try (InputStreamReader r = new InputStreamReader(is)) {
                return doSomething();
            }
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    private int doSomething() {
        return 1;
    }
}

Decompiling with --obfuscationpath does not create local variable names using deobfuscated name

CFR version

0.147

Description

When decompiling with the newly introduced --obfuscationpath option, local variables names are not generated based on the deobfuscated name. E.g. if a class MyClass is obfuscated to a, then a local variable of type MyClass will be named a1 instead of myClass.

public class MyClass {
    public void doSomething(OtherClass b2, String string) {
        OtherClass b3 = new OtherClass();
        if (b2 != null) {
            b3 = b2;
        }
        b3.toString();
    }
}

Reproduction steps

  1. Download
  2. Run CFR
    java -jar ./cfr-0.147.jar var-name-obf.jar --obfuscationpath mappings.txt
    

Invalid cast insertion

Hello,

I might have found the following bug:

Original java sources:

	private static final ConcurrentMap<String, List<Locale>> cLanguagesByCountry =
        new ConcurrentHashMap<>();
	
	[...]
    public static List<Locale> languagesByCountry(final String countryCode) {
        if (countryCode == null) {
            return Collections.emptyList();
        }
        List<Locale> langs = cLanguagesByCountry.get(countryCode);
        if (langs == null) {
            langs = new ArrayList<>();
            final List<Locale> locales = availableLocaleList();
            for (final Locale locale : locales) {
                if (countryCode.equals(locale.getCountry()) &&
                    locale.getVariant().isEmpty()) {
                    langs.add(locale);
                }
            }
            langs = Collections.unmodifiableList(langs);
            cLanguagesByCountry.putIfAbsent(countryCode, langs);
            langs = cLanguagesByCountry.get(countryCode);
        }
        return langs;
    }

when compiled with javac is decompiled into (by CFR-0.144)

    public static List<Locale> languagesByCountry(String countryCode) {
        if (countryCode == null) {
            return Collections.emptyList();
        }
        List<Locale> langs = (ArrayList)cLanguagesByCountry.get(countryCode);
        if (langs == null) {
            langs = new ArrayList();
            List<Locale> locales = LocaleUtils.availableLocaleList();
            for (Locale locale : locales) {
                if (!countryCode.equals(locale.getCountry()) || !locale.getVariant().isEmpty()) continue;
                langs.add(locale);
            }
            langs = Collections.unmodifiableList(langs);
            cLanguagesByCountry.putIfAbsent(countryCode, langs);
            langs = (List)cLanguagesByCountry.get(countryCode);
        }
        return langs;
    }

When cLanguagesByCountry.get(countryCode) is not an ArrayList, the cast (ArrayList)cLanguagesByCountry.get(countryCode) fails.

Bytecode is available here (Original sources are from Apache commons-lang3 and compiled with javac.)

The bytecode lead to the exact same error for another method public static List<Locale> countriesByLanguage(final String languageCode).

Decompiler outputs unprintable symbol 0x7F

In this string in constant pool there is one byte that should be also unicoded: 0x7F.
String is:
7C2F7F417608590454125F41690448175F131A095B121A0F55151A035F045441590E540753064F135F051AC08049415B4172356E316941490448175F131A49530F1A0D55025B0D1702550F5C085D1448C0804E08550F14185B0C564814

Output is:

"|/๏ฟฝAv\bY\u0004T\u0012_Ai\u0004H\u0017_\u0013\u001a\t[\u0012\u001a\u000fU\u0015\u001a\u0003_\u0004TAY\u000eT\u0007S\u0006O\u0013_\u0005\u001a\u0000IA[Ar5n1iAI\u0004H\u0017_\u0013\u001aIS\u000f\u001a\rU\u0002[\r\u0017\u0002U\u000f\\\b]\u0014H\u0000N\bU\u000f\u0014\u0018[\fVH\u0014"

PLEASE DO NOT UPLOAD COPYRIGHT MATERIALS.

Hi. If you have an issue, please don't upload copyright materials. I will have to delete your issue if you do that.

If you are able to build a test case to which you have copyright, that's awesome, and it can be included in the test suite.

THANKS!

Incorrect try-catch representation (v145)

Using this (probably unlikely) example:

.version 49 0 
.class public Test 
.super java/lang/Object 

.method public static main : ([Ljava/lang/String;)V 
    .code stack 8 locals 1 
L0:     goto L12 
L3:     getstatic Field java/lang/System out Ljava/io/PrintStream; 
L6:     ldc 'Hello #2' 
L8:     invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
L11:    return 
        .catch FakeException from L12 to L22 using L25 
L12:    aconst_null 
L13:    getstatic Field java/lang/System out Ljava/io/PrintStream; 
L16:    ldc 'Hello #1' 
L18:    invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
L21:    pop 
L22:    goto L27 
L25:    pop 
L26:    return 
L27:    goto L3 
L30:    
    .end code 
.end method 
.end class 

you can force CFR to put the decompiled result of L3 to L8 inside of the catch block resulting in a possible behavior change.

Expected:

public static void main(String[] arrstring) {
    try {
        System.out.println("Hello #1");
        return;
    }
    catch (FakeException fakeException) {
        return;
    }
    System.out.println("Hello #2");
}

Actual:

public static void main(String[] arrstring) {
    try {
        System.out.println("Hello #1");
        System.out.println("Hello #2");
        return;
    }
    catch (FakeException fakeException) {
        return;
    }
}

Access from multiple nested inner classes is incorrect

CFR version

0.149-SNAPSHOT (commit 06539a4)

Description

When decompiling classes containing multiple nested inner classes which access the field of the enclosing class, the code generated by CFR is invalid:

A.this.NestedInnerClasses.this.str

Example code

public class NestedInnerClasses {
    private String str;
    
    class A {
        class B {
            String doSomething() {
                return str;
            }
        }
    }
}

Recover full method path

It there any way to specify full method path decompilation?

I mean, for now it shows only myClass.myMethod() call, but it will be great also to show it like this:
com.mycom.myClass.myMethod().

Access to shadowed super members is incorrectly decompiled to access to `this`

CFR version

0.149-SNAPSHOT (commit 06539a4)

Description

When decompiling classes which shadow members of their parent and then access the parent member using super.X, it is incorrectly decompiled as this.X.

Example code

public class SuperMemberAccess {
    private String a;
    
    private String test() {
        return null;
    }
    
    public static class Child extends SuperMemberAccess {
        private int a;
        
        private int test() {
            return 1;
        }
        
        String doSomething() {
            String s = super.test();
            return super.a;
        }
    }
}

Decompiling with --obfuscationpath creates incorrect package declaration and import

CFR version

0.147

The bug

When decompiling with the newly introduced --obfuscationpath option, the package declaration in the Java source uses the obfuscated package name, and additionally the current class is added as import.

For example when decompiling a.a (deobf: test.Main) the created source is:

package a;

import test.Main;

class Main {
    ...
}

instead of

package test;

class Main {
    ...
}

Reproduction steps

  1. Download
  2. Run CFR
    java -jar ./cfr-0.147.jar cfr-mapping-test-obf.jar --obfuscationpath mappings.txt
    

java.lang.ClassCastException: org.benf.cfr.reader.entities.attributes.AttributeUnknown cannot be cast to org.benf.cfr.reader.entities.attributes.AttributeRuntimeInvisibleAnnotations

Stacktrace:

java.lang.ClassCastException: org.benf.cfr.reader.entities.attributes.AttributeUnknown cannot be cast to org.benf.cfr.reader.entities.attributes.AttributeRuntimeInvisibleAnnotations
	at org.benf.cfr.reader.entities.classfilehelpers.AbstractClassFileDumper.dumpAnnotations(AbstractClassFileDumper.java:169)
	at org.benf.cfr.reader.entities.classfilehelpers.ClassFileDumperNormal.dump(ClassFileDumperNormal.java:66)
	at org.benf.cfr.reader.entities.ClassFile.dump(ClassFile.java:1070)
	at org.benf.cfr.reader.Driver.doClass(Driver.java:83)
	at org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:67)

Local variable names in lambdas can collide when using --usenametable false

CFR version

0.149-SNAPSHOT (commit 06539a4)

Description

When decompiling with --usenametable false, it is possible that the local variable name inside a lamba is the same as the name of a variable in the enclosing method.

Example code

public class LambdaNameCollision {
    void test() {
        String string = "";
        
        Runnable r = () -> {
            // Becomes `string` as well
            String s = System.getProperty("key");
        };
    }
}

Hide Eclipse enum switch table when --eclipse option is set

CFR version

0.147

Description

Eclipse compiles switch statements on enum values differently than javac (https://www.benf.org/other/cfr/switch-on-enum.html). Instead of adding an inner class, it adds the switch array ($SWITCH_TABLE$mypackage$MyEnum) as field and adds a method with the same name which lazily initializes the array and returns it.

Currently CFR outputs this even when using --eclipse:

public enum MyEnum {
    FIRST;

    private static /* synthetic */ int[] $SWITCH_TABLE$mypackage$MyEnum;

    public int get() {
        switch (MyEnum.$SWITCH_TABLE$mypackage$MyEnum()[this.ordinal()]) {
            case 1: {
                return 2;
            }
        }
        return -1;
    }

    static /* synthetic */ int[] $SWITCH_TABLE$mypackage$MyEnum() {
        if ($SWITCH_TABLE$mypackage$MyEnum != null) {
            int[] arrn;
            return arrn;
        }
        int[] arrn = new int[MyEnum.values().length];
        try {
            arrn[MyEnum.FIRST.ordinal()] = 1;
        }
        catch (NoSuchFieldError noSuchFieldError) {}
        $SWITCH_TABLE$mypackage$MyEnum = arrn;
        return arrn;
    }
}

Reproduction steps

  1. Compile the following code using Eclipse
    package mypackage;
    
    public enum MyEnum {
        FIRST;
        
        public int get() {
            switch (this) {
                case FIRST:
                    return 2;
                default:
                    return -1;
            }
        }
    }
    
  2. Use CFR with the --eclipse option (enabled by default)

Alternatively, decompile the following .jar (remove .zip from its name, this was only added to make GitHub happy):
enum-switch.jar.zip

Unnecessary casts

CFR adds unnecessary casts on method invocation
return String.format((String)"[Region FileName=%s, Begin=(%d, %d), End=(%d, %d)]", (Object[])new Object[]{this._fileName, this._beginLine, this._beginColumn, this._endLine, this._endColumn});

NonStaticLifter can change order of initialisers with side effects

NonStaticLifter lifts initialisers even if the fields are declared in a different order to the order of the initialisation code in the constructor. This changes the semantics of the program if the initialisers have side effects.

Here's a simple test case:

public class Test {
    private static int printA() {
        System.out.println("A");
        return 0;
    }

    private static int printB() {
        System.out.println("B");
        return 0;
    }

    private int a;
    private int b;

    private Test() {
        b = printB();
        a = printA();
    }

    public static void main(String[] args) {
        new Test();
    }
}

It prints B followed by A:

$ java Test
B
A
$ 

This is how cfr decompiles the code - note that printA/B are now called in the opposite order:

/*
 * Decompiled with CFR 0.145.
 */
import java.io.PrintStream;

public class Test {
    private int a = Test.printA();
    private int b = Test.printB();

    private static int printA() {
        System.out.println("A");
        return 0;
    }

    private static int printB() {
        System.out.println("B");
        return 0;
    }

    private Test() {
    }

    public static void main(String[] arrstring) {
        new Test();
    }
}

It incorrectly prints A followed by B:

$ java Test
A
B
$ 

Missing blank line in front of nested classes

CFR version

0.149-SNAPSHOT (commit bfb754c)

Description

No blank line is inserted if there is already a member (field, method or another nested class) in the class and a nested class is written.

This might be related to #46 which adds one (but only at most one) blank line at the end of the enclosing class.

Example code

To compile and decompile:

public class NestedClassTest {
    private int otherField = 1;
    private void doSomething() { }
    // Empty line will be missing here
    private static class Nested { }
    // Empty line will be missing here
    private static class Nested2 { }
}

The decompiled output is currently:

public class NestedClassTest {
    private int otherField = 1;

    private void doSomething() {
    }
    private static class Nested {
        private Nested() {
        }
    }

}

Decompiling with --obfuscationpath uses obfuscated enum names for switch

CFR version

0.147

Description

When decompiling with the newly introduced --obfuscationpath option, enum value names used in switch statements are not deobfuscated.

public enum MyEnum {
    FIRST;


    /*
     * Exception decompiling
     */
    private MyEnum() {
        // See https://github.com/leibnitz27/cfr/issues/29
    }

    public int get() {
        switch (this) {
            // BUG: Not deobfuscated, should be `FIRST`
            case a: {
                return 2;
            }
        }
        return -1;
    }

    public static MyEnum getFirst() {
        // Correctly deobfuscated
        return FIRST;
    }
}

Reproduction steps

  1. Download
  2. Run CFR
    java -jar ./cfr-0.147.jar enum-switch-obf.jar --obfuscationpath mappings.txt
    

Decompiling obfuscated enum fails

CFR version

0.147

Description

Decompiling an enum obfuscated using ProGuard fails:

public enum a {
    a;


    /*
     * Exception decompiling
     */
    private a() {
        // This method has failed to decompile.  When submitting a bug report, please provide this stack trace, and (if you hold appropriate legal rights) the relevant class file.
        // java.lang.IllegalStateException
        // org.benf.cfr.reader.bytecode.analysis.variables.VariableFactory.localVariable(VariableFactory.java:57)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs.mkRetrieve(Op02WithProcessedDataAndRefs.java:936)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs.createStatement(Op02WithProcessedDataAndRefs.java:984)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs.access$100(Op02WithProcessedDataAndRefs.java:56)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs$11.call(Op02WithProcessedDataAndRefs.java:2065)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs$11.call(Op02WithProcessedDataAndRefs.java:2062)
        // org.benf.cfr.reader.util.graph.AbstractGraphVisitorFI.process(AbstractGraphVisitorFI.java:60)
        // org.benf.cfr.reader.bytecode.analysis.opgraph.Op02WithProcessedDataAndRefs.convertToOp03List(Op02WithProcessedDataAndRefs.java:2074)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:396)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:238)
        // org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:183)
        // org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:96)
        // org.benf.cfr.reader.entities.Method.analyse(Method.java:397)
        // org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:922)
        // org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:813)
        // org.benf.cfr.reader.Driver.doJarVersionTypes(Driver.java:239)
        // org.benf.cfr.reader.Driver.doJar(Driver.java:120)
        // org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:65)
        // org.benf.cfr.reader.Main.main(Main.java:48)
        throw new IllegalStateException("Decompilation failed");
    }
}

Source code:

package mypackage;

public enum MyEnum {
    FIRST;
}

Reproduction steps

  1. Download enum-obf.jar.zip (remove .zip from the name, this was only added to make GitHub happy)
  2. Decompile
    java -jar ./cfr-0.147.jar enum-obf.jar
    

Adding custom deobfuscators

I understand that your decompiler is not designed to deal with obfuscated strings, but for others (or imagine situation that you obfuscated your code and have lost it) it will be great to have an ability to do some post-things with strings. For example:
Deobfuscation method which accepts encrypted buffer, and decodes it

That's how I see it:

  • Some MySuperDecoderClass extends DecoderClass which has decode method that accepts String fullMethodPath, String buffer and returns decoded string.
  • Depending on method's name I would use a different decoding code, or just simply return an original string.
  • You just need to collect such implementations and callback them.

That would help with obfuscated strings.

Decompilation failed

```
//This method has failed to decompile. When submitting a bug report, please provide this stack trace, and (if you hold appropriate legal rights) the relevant class file.
// org.benf.cfr.reader.util.ConfusedCFRException: Tried to end blocks [5[TRYBLOCK]], but top level block is 17[CATCHBLOCK]
// org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.processEndingBlocks(Op04StructuredStatement.java:427)
// org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.buildNestedBlocks(Op04StructuredStatement.java:479)
// org.benf.cfr.reader.bytecode.analysis.opgraph.Op03SimpleStatement.createInitialStructuredBlock(Op03SimpleStatement.java:619)
// org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:750)
// org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:238)
// org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:183)
// org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:96)
// org.benf.cfr.reader.entities.Method.analyse(Method.java:397)
// org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:917)
// org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:813)
// org.benf.cfr.reader.Driver.doClass(Driver.java:64)
// org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:67)
// org.benf.cfr.reader.Main.main(Main.java:48)
throw new IllegalStateException("Decompilation failed");

the same to #7

Unchecked cast is not emitted

CFR version

0.149-SNAPSHOT (commit 06539a4)

Description

It appears CFR is not emitting unchecked casts (in some cases).

Example code

import java.util.List;

public class UncheckedCast<T> {
    void use(T t) { }
    
    void doSomething(List<T> l) {
        use((T) l); // Unchecked cast
    }
}

Ternary ? 1 : 0 is incorrectly replaced with boolean

CFR version

0.149-SNAPSHOT (commit 06539a4)

Description

Similar to #51
When a ternary returns either 1 or 0, it is omitted when a numeric value is expected:
(float) (b ? 1 : 0) becomes (float) b

Example code

public class TernaryBoolean {
    float test(boolean b) {
        return b ? 1 : 0;
    }
}

Discarded (popped) expressions can cause "not a statement" if not consumed into ignored locals.

FILE (no copyright materials): o_test.zip

CFR:

public static String fib(String a) {
    int n = a.length();
    int n2 = n - 1;
    char[] arrc = new char[n];
    int n3 = (3 ^ 5) << 4 ^ 1;
    (3 ^ 5) << 3 ^ (3 ^ 5);
    int n4 = n2;
    int n5 = (2 ^ 5) << 3 ^ 2;
    while (n4 >= 0) {
        int n6 = n2--;
        arrc[n6] = (char)(a.charAt(n6) ^ n5);
        if (n2 < 0) break;
        int n7 = n2--;
        arrc[n7] = (char)(a.charAt(n7) ^ n3);
        n4 = n2;
    }
    return new String(arrc);
}

Fernflower:

public static String fib(String a) {
    int var10000 = (2 ^ 5) << 3 ^ 2;
    int var10001 = (3 ^ 5) << 3 ^ 3 ^ 5;
    int var10002 = (3 ^ 5) << 4 ^ 1;
    int var10003 = a.length();
    char[] var10004 = new char[var10003];
    boolean var10006 = true;
    int var5 = var10003 - 1;
    var10003 = var10002;
    int var3;
    var10002 = var3 = var5;
    char[] var1 = var10004;
    int var4 = var10003;
    var10001 = var10000;
    var10000 = var10002;

    for(int var2 = var10001; var10000 >= 0; var10000 = var3) {
        var10001 = var3;
        char var6 = a.charAt(var3);
        --var3;
        var1[var10001] = (char)(var6 ^ var2);
        if (var3 < 0) {
            break;
        }

        var10002 = var3--;
        var1[var10002] = (char)(a.charAt(var10002) ^ var4);
    }

    return new String(var1);
}

Optimizations are ok, but that line with the mathematical operation looks weird.:)

Invalid code for method reference to constructor with parameters

CFR version

0.149-SNAPSHOT (commit 6d1714b)

Description

CFR produces invalid code when decompiling a method reference to a constructor with parameters. Code emitted by CFR:

(arg_0, arg_1) -> MethodRefTest.new(arg_0, arg_1)

It should be either

(arg_0, arg_1) -> new MethodRefTest(arg_0, arg_1)

or

MethodRefTest::new

Example code

public class MethodRefTest {
    public MethodRefTest(int a, String b) { }
    
    interface Factory {
        MethodRefTest create(int a, String b);
    }
    
    public static void create(Factory factory) { }
    
    public static void main(String[] args) {
        create(MethodRefTest::new);
    }
}

Fix warning on javadoc

Due to issue with j8 compatibility outlined in #21 , -html5 argument can't be used in javadoc pom stanza.

But because of this, we get an annoying warning.

Need to satisfy j9's requirement for an -html4|-html5 flag, while not breaking j8 javadoc.

Type parameter names or wildcards are used as local variable type

CFR version

0.149-SNAPSHOT (commit 06539a4)

Description

When decompiling methods which store the result of a generic method in a local variable and there is no further usage of this result, CFR emits the type parameter name or a wildcard, which is invalid code:

public static void main(String[] args) {
    ? o = GenericType.getWildcardInstance().doSomething();
    T o2 = GenericType.getInstance().doSomething();
    GenericType<T> o3 = GenericType.getInstance();
}

Also sorry for this flood of issues, I started poking at CFR a little bit. Let me know if they are too many or if you are not interested in these issues.

Example code

public interface GenericType<T> {
    T doSomething();
    
    static GenericType<?> getWildcardInstance() {
        return null;
    }
    
    static <T> GenericType<T> getInstance() {
        return null;
    }
    
    public static void main(String[] args) {
        Object o = getWildcardInstance().doSomething();
        Object o2 = getInstance().doSomething();
        Object o3 = getInstance();
    }
}

Decompiling explicit usage of MethodHandle does not add required casts

CFR version

0.149-SNAPSHOT (commit fdd0a63)

Description

The methods MethodHandle.invoke and invokeExact are signature polymorphic, i.e. it matters what type the arguments and the return value are, even if it would not matter for normal methods. However, CFR does not add the necessary casts.

Example code

import java.io.PrintStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class MethodHandleTest {
    public static void main(String[] args) throws Throwable {
        testReturnType();
        testReturnTypeNoEffect();
        testInvokeArgs();
    }
    
    private static void testReturnType() throws Throwable {
        Lookup lookup = MethodHandles.lookup();
        MethodHandle handle = lookup.findStaticGetter(System.class, "out", PrintStream.class);
        // Decompiler must emit cast here
        PrintStream ps = (PrintStream) handle.invoke();
        ps.print("Hello");
    }
    
    private static void useObj(Object obj) { }
    
    private static void testReturnTypeNoEffect() throws Throwable {
        Lookup lookup = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(String.class, String.class);
        MethodHandle handle = lookup.findStatic(System.class, "getProperty", methodType);
        // Must cast to String to describe return type, despite useObj allowing any Object
        useObj((String) handle.invokeExact("my-arg"));
    }
    
    private static void testInvokeArgs() throws Throwable {
        Lookup lookup = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(Collection.class, Collection.class);
        MethodHandle handle = lookup.findStatic(Collections.class, "unmodifiableCollection", methodType);
        List<String> list = new ArrayList<>();
        list.listIterator(); // Prevent decompiler from converting variable to Collection
        // Cast of argument and return value is required
        System.out.println((Collection<?>) handle.invokeExact((Collection<?>) list));
    }
}

Code formatting standards

CFR does have some small flaws at code formatting.

Examples that i found:

new line before imports / extends
space between ... in method parameters ("Object ... objects" instead of "Object... objects")
no new line after label definition (block0: for (...))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.