From 62049a89b7ef44958635163ef7e232c62147b006 Mon Sep 17 00:00:00 2001 From: Alexander Yakushev Date: Sat, 30 Jul 2022 14:49:43 +0300 Subject: [PATCH 1/2] [#10] Don't lose metadata when transforming the form with walk --- src/clj_java_decompiler/core.clj | 47 ++++++++++++++---- test/clj_java_decompiler/core_test.clj | 67 ++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 10 deletions(-) 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; + } +}"))) From 20eeb428f1dd79e2a1f442f55e10644b891929f5 Mon Sep 17 00:00:00 2001 From: Alexander Yakushev Date: Sat, 30 Jul 2022 15:00:28 +0300 Subject: [PATCH 2/2] Update README --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 [![CircleCI](https://dl.circleci.com/status-badge/img/gh/clojure-goes-fast/clj-java-decompiler/tree/master.svg?style=svg)](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