Skip to content

Commit 5e179a9

Browse files
mbuczkoexpez
authored andcommitted
[Fix #278] Memory leak in find-symbol
1 parent 57eb92e commit 5e179a9

File tree

4 files changed

+47
-50
lines changed

4 files changed

+47
-50
lines changed

project.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
:dependencies [[nrepl "0.5.3"]
77
^:inline-dep [http-kit "2.3.0"]
88
^:inline-dep [cheshire "5.8.1"]
9-
^:inline-dep [org.clojure/tools.analyzer.jvm "0.7.2"]
9+
^:inline-dep [org.clojure/tools.analyzer.jvm "0.7.3"]
1010
^:inline-dep [org.clojure/tools.namespace "0.3.1" :exclusions [org.clojure/tools.reader]]
1111
^:inline-dep [org.clojure/tools.reader "1.3.2"]
1212
^:inline-dep [cider/orchard "0.5.4"]

src/refactor_nrepl/analyzer.clj

+11-10
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,17 @@
2828
a map of the aliases for the namespace as the second element
2929
in the same format as ns-aliases"
3030
[body]
31-
(let [ns-decl (read-ns-decl (PushbackReader. (java.io.StringReader. body)))
32-
aliases (->> ns-decl
33-
(filter list?)
34-
(some #(when (#{:require} (first %)) %))
35-
rest
36-
(remove symbol?)
37-
(filter #(contains? (set %) :as))
38-
(#(zipmap (map (partial get-alias nil) %)
39-
(map first %))))]
40-
[(second ns-decl) aliases]))
31+
(with-open [string-reader (java.io.StringReader. body)]
32+
(let [ns-decl (read-ns-decl (PushbackReader. string-reader))
33+
aliases (->> ns-decl
34+
(filter list?)
35+
(some #(when (#{:require} (first %)) %))
36+
rest
37+
(remove symbol?)
38+
(filter #(contains? (set %) :as))
39+
(#(zipmap (map (partial get-alias nil) %)
40+
(map first %))))]
41+
[(second ns-decl) aliases])))
4142

4243
(defn- noop-macroexpand-1 [form]
4344
form)

src/refactor_nrepl/find/find_symbol.clj

+29-28
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
(str/replace "#'" "")
2525
(str/replace "clojure.core/" "")))
2626
full-class (get alias-info class class)]
27-
(str/join "/" (remove nil? [full-class (:field node)]))))
27+
(str/join "/" (remove nil? [(if (map? full-class) "" full-class) (:field node)]))))
2828

2929
(defn- contains-var?
30-
"Checks if the var of `node` is present in the `var-set`."
31-
[vars-set alias-info node]
32-
(vars-set (node->var alias-info node)))
30+
"Checks if the var of `node` is same as given `var-name`"
31+
[var-name alias-info node]
32+
(= var-name (node->var alias-info node)))
3333

3434
(defn present-before-expansion?
3535
"returns true if node is not result of macro expansion or if it is and it contains
@@ -48,8 +48,7 @@
4848

4949
(defn- find-nodes
5050
"Filters `ast` with `pred` and returns a list of vectors with line-beg, line-end,
51-
colum-beg, column-end and the result of applying pred to the node for each
52-
node in the AST.
51+
colum-beg, column-end for each node in the AST.
5352
5453
if name present macro call sites are checked if they contained name before macro expansion"
5554
([asts pred]
@@ -58,8 +57,7 @@
5857
(map (juxt (comp :line :env)
5958
(comp :end-line :env)
6059
(comp :column :env)
61-
(comp :end-column :env)
62-
pred))
60+
(comp :end-column :env)))
6361
(map #(zipmap [:line-beg :line-end :col-beg :col-end] %))))
6462
([name asts pred]
6563
(find-nodes (map #(postwalk % (partial dissoc-macro-nodes name)) asts) pred)))
@@ -81,7 +79,7 @@
8179
var-name)))
8280

8381
(defn- contains-var-or-const? [var-name alias-info node]
84-
(or (contains-var? #{var-name} alias-info node)
82+
(or (contains-var? var-name alias-info node)
8583
(contains-const? var-name alias-info node)))
8684

8785
(defn- find-symbol-in-ast [name asts]
@@ -103,24 +101,24 @@
103101
(str/join "\n")
104102
str/trim)))
105103

106-
(defn- find-symbol-in-file [fully-qualified-name ignore-errors ^File file]
104+
(defn- find-symbol-in-file [fully-qualified-name ignore-errors referred-syms ^File file]
107105
(let [file-content (slurp file)
108106
locs (try (->> (ana/ns-ast file-content)
109107
(find-symbol-in-ast fully-qualified-name)
110108
(filter :line-beg))
111109
(catch Exception e
112110
(when-not ignore-errors
113111
(throw e))))
114-
locs (concat locs
115-
(some->
116-
(libspecs/referred-syms-by-file&fullname)
117-
(get-in [:clj (str file) fully-qualified-name])
118-
meta
119-
((fn [{:keys [line column end-line end-column]}]
120-
(list {:line-beg line
121-
:line-end end-line
122-
:col-beg column
123-
:col-end end-column})))))
112+
locs (into
113+
locs (some->
114+
referred-syms
115+
(get-in [:clj (str file) fully-qualified-name])
116+
meta
117+
((fn [{:keys [line column end-line end-column]}]
118+
(list {:line-beg line
119+
:line-end end-line
120+
:col-beg column
121+
:col-end end-column})))))
124122
gather (fn [info]
125123
(merge info
126124
{:file (.getCanonicalPath file)
@@ -134,10 +132,11 @@
134132
(let [namespace (or ns (core/ns-from-string (slurp file)))
135133
fully-qualified-name (if (= namespace "clojure.core")
136134
var-name
137-
(str/join "/" [namespace var-name]))]
135+
(str/join "/" [namespace var-name]))
136+
referred-syms (libspecs/referred-syms-by-file&fullname)]
138137
(->> (core/dirs-on-classpath)
139138
(mapcat (partial core/find-in-dir (some-fn core/clj-file? core/cljc-file?)))
140-
(mapcat (partial find-symbol-in-file fully-qualified-name ignore-errors)))))
139+
(mapcat (partial find-symbol-in-file fully-qualified-name ignore-errors referred-syms)))))
141140

142141
(defn- get&read-enclosing-sexps
143142
[file-content {:keys [^long line-beg ^long col-beg]}]
@@ -165,7 +164,7 @@
165164
res)))
166165

167166
(defn- occurrence-for-optmap-default
168-
[var-name [{:keys [line-beg col-beg] :as orig-occurrence} [_ _ ^String level2-string _]]]
167+
[var-name [orig-occurrence [_ _ ^String level2-string _]]]
169168
(let [var-positions (re-pos (re-pattern (format "\\W%s\\W" var-name)) level2-string)
170169
^long var-default-pos (first (second var-positions))
171170
newline-cnt (reduce (fn [cnt char] (if (= char \newline) (inc (long cnt)) cnt)) 0 (.substring level2-string 0 var-default-pos))
@@ -228,10 +227,6 @@
228227
(map (partial occurrence-for-optmap-default var-name)))]
229228
(sort-by :line-beg (concat local-occurrences optmap-def-occurrences))))))
230229

