1+ ; Copyright (c) Rich Hickey. All rights reserved.
2+ ; The use and distribution terms for this software are covered by the
3+ ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+ ; which can be found in the file epl-v10.html at the root of this distribution.
5+ ; By using this software in any fashion, you are agreeing to be bound by
6+ ; the terms of this license.
7+ ; You must not remove this notice, or any other, from this software.
8+
9+ (ns clojure.test-clojure.clearing
10+ (:import
11+ [java.lang.reflect Field])
12+ (:require
13+ [clojure.string :as str]
14+ [clojure.test :refer :all ]))
15+
16+ (set! *warn-on-reflection* true )
17+
18+ ; ;; ClojureCLR does not do locals clearing.
19+ ; ;; A very long time ago, in a conversation with the DLR/IronXXX people
20+ ; ;; (I think Rich Hickey raised the issue), we were told that locals clearing
21+ ; ;; would not be necessary on the CLR.
22+ ; ;;
23+ ; ;; Indeed, if one runs CLJ-2145-repro with 1E9 as suggested, exhaustion does not occur.
24+
25+ #_(defn fields
26+ [o]
27+ (.getDeclaredFields (class o)))
28+
29+ #_(defn primitive?
30+ [^Field field]
31+ (.isPrimitive (.getType field)))
32+
33+ #_(defn special-fn-field?
34+ [^String field-name]
35+ (or (= field-name " __meta" )
36+ (str/starts-with? field-name " __cached_class__" )
37+ (str/starts-with? field-name " const__" )
38+ (str/ends-with? field-name " __" )))
39+
40+ #_(defn clearable-closed-overs
41+ [fobj]
42+ (->> (fields fobj)
43+ (remove primitive?) ; ; can't clear primitives
44+ (remove #(special-fn-field? (.getName ^Field %)))))
45+
46+ #_(defn private-field-value [^Object obj ^Field field]
47+ (. field (setAccessible true ))
48+ (. field (get obj)))
49+
50+ ; ; Check whether all non-primitive closed-overs in a function are nil
51+ #_(defn cleared?
52+ [fobj]
53+ (every? #(nil? (private-field-value fobj %)) (clearable-closed-overs fobj)))
54+
55+ ; ; ---
56+
57+ ; ; After invocation, check all closed-over non-primitive fields in a :once fn
58+
59+ #_(defn check-clear
60+ [f]
61+ (is (not (cleared? f)))
62+ (f )
63+ (cleared? f))
64+
65+ #_(deftest test-clearing
66+ (let [x :a ]
67+ ; ; base case
68+ (is (check-clear (^{:once true } fn* [] x)))
69+
70+ ; ; conditional above fn
71+ (when true
72+ (is (check-clear (^{:once true } fn* [] x))))
73+ (case x
74+ :a (is (check-clear (^{:once true } fn* [] x))))
75+
76+ ; ; loop above fn
77+ (loop []
78+ (is (check-clear (^{:once true } fn* [] x))))
79+
80+ ; ; conditional below fn
81+ (is (check-clear (^{:once true } fn* [] (when true x))))
82+
83+ ; ; loop below fn
84+ (is (not (check-clear (^{:once true } fn* [] (loop [] x)))))
85+ (is (not (check-clear (^{:once true } fn* [] (loop [] x) nil ))))
86+
87+ ; ; recur in :once below fn
88+ (is (not (check-clear (^{:once true } fn* [] (if false (recur ) x)))))
89+ ))
90+
91+ #_(deftest test-nested
92+ (let [x :a ]
93+ ; ; nested fns
94+ (let [inner (^{:once true } fn* [] x)
95+ outer (fn* [] inner)]
96+ (is (not (check-clear outer))) ; ; outer not :once
97+ (is (check-clear inner)))
98+
99+ (let [inner (^{:once true } fn* [] x)
100+ outer (^{:once true } fn* [] inner)]
101+ (is (check-clear outer))
102+ (is (check-clear inner)))
103+
104+ (let [inner (^{:once true } fn* [] x)
105+ middle (^{:once true } fn* [] inner)
106+ outer (^{:once true } fn* [] middle)]
107+ (is (check-clear outer))
108+ (is (check-clear middle))
109+ (is (check-clear inner)))))
110+
111+ ; ; Repro from CLJ-2145
112+ #_(defn consume [x] (doseq [_ x] _))
113+ #_(defn call-and-keep [f] (f ) f )
114+ #_(defn repro [x]
115+ (if true (call-and-keep (^:once fn* [] (consume x)))))
116+ #_(deftest CLJ-2145-repro
117+ (let [f (repro (range 100 ))] ; ; 1e9 to exhaust
118+ (is (cleared? f))))
0 commit comments