diff --git a/README.md b/README.md
index 0c0dbf9..2a188c4 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# clj-java-decompiler
+# clj-java-decompiler [](https://dl.circleci.com/status-badge/redirect/gh/clojure-goes-fast/clj-java-decompiler/tree/master)
_You can read the motivation behind clj-java-decompiler and the usage example in
the
@@ -170,13 +170,15 @@ program with ND's agent on the classpath.
So, you can't decompile an existing function definition with CJD. But if you are
using CIDER, you can jump to the definition of the function you want to
-decompile, disable read-only mode (C-x C-q), wrap the `defn` form
-with `clj-java-decompiler.core/decompile` and recompile the form (C-c
-C-c).
+decompile and call `M-x clj-decompiler-decompile` on it. Alternatively, if you
+don't use `clj-decompiler.el`, you can disable read-only mode (C-x
+C-q) in the buffer of the existing function you want to decompile, wrap
+the `defn` form with `clj-java-decompiler.core/decompile` and recompile the form
+(C-c C-c).
## License
clj-java-decompiler is distributed under the Eclipse Public License.
See [LICENSE](LICENSE).
-Copyright 2018-2020 Alexander Yakushev
+Copyright 2018-2022 Alexander Yakushev
diff --git a/src/clj_java_decompiler/core.clj b/src/clj_java_decompiler/core.clj
index ba78f31..373247d 100644
--- a/src/clj_java_decompiler/core.clj
+++ b/src/clj_java_decompiler/core.clj
@@ -1,6 +1,5 @@
(ns clj-java-decompiler.core
- (:require [clojure.java.io :as io]
- [clojure.walk :as walk])
+ (:require [clojure.java.io :as io])
(:import clojure.lang.Compiler
com.strobel.assembler.InputTypeLoader
(com.strobel.assembler.metadata DeobfuscationUtilities MetadataSystem
@@ -17,17 +16,45 @@
;;;; Compilation
+clojure.walk/prewalk
+(defn- walk-meta-preserving
+ "Like `clojure.walk/walk`, but preserves meta. Redundant after
+ https://clojure.atlassian.net/browse/CLJ-2568 is merged."
+ [inner outer form]
+ (let [restore-meta #(if-let [fm (meta form)]
+ (with-meta %
+ (merge fm (meta %)))
+ %)]
+ (cond
+ (list? form) (outer (restore-meta (apply list (map inner form))))
+ (instance? clojure.lang.IMapEntry form)
+ (outer (clojure.lang.MapEntry/create (inner (key form)) (inner (val form))))
+ (seq? form) (outer (restore-meta (doall (map inner form))))
+ (instance? clojure.lang.IRecord form)
+ (outer (restore-meta (reduce (fn [r x] (conj r (inner x))) form form)))
+ (coll? form) (outer (restore-meta (into (empty form) (map inner form))))
+ :else (outer form))))
+
+(defn- prewalk [f form]
+ (walk-meta-preserving (partial prewalk f) identity (f form)))
+
+(defn- enrich-lambdas-with-line-numbers
+ "Walk the `form` and attach line number-derived names to nameless fns so that
+ they are easier to match to source code."
+ [form]
+ (prewalk
+ #(if (and (sequential? %) (not (vector? %))
+ ('#{fn fn*} (first %)) (not (symbol? (second %))))
+ (if-let [line (:line (meta %))]
+ (list* 'fn (symbol (str "fn_line_" line)) (rest %))
+ %)
+ %)
+ form))
+
(defn- aot-compile
"Compile the form to classfiles in the temporary directory."
[form]
- (let [form (walk/prewalk
- #(if (and (sequential? %) (not (vector? %))
- ('#{fn fn*} (first %)) (not (symbol? (second %))))
- (if-let [line (:line (meta %))]
- (list* 'fn (symbol (str "fn_line_" line)) (rest %))
- %)
- %)
- form)
+ (let [form (enrich-lambdas-with-line-numbers form)
tmp-source (File/createTempFile "tmp-src" "" tmp-dir)]
(spit tmp-source (binding [*print-meta* true]
(pr-str form)))
diff --git a/test/clj_java_decompiler/core_test.clj b/test/clj_java_decompiler/core_test.clj
index 143d5c9..550ba57 100644
--- a/test/clj_java_decompiler/core_test.clj
+++ b/test/clj_java_decompiler/core_test.clj
@@ -102,3 +102,70 @@ public final class core_test$fn_line_91__<<>> extends AFunction
return invokeStatic();
}
}")))
+
+(deftest metadata-preserved-test
+ (is (=str (with-out-trimmed-str
+ (sut/decompile (defn hint-callsite [m] (.intValue ^Long (m 1)))))
+ "// Decompiling class: clj_java_decompiler/core_test$hint_callsite
+package clj_java_decompiler;
+import clojure.lang.*;
+public final class core_test$hint_callsite extends AFunction
+{
+ public static final Object const__0;
+ public static Object invokeStatic(final Object m) {
+ return ((Long)((IFn)m).invoke(core_test$hint_callsite.const__0)).intValue();
+ }
+ @Override
+ public Object invoke(final Object m) {
+ return invokeStatic(m);
+ }
+ static {
+ const__0 = 1L;
+ }
+}"))
+
+ (is (=str (with-out-trimmed-str
+ (sut/decompile (defn hint-let-symbol [m]
+ (let [^Long l (m 1)]
+ (.intValue l)))))
+ "// Decompiling class: clj_java_decompiler/core_test$hint_let_symbol
+package clj_java_decompiler;
+import clojure.lang.*;
+public final class core_test$hint_let_symbol extends AFunction
+{
+ public static final Object const__0;
+ public static Object invokeStatic(final Object m) {
+ final Object l = ((IFn)m).invoke(core_test$hint_let_symbol.const__0);
+ return ((Long)l).intValue();
+ }
+ @Override
+ public Object invoke(final Object m) {
+ return invokeStatic(m);
+ }
+ static {
+ const__0 = 1L;
+ }
+}"))
+
+ (is (=str (with-out-trimmed-str
+ (sut/decompile (defn hint-let-value [m]
+ (let [l ^Long (m 1)]
+ (.intValue l)))))
+ "// Decompiling class: clj_java_decompiler/core_test$hint_let_value
+package clj_java_decompiler;
+import clojure.lang.*;
+public final class core_test$hint_let_value extends AFunction
+{
+ public static final Object const__0;
+ public static Object invokeStatic(final Object m) {
+ final Object l = ((IFn)m).invoke(core_test$hint_let_value.const__0);
+ return ((Long)l).intValue();
+ }
+ @Override
+ public Object invoke(final Object m) {
+ return invokeStatic(m);
+ }
+ static {
+ const__0 = 1L;
+ }
+}")))