Skip to content

Commit

Permalink
feat: improve API ergonomics, arbitrary class file sources
Browse files Browse the repository at this point in the history
  • Loading branch information
zlataovce committed Jun 18, 2024
1 parent 5c59ed6 commit 912dbe7
Show file tree
Hide file tree
Showing 8 changed files with 3,824 additions and 3,780 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ const { decompile } = require("./cfr.js"); // get it from the dist/ directory or

const data = fs.readFileSync("./your/package/HelloWorld.class"); // read a class file
console.log(decompile(data, {
classes: [] /* no additional classes for analysis */,
source: (name) => {
/* provide any additional classes for analysis here */

console.log(name); /* internal name, e.g. java/lang/Object */
return null; /* class not available */
},
options: {
/* see https://github.com/leibnitz27/cfr/blob/master/src/org/benf/cfr/reader/util/getopt/OptionsImpl.java#L274 */
"hidelangimports": "false", /* testing option - don't hide java.lang imports */
Expand Down
7,498 changes: 3,750 additions & 3,748 deletions dist/cfr.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
console.log(`Decompiling ${name}...`);

document.getElementById("file-content").textContent = // stripHeader(
decompile(new Int8Array(evt.target.result), { classes: [], options: {} });
decompile(new Int8Array(evt.target.result));
// );

console.log(`Decompiled ${name} in ${Date.now() - start}ms.`);
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/dev/cephx/cfr/DecompilerOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@

import org.teavm.jso.JSBody;
import org.teavm.jso.JSByRef;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;

public interface DecompilerOptions extends JSObject {
@JSProperty
@JSByRef
byte[][] getClasses();

@JSBody(script = "return Object.entries(this.options).map(e => { return { name: e[0], value: e[1] }; });")
@JSBody(script = "return this.options ? Object.entries(this.options) : [];")
Option[] getOptions();

// can't use the logical OR, it breaks the TeaVM minifier
@JSBody(script = "return this.source ? this.source : (() => { return null; });")
Source getSource();

interface Option extends JSObject {
@JSProperty
@JSBody(script = "return this[0];")
String getName();

@JSProperty
@JSBody(script = "return this[1];")
String getValue();
}

@JSFunctor
interface Source extends JSObject {
@JSByRef
byte[] get(String name);
}
}
14 changes: 10 additions & 4 deletions src/main/java/dev/cephx/cfr/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@

import dev.cephx.cfr.source.ByteArrayClassSource;
import dev.cephx.cfr.source.CompositeClassSource;
import dev.cephx.cfr.source.OutputSinkFactoryImpl;
import dev.cephx.cfr.sink.OutputSinkFactoryImpl;
import dev.cephx.cfr.source.SimpleClassSource;
import dev.cephx.cfr.source.VMClassSource;
import org.benf.cfr.reader.api.CfrDriver;
import org.teavm.jso.JSByRef;
import org.teavm.jso.JSExport;
import org.teavm.jso.core.JSObjects;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
@JSExport
public static String decompile(@JSByRef byte[] b, DecompilerOptions options) throws Throwable {
final OutputSinkFactoryImpl sinkFactory = new OutputSinkFactoryImpl();
if (options == null || JSObjects.isUndefined(options)) {
options = JSObjects.create();
}

final var sinkFactory = new OutputSinkFactoryImpl();
final var source = new ByteArrayClassSource(b);
new CfrDriver.Builder()
.withClassFileSource(
new CompositeClassSource(
source,
new ByteArrayClassSource(options.getClasses()),
new SimpleClassSource(options.getSource()::get),
VMClassSource.INSTANCE
)
)
Expand All @@ -34,7 +40,7 @@ public static String decompile(@JSByRef byte[] b, DecompilerOptions options) thr
))
)
.build()
.analyse(source.names());
.analyse(List.of(source.name()));

return sinkFactory.outputOrThrow();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.cephx.cfr.source;
package dev.cephx.cfr.sink;

import org.benf.cfr.reader.api.OutputSinkFactory;

Expand Down
22 changes: 5 additions & 17 deletions src/main/java/dev/cephx/cfr/source/ByteArrayClassSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,12 @@
import org.benf.cfr.reader.api.ClassFileSource;
import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public record ByteArrayClassSource(Map<String, byte[]> classes) implements ClassFileSource {
public ByteArrayClassSource(byte[]... classes) {
this(
Arrays.stream(classes)
.map(b -> Map.entry(ClassFileUtil.getClassName(b), b))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
);

public record ByteArrayClassSource(String name, byte[] data) implements ClassFileSource {
public ByteArrayClassSource(byte[] data) {
this(ClassFileUtil.getClassName(data), data);
}

@Override
Expand All @@ -41,11 +34,6 @@ public Pair<byte[], String> getClassFileContent(String path) {
name = path.substring(0, extIndex);
}

final byte[] data = classes.get(name);
return data != null ? new Pair<>(data, path) : null;
}

public List<String> names() {
return List.copyOf(classes.keySet());
return this.name.equals(name) ? new Pair<>(this.data, path) : null;
}
}
37 changes: 37 additions & 0 deletions src/main/java/dev/cephx/cfr/source/SimpleClassSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dev.cephx.cfr.source;

import org.benf.cfr.reader.api.ClassFileSource;
import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;

import java.util.Collection;
import java.util.List;
import java.util.function.Function;

public record SimpleClassSource(Function<String, byte[]> source) implements ClassFileSource {
@Override
public void informAnalysisRelativePathDetail(String usePath, String classFilePath) {
}

@Override
public Collection<String> addJar(String jarPath) {
return List.of();
}

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

@Override
public Pair<byte[], String> getClassFileContent(String path) {
String name = path;

final int extIndex = path.indexOf(".class");
if (extIndex != -1) {
name = path.substring(0, extIndex);
}

final byte[] b = this.source.apply(name);
return b != null ? new Pair<>(b, path) : null;
}
}

0 comments on commit 912dbe7

Please sign in to comment.