Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/clojure-goes-fast/clj-java-decompiler

REPL-integrated Clojure-to-Java decompiler
https://github.com/clojure-goes-fast/clj-java-decompiler

bytecode decompiler java

Last synced: 3 months ago
JSON representation

REPL-integrated Clojure-to-Java decompiler

Awesome Lists containing this project

README

        

# clj-java-decompiler [![CircleCI](https://img.shields.io/circleci/build/github/clojure-goes-fast/clj-java-decompiler/master.svg)](https://dl.circleci.com/status-badge/redirect/gh/clojure-goes-fast/clj-java-decompiler/tree/master) [![](https://img.shields.io/clojars/dt/com.clojure-goes-fast/clj-java-decompiler?color=teal)](https://clojars.org/com.clojure-goes-fast/clj-java-decompiler) [![](https://img.shields.io/badge/-changelog-blue.svg)](CHANGELOG.md)

_You can read the motivation behind clj-java-decompiler and the usage example in
the
[blog post](http://clojure-goes-fast.com/blog/introspection-tools-java-decompilers/)._

This library is an integrated Clojure-to-Java decompiler usable from the REPL.
Under the hood, it uses [Procyon](https://github.com/mstrobel/procyon) to
decompile the bytecode generated by Clojure compiler into the equivalent Java
source code.

Quick demo:

```java
user> (clj-java-decompiler.core/decompile
(loop [i 100, sum 0]
(if (< i 0)
sum
(recur (unchecked-dec i) (unchecked-add sum i)))))

// Decompiling class: user$fn__13332
import clojure.lang.*;

public final class user$fn__13332 extends AFunction
{
public static Object invokeStatic() {
long i = 100L;
long sum = 0L;
while (i >= 0L) {
final long n = i - 1L;
sum += i;
i = n;
}
return Numbers.num(sum);
}

public Object invoke() {
return invokeStatic();
}
}
```

## Why?

There are several usecases when you may want to use a Java decompiler:

- To get a general understanding how Clojure compiler works: how functions are
compiled into classes, how functions are invoked, etc.
- To optimize performance bottlenecks when using low-level constructs like
loops, primitive math, and type hints.
- To investigate how Java interop facilities are implemented (`reify`, `proxy`,
`gen-class`).

## Usage

Add `com.clojure-goes-fast/clj-java-decompiler` to your dependencies:

[![](https://clojars.org/com.clojure-goes-fast/clj-java-decompiler/latest-version.svg)](https://clojars.org/com.clojure-goes-fast/clj-java-decompiler)

Then, at the REPL:

```clojure
user> (require '[clj-java-decompiler.core :refer [decompile]])
nil
user> (decompile (fn [] (println "Hello, decompiler!")))
```

```java
// Decompiling class: clj_java_decompiler/core$fn__13257
import clojure.lang.*;

public final class core$fn__13257 extends AFunction
{
public static final Var __println;

public static Object invokeStatic() {
return __println.invoke("Hello, decompiler!");
}

public Object invoke() {
return invokeStatic();
}

static {
__println = RT.var("clojure.core", "println");
}
}
```

You can also disassemble to bytecode, with the output being similar to the one
of `javap`.

```
user> (disassemble (fn [] (println "Hello, decompiler!")))

;;; Redacted

public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: getstatic clj_java_decompiler/core$fn__17004.const__0:Lclojure/lang/Var;
3: invokevirtual clojure/lang/Var.getRawRoot:()Ljava/lang/Object;
linenumber 1
6: checkcast Lclojure/lang/IFn;
9: getstatic clj_java_decompiler/core$fn__17004.const__1:
Lclojure/lang/Var;
12: invokevirtual clojure/lang/Var.getRawRoot:()Ljava/lang/Object;
linenumber 1
15: checkcast Lclojure/lang/IFn;
18: ldc "Hello, decompiler!"
linenumber 1
20: invokeinterface clojure/lang/IFn.invoke:(Ljava/lang/Object;)Ljava/lang/Object;
linenumber 1
25: invokeinterface clojure/lang/IFn.invoke:(Ljava/lang/Object;)Ljava/lang/Object;
30: areturn
```

### Post-processing and de-cluttering

To make the output clearer, clj-java-decompiler by default disables [locals
clearing](https://clojuredocs.org/clojure.core/*compiler-options*) for the code
it compiles. You can re-enable it by setting this compiler option to false
explicitly, like this:

```clj
(binding [*compiler-options* {:disable-locals-clearing false}]
(decompile ...))
```

You can also change other compiler options (static linking, metadata elision) in
the same way.

By default, clj-java-decompiler also performs additional post-processing of the
Procyon output. This includes removing current class name from static
references, and replacing opaque `const__` fields with more informative var
names. You can disable this post-processing by executing:

```clj
(reset! clj-java-decompier.core/postprocessing-enabled false)
```

### Usage from Emacs

You can use [clj-decompiler.el](https://github.com/bsless/clj-decompiler.el)
package (installable from MELPA) to fluidly invoke `clj-java-decompiler` right
from your Clojure code buffer. Like with `cider-macroexpand`, you place your
cursor at the end of the form you want to decompile and invoke `M-x
clj-decompiler-decompile`. This will compile the form before the cursor, then
decompile it with `clj-java-decompiler`, and present you the Java output in a
separate syntax-highlighted buffer.

`clj-decompiler.el` can also automatically inject `clj-java-decompiler`
dependency at `cider-jack-in` time. Check its repository for more details.

### How to decompile an already defined function

Short answer: you can't do that. JVM doesn't retain the bytecode for classes it
has already loaded. When the Clojure compiler compiles a piece of Clojure code,
it transforms it into bytecode in memory, then loads it with a classloader, and
discards the bytecode. So, in order to decompile a function, you must pass its
source code to the `decompile` macro.

Fortunately, most Clojure libraries are distributed in the source form. If you
use CIDER or any other Clojure IDE, you can jump to the definition of the
function you want to decompile, disable read-only mode (in Emacs, that is done
with C-x C-q), wrap the `defn` form with
`clj-java-decompiler.core/decompile` and recompile the form (C-c C-c
in Emacs). This becomes much simpler if you use
[clj-decompiler.el](https://github.com/bsless/clj-decompiler.el), you just call
`M-x clj-decompiler-decompile` on the function you've jumped to.

If you absolutely need to decompile a loaded function for which the source code
is not available, you can consider trying the
[no.disassemble](https://github.com/gtrak/no.disassemble) library. Note that it
must be loaded into the JVM at startup time as an agent and can only disassemble
functions into bytecode representation (not decompile into Java code).

Another option for when you have no source code but compiled `.class` files is
to use one of the available [Java
decompilers](http://clojure-goes-fast.com/blog/introspection-tools-java-decompilers/).

## License

clj-java-decompiler is distributed under the Eclipse Public License.
See [LICENSE](LICENSE).

Copyright 2018-2024 Alexander Yakushev