231-
(defn- to-find-symbol-result
232-
[{:keys [line-beg line-end col-beg col-end name file match]}]
233-
[line-beg line-end col-beg col-end name file match])
234-
235230
(defn find-symbol [{:keys [file ns name line column ignore-errors]}]
236231
(core/throw-unless-clj-file file)
237232
(let [ignore-errors? (= ignore-errors "true")
@@ -240,9 +235,15 @@
240235
distinct
241236
(remove find-util/spurious?)
242237
future)]
238+
243239
(or
244240
;; find-local-symbol is the fastest of the three
245-
(not-empty (remove find-util/spurious? (distinct (find-local-symbol file name line column))))
241+
;; if result is not empty, there is no point in keeping `find-macro` and `find-global-symbol` futures still active
242+
(when-let [result (not-empty (remove find-util/spurious? (distinct (find-local-symbol file name line column))))]
243+
(future-cancel macros)
244+
(future-cancel globals)
245+
result)
246+
246247
;; find-macros has to be checked first because find-global-symbol
247248
;; can return spurious hits for some macro definitions
248249
@macros

src/refactor_nrepl/middleware.clj

+6-11
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
`(with-errors-being-passed-on ~transport ~msg
5555
(config/with-config ~msg
5656
(transport/send ~transport
57-
(response-for ~msg ~(apply hash-map :status :done kvs))))))
57+
(response-for ~msg ~(apply hash-map kvs))))))
5858

5959
(defn- bencode-friendly-data [data]
6060
;; Bencode only supports byte strings, integers, lists and maps.
@@ -94,15 +94,10 @@
9494
(require-and-resolve 'refactor-nrepl.find.find-symbol/find-symbol)))
9595

9696
(defn- find-symbol-reply [{:keys [transport] :as msg}]
97-
(config/with-config msg
98-
(with-errors-being-passed-on transport msg
99-
(let [occurrences (@find-symbol msg)]
100-
(doseq [occurrence occurrences
101-
:let [response (serialize-response msg occurrence)]]
102-
(transport/send transport
103-
(response-for msg :occurrence response)))
104-
(transport/send transport (response-for msg :count (count occurrences)
105-
:status :done))))))
97+
(let [occurrences (@find-symbol msg)]
98+
(doseq [occurrence occurrences]
99+
(reply transport msg :occurrence (serialize-response msg occurrence)))
100+
(reply transport msg :count (count occurrences) :status :done)))
106101

107102
(def ^:private artifact-list
108103
(delay (require-and-resolve 'refactor-nrepl.artifacts/artifact-list)))
@@ -138,7 +133,7 @@
138133
(require-and-resolve 'refactor-nrepl.find.find-locals/find-used-locals)))
139134

140135
(defn- find-used-locals-reply [{:keys [transport] :as msg}]
141-
(reply transport msg :used-locals (@find-used-locals msg)))
136+
(reply transport msg :used-locals (@find-used-locals msg) :status :done))
142137

143138
(defn- version-reply [{:keys [transport] :as msg}]
144139
(reply transport msg :status :done :version (core/version)))

0 commit comments

Comments
 (0)