benf.org :  other :  cfr :  api.

CFR is mainly intended to be a stand alone classfile decompiler, but you can use the jar as a library.

A caveat/apology/whatever : Note that, due to CFR being very deliberately implemented to only require the Java6 runtime, there are implementations of (what have become) standard interfaces used, which might feel a bit redundant.

Don't forget - if you're embedding CFR, the license is here, and it'd be neighbourly of you to drop me a note, because it's great to know about all the cool things using it!

You can pick it up as a maven dependency, should you so wish.

Javadoc

Jump straight into the javadoc

Examples

Note - all the examples below are available here.

Basic

The most basic usage of the API is to construct a CFRDriver with no options, no source, and no sink, and ask it to analyse some classes.

This will dump output to stdout (as that's the default for CFR)...

I'll include the class and imports here, but they're obvious, so will elide in subsequent examples

import org.benf.cfr.reader.api.CfrDriver;
import java.util.Arrays;

public class Basic {
    public static void main(String ... args) {
        CfrDriver driver = new CfrDriver.Builder().build();
        driver.analyse(Arrays.asList(args));
    }
}

Override some options

CFR has lots of command line options, most of which are never expected to be used. However, you can override them if you want.

Let's say you want to see the madness that is the "real" string switch. Then you can override "decodestringswitch", et... voila!

public static void main(String ... args) {
    Map<String, String> options = new HashMap<>();
    options.put("decodestringswitch", "false");
    CfrDriver driver = new CfrDriver.Builder().withOptions(options).build();
    driver.analyse(Collections.singletonList("out/production/cfr_client/org/benf/cfr_client_example/decompile_me/StringSwitch.class"));
}

Note that the options are Not yet concretely defined in the API. You can use the same options as on the command line, or you can examine the members of OptionsImpl, however this is not guaranteed to be stable

Override where the output goes

Options are nice and all that, and we can override outputpath if we want to get the example above to write somewhere convenient, but what if we want to capture the output directly?

CFR has various types of output that it uses (which are all directed to convenient places in normal operation).

These are defined in SinkType

You can supply an OutputSinkFactory, which CFR will use to dump output.

Let's say you want to dump decompiled java to stdout, and ignore everything else

public static void main(String ... args) {
    OutputSinkFactory mySink = new OutputSinkFactory() {
        @Override
        public List<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> collection) {
            // I only understand how to sink strings, regardless of what you have to give me.
            return Collections.singletonList(SinkClass.STRING);
        }

        @Override
        public <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
            return sinkType == SinkType.JAVA ? System.out::println : ignore -> {};
        }
    };

    CfrDriver driver = new CfrDriver.Builder().withOutputSink(mySink).build();
    driver.analyse(Collections.singletonList("out/production/cfr_client/org/benf/cfr_client_example/decompile_me/SpyStuff.class"));
}

What's going on here?

SinkType

SinkType defines which sort of output CFR wants to send you. You're probably interested in JAVA. But if you're decompiling an entire JAR, you might want a PROGRESS report.

SinkClass

SinkClass defines what the type of object that will be sunk is. All sinks will always offer to sink STRINGS, however (example later), we might want more information.

Why go through this crazy rigamarole? Why not just have methods for sinking different kinds of things? Because!

Override output, with a better type

When your sinkFactory is asked what sort of sinkClasses it supports for a given sinkType, you can return an ordered list of preferences.

SinkClass.DECOMPILED will give you objects of type Decompiled (imaginative!). You can call .sinkClass() on a sink class to find what type it sinks, if you wish - it's also in the JavaDoc.

OutputSinkFactory mySink = new OutputSinkFactory() {
	@Override
	public List<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> collection) {
		System.out.println("CFR wants to sink " + sinkType + ", and I can choose:");
		collection.forEach(System.out::println);
		if (sinkType == SinkType.JAVA && collection.contains(SinkClass.DECOMPILED)) {
			// I'd like "Decompiled".  If you can't do that, I'll take STRING.
			return Arrays.asList(SinkClass.DECOMPILED, SinkClass.STRING);
		} else {
			// I only understand how to sink strings, regardless of what you have to give me.
			return Collections.singletonList(SinkClass.STRING);
		}
	}

	Consumer<SinkReturns.Decompiled> dumpDecompiled = d -> {
		System.out.println("Package [" + d.getPackageName() + "] Class [" + d.getClassName() + "]");
		System.out.println(d.getJava());
	};

	@Override
	public <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
		if (sinkType == SinkType.JAVA && sinkClass == SinkClass.DECOMPILED) {
			return x -> dumpDecompiled.accept((SinkReturns.Decompiled) x);
		}
		return ignore -> {};
	}
};

CfrDriver driver = new CfrDriver.Builder().withOutputSink(mySink).build();
driver.analyse(Collections.singletonList("out/production/cfr_client/org/benf/cfr_client_example/decompile_me/SpyStuff.class"));

This will result in:

CFR wants to sink EXCEPTION, and I can choose:
EXCEPTION_MESSAGE
STRING
CFR wants to sink PROGRESS, and I can choose:
STRING
CFR wants to sink JAVA, and I can choose:
DECOMPILED
STRING
Package [org.benf.cfr_client_example.decompile_me] Class [SpyStuff]
/*
 * Decompiled with CFR 0_135.
 */
package org.benf.cfr_client_example.decompile_me;

import java.io.PrintStream;

public class SpyStuff {
    public void greeting(int secret) {
        System.out.println(secret == 12345 ? "THE FROG CROAKS AT MIDNIGHT" : "HELLO WORLD");
    }
}

Overriding where class files come from

You can also override where class file binaries come from (ok, this is getting complicated!)

I'm not going to go into the details here - please see the javadoc

public static void main(String ... args) {

    class DataSource implements ClassFileSource {
        @Override
        public void informAnalysisRelativePathDetail(String usePath, String classFilePath) {
        }

        @Override
        public Collection<String> addJar(String jarPath) {
            throw new UnsupportedOperationException();
        }

        @Override
        public String getPossiblyRenamedPath(String s) {
            return s;
        }

        @Override
        public Pair<byte[], String> getClassFileContent(String s) throws IOException {
            if (!s.equals("Sml.class")) throw new FileNotFoundException("Can't find " + s);
            byte data[] = {
                    (byte)0xca,(byte)0xfe,(byte)0xba,(byte)0xbe,0x00,0x00,0x00,0x34,0x00,0x1f,0x0a,0x00,0x05,0x00,0x13,0x09
                    ,0x00,0x14,0x00,0x15,0x0a,0x00,0x16,0x00,0x17,0x07,0x00,0x18,0x07,0x00,0x19,0x01
                    ,0x00,0x06,0x3c,0x69,0x6e,0x69,0x74,0x3e,0x01,0x00,0x03,0x28,0x29,0x56,0x01,0x00
                    ,0x04,0x43,0x6f,0x64,0x65,0x01,0x00,0x0f,0x4c,0x69,0x6e,0x65,0x4e,0x75,0x6d,0x62
                    ,0x65,0x72,0x54,0x61,0x62,0x6c,0x65,0x01,0x00,0x12,0x4c,0x6f,0x63,0x61,0x6c,0x56
                    ,0x61,0x72,0x69,0x61,0x62,0x6c,0x65,0x54,0x61,0x62,0x6c,0x65,0x01,0x00,0x04,0x74
                    ,0x68,0x69,0x73,0x01,0x00,0x1a,0x4c,0x6f,0x72,0x67,0x2f,0x62,0x65,0x6e,0x66,0x2f
                    ,0x63,0x66,0x72,0x5f,0x65,0x78,0x61,0x6d,0x70,0x6c,0x65,0x2f,0x53,0x6d,0x6c,0x3b
                    ,0x01,0x00,0x03,0x66,0x6f,0x6f,0x01,0x00,0x04,0x28,0x49,0x29,0x56,0x01,0x00,0x01
                    ,0x78,0x01,0x00,0x01,0x49,0x01,0x00,0x0a,0x53,0x6f,0x75,0x72,0x63,0x65,0x46,0x69
                    ,0x6c,0x65,0x01,0x00,0x08,0x53,0x6d,0x6c,0x2e,0x6a,0x61,0x76,0x61,0x0c,0x00,0x06
                    ,0x00,0x07,0x07,0x00,0x1a,0x0c,0x00,0x1b,0x00,0x1c,0x07,0x00,0x1d,0x0c,0x00,0x1e
                    ,0x00,0x0e,0x01,0x00,0x18,0x6f,0x72,0x67,0x2f,0x62,0x65,0x6e,0x66,0x2f,0x63,0x66
                    ,0x72,0x5f,0x65,0x78,0x61,0x6d,0x70,0x6c,0x65,0x2f,0x53,0x6d,0x6c,0x01,0x00,0x10
                    ,0x6a,0x61,0x76,0x61,0x2f,0x6c,0x61,0x6e,0x67,0x2f,0x4f,0x62,0x6a,0x65,0x63,0x74
                    ,0x01,0x00,0x10,0x6a,0x61,0x76,0x61,0x2f,0x6c,0x61,0x6e,0x67,0x2f,0x53,0x79,0x73
                    ,0x74,0x65,0x6d,0x01,0x00,0x03,0x6f,0x75,0x74,0x01,0x00,0x15,0x4c,0x6a,0x61,0x76
                    ,0x61,0x2f,0x69,0x6f,0x2f,0x50,0x72,0x69,0x6e,0x74,0x53,0x74,0x72,0x65,0x61,0x6d
                    ,0x3b,0x01,0x00,0x13,0x6a,0x61,0x76,0x61,0x2f,0x69,0x6f,0x2f,0x50,0x72,0x69,0x6e
                    ,0x74,0x53,0x74,0x72,0x65,0x61,0x6d,0x01,0x00,0x05,0x70,0x72,0x69,0x6e,0x74,0x00
                    ,0x21,0x00,0x04,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x06,0x00
                    ,0x07,0x00,0x01,0x00,0x08,0x00,0x00,0x00,0x2f,0x00,0x01,0x00,0x01,0x00,0x00,0x00
                    ,0x05,0x2a,(byte)0xb7,0x00,0x01,(byte)0xb1,0x00,0x00,0x00,0x02,0x00,0x09,0x00,0x00,0x00,0x06
                    ,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x0a,0x00,0x00,0x00,0x0c,0x00,0x01,0x00,0x00
                    ,0x00,0x05,0x00,0x0b,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x0d,0x00,0x0e,0x00,0x01
                    ,0x00,0x08,0x00,0x00,0x00,0x42,0x00,0x03,0x00,0x02,0x00,0x00,0x00,0x0a,(byte)0xb2,0x00
                    ,0x02,0x1b,0x04,0x60,(byte)0xb6,0x00,0x03,(byte)0xb1,0x00,0x00,0x00,0x02,0x00,0x09,0x00,0x00
                    ,0x00,0x0a,0x00,0x02,0x00,0x00,0x00,0x05,0x00,0x09,0x00,0x06,0x00,0x0a,0x00,0x00
                    ,0x00,0x16,0x00,0x02,0x00,0x00,0x00,0x0a,0x00,0x0b,0x00,0x0c,0x00,0x00,0x00,0x00
                    ,0x00,0x0a,0x00,0x0f,0x00,0x10,0x00,0x01,0x00,0x01,0x00,0x11,0x00,0x00,0x00,0x02
                    ,0x00,0x12 };
            return Pair.make(data, "Sml.class");
        }
    }

    ClassFileSource source = new DataSource();
    CfrDriver driver = new CfrDriver.Builder().withClassFileSource(source).build();
    driver.analyse(Collections.singletonList("Sml.class"));
}

When we run this.... we've created a class file from nowhere!

package org.benf.cfr_example;

import java.io.PrintStream;

public class Sml {
    void foo(int x) {
        System.out.print(x + 1);
    }
}

Why is this so complicated?

I appreciate that the various SinkTypes and classes make things slightly (but not much) more complex than they could be. However, they come with a massive upside of future proofing. New output categories and types can be introduced without breaking the ABI. This is important, as I don't want people to fear dropping a new CFR binary.

Things are also made slightly more complex by implementing CFR in java6, heh. But that's on purpose. You'll note that the examples are really *not* j6.


Last updated 11/2018