From 587f9b2d3a7b609bfb4a80bd0bd489d71328b83a Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Mon, 20 Apr 2015 18:36:27 +0200 Subject: [PATCH 001/854] CLJ-1698 fix conditional reading bugs Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LispReader.java | 8 +------- test/clojure/test_clojure/reader.cljc | 9 ++++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index c249eda7c4..3a1766bf6a 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -253,8 +253,6 @@ static private Object read(PushbackReader r, boolean eofIsError, Object eofValue if(macroFn != null) { Object ret = macroFn.invoke(r, (char) ch, opts, pendingForms); - if(RT.suppressRead()) - return null; //no op macros return the reader if(ret == r) continue; @@ -1210,13 +1208,9 @@ public Object invoke(Object reader, Object firstChar, Object opts, Object pendin if (!(name instanceof Symbol)) throw new RuntimeException("Reader tag must be a symbol"); Symbol sym = (Symbol)name; - if (RT.suppressRead()) { - read(r, true, null, true, opts, pendingForms); - return r; - } Object form = read(r, true, null, true, opts, pendingForms); - if(isPreserveReadCond(opts)) { + if(isPreserveReadCond(opts) || RT.suppressRead()) { return TaggedLiteral.create(sym, form); } else { return sym.getName().contains(".") ? readRecord(form, sym, opts, pendingForms) : readTagged(form, sym, opts, pendingForms); diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc index eda7f3f953..f946d4bb35 100644 --- a/test/clojure/test_clojure/reader.cljc +++ b/test/clojure/test_clojure/reader.cljc @@ -692,7 +692,14 @@ (is (thrown-with-msg? RuntimeException #"is reserved" (read-string {:read-cond :allow} "#?@(:foo :a :else :b)"))) (is (thrown-with-msg? RuntimeException #"must be a list" (read-string {:read-cond :allow} "#?[:foo :a :else :b]"))) (is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string {:read-cond :BOGUS} "#?[:clj :a :default nil]"))) - (is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string "#?[:clj :a :default nil]"))))) + (is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string "#?[:clj :a :default nil]")))) + (testing "clj-1698-regression" + (let [opts {:features #{:clj} :read-cond :allow}] + (is (= 1 (read-string opts "#?(:cljs {'a 1 'b 2} :clj 1)"))) + (is (= 1 (read-string opts "#?(:cljs (let [{{b :b} :a {d :d} :c} {}]) :clj 1)"))) + (is (= '(def m {}) (read-string opts "(def m #?(:cljs ^{:a :b} {} :clj ^{:a :b} {}))"))) + (is (= '(def m {}) (read-string opts "(def m #?(:cljs ^{:a :b} {} :clj ^{:a :b} {}))"))) + (is (= 1 (read-string opts "#?(:cljs {:a #_:b :c} :clj 1)")))))) (deftest eof-option (is (= 23 (read-string {:eof 23} ""))) From d1b344eb9c0c65846bcabc935914161a52d4d9ef Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 13 Apr 2015 13:49:19 -0500 Subject: [PATCH 002/854] CLJ-1699 Support data_readers.cljc with reader conditionals Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 9a773a14a4..9097e00207 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7442,9 +7442,10 @@ nil) (defn- data-reader-urls [] - (enumeration-seq - (.. Thread currentThread getContextClassLoader - (getResources "data_readers.clj")))) + (let [cl (.. Thread currentThread getContextClassLoader)] + (concat + (enumeration-seq (.getResources cl "data_readers.clj")) + (enumeration-seq (.getResources cl "data_readers.cljc"))))) (defn- data-reader-var [sym] (intern (create-ns (symbol (namespace sym))) @@ -7455,7 +7456,10 @@ (java.io.InputStreamReader. (.openStream url) "UTF-8"))] (binding [*file* (.getFile url)] - (let [new-mappings (read rdr false nil)] + (let [read-opts (if (.endsWith (.getPath url) "cljc") + {:eof nil :read-cond :allow} + {:eof nil}) + new-mappings (read read-opts rdr)] (when (not (map? new-mappings)) (throw (ex-info (str "Not a valid data-reader map") {:url url}))) From e251e14e2459663dbc1492aca025c7171a2e4ff0 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 13 Apr 2015 12:34:18 -0500 Subject: [PATCH 003/854] CLJ-1700 Enable reader conditionals from the REPL Signed-off-by: Stuart Halloway --- src/clj/clojure/main.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj index a87d19848e..14af9c7e85 100644 --- a/src/clj/clojure/main.clj +++ b/src/clj/clojure/main.clj @@ -137,7 +137,7 @@ [request-prompt request-exit] (or ({:line-start request-prompt :stream-end request-exit} (skip-whitespace *in*)) - (let [input (read)] + (let [input (read {:read-cond :allow} *in*)] (skip-if-eol *in*) input))) From 7c45fe6815254c722f350ef8bbc53a0caf757907 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 17 Apr 2015 11:26:24 -0500 Subject: [PATCH 004/854] CLJ-1703 - Make Throwable->map public and pretty print #error Signed-off-by: Stuart Halloway --- src/clj/clojure/core_print.clj | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 78c00745a8..f78594a8b7 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -413,7 +413,7 @@ (defmethod print-method StackTraceElement [^StackTraceElement o ^Writer w] (print-method [(symbol (.getClassName o)) (symbol (.getMethodName o)) (.getFileName o) (.getLineNumber o)] w)) -(defn- throwable-as-map [^Throwable o] +(defn Throwable->map [^Throwable o] (let [base (fn [^Throwable t] {:type (class t) :message (.getLocalizedMessage t) @@ -427,8 +427,33 @@ :trace (vec (.getStackTrace (or ^Throwable (last via) o)))})) (defn- print-throwable [^Throwable o ^Writer w] - (.write w "#error") - (print-method (throwable-as-map o) w)) + (.write w "#error {\n :cause ") + (let [{:keys [cause via trace]} (Throwable->map o) + print-via #(do (.write w "{:type ") + (print-method (:type %) w) + (.write w "\n :message ") + (print-method (:message %) w) + (.write w "\n :at ") + (print-method (:at %) w) + (.write w "}"))] + (print-method cause w) + (when via + (.write w "\n :via\n [") + (when-let [fv (first via)] + (print-via fv) + (doseq [v (rest via)] + (.write w "\n ") + (print-via v))) + (.write w "]")) + (when trace + (.write w "\n :trace\n [") + (when-let [ft (first trace)] + (print-method ft w) + (doseq [t (rest trace)] + (.write w "\n ") + (print-method t w))) + (.write w "]"))) + (.write w "}")) (defmethod print-method Throwable [^Throwable o ^Writer w] (print-throwable o w)) From ce033a15d08bba9cfff373badab1bfe5aba24d1c Mon Sep 17 00:00:00 2001 From: Devin Walters Date: Fri, 24 Apr 2015 08:18:58 -0500 Subject: [PATCH 005/854] CLJ-1709 Fixes bug in count and contents of LongRange with step != 1 Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LongRange.java | 4 ++-- test/clojure/test_clojure/sequences.clj | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/jvm/clojure/lang/LongRange.java b/src/jvm/clojure/lang/LongRange.java index 125fa45528..c956157dda 100644 --- a/src/jvm/clojure/lang/LongRange.java +++ b/src/jvm/clojure/lang/LongRange.java @@ -156,9 +156,9 @@ public ISeq chunkedMore() { } public int absCount(long start, long end, long step) { - double c = (end - start) / step; + double c = (double) (end - start) / step; int ic = (int) c; - if(c < ic) + if(c > ic) return ic + 1; else return ic; diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj index 17b9273cda..f6a93fa184 100644 --- a/test/clojure/test_clojure/sequences.clj +++ b/test/clojure/test_clojure/sequences.clj @@ -10,7 +10,10 @@ ; Contributors: Stuart Halloway (ns clojure.test-clojure.sequences - (:use clojure.test) + (:require [clojure.test :refer :all] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop] + [clojure.test.check.clojure-test :refer (defspec)]) (:import clojure.lang.IReduce)) ;; *** Tests *** @@ -973,6 +976,12 @@ {} {:a 1 :b 2} #{} #{1 2} )) +(defspec longrange-equals-range 100 + (prop/for-all [start gen/int + end gen/int + step gen/s-pos-int] + (= (clojure.lang.Range/create start end step) + (clojure.lang.LongRange/create start end step)))) (deftest test-range (are [x y] (= x y) From 7b9c61d83304ff9d5f9feddecf23e620c0b33c6e Mon Sep 17 00:00:00 2001 From: Immo Heikkinen Date: Wed, 22 Apr 2015 08:33:30 +0300 Subject: [PATCH 006/854] CLJ-1711 - Fix structmap iterator Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/PersistentStructMap.java | 4 ++-- test/clojure/test_clojure/data_structures.clj | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/jvm/clojure/lang/PersistentStructMap.java b/src/jvm/clojure/lang/PersistentStructMap.java index 3878841824..68baf7cc87 100644 --- a/src/jvm/clojure/lang/PersistentStructMap.java +++ b/src/jvm/clojure/lang/PersistentStructMap.java @@ -195,9 +195,9 @@ public boolean hasNext(){ public Object next(){ if(ks != null) { - Object ret = ks.first(); + Object key = ks.first(); ks = ks.next(); - return ret; + return entryAt(key); } else if(extIter != null && extIter.hasNext()) return extIter.next(); diff --git a/test/clojure/test_clojure/data_structures.clj b/test/clojure/test_clojure/data_structures.clj index d8f68afd88..27e7705237 100644 --- a/test/clojure/test_clojure/data_structures.clj +++ b/test/clojure/test_clojure/data_structures.clj @@ -1261,4 +1261,17 @@ (defspec seq-and-iter-match-for-vals identity [^{:tag clojure.test-clojure.data-structures/gen-map} m] - (seq-iter-match (vals m) (vals m))) \ No newline at end of file + (seq-iter-match (vals m) (vals m))) + +(defstruct test-struct :a :b) + +(defn gen-struct + [] + (let [s (struct test-struct (gen/int) (gen/int))] + (gen/one-of s + (assoc s (rand-nth cgen/ednable-scalars) (rand-nth cgen/ednable-scalars))))) + +(defspec seq-and-iter-match-for-structs + identity + [^{:tag clojure.test-clojure.data-structures/gen-struct} s] + (seq-iter-match s s)) \ No newline at end of file From afa556860369b19e96d5f955c9bee778e454bb4c Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 21 Apr 2015 06:58:39 -0700 Subject: [PATCH 007/854] CLJ-1713 - inner range classes should be serializable Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LongRange.java | 2 +- test/clojure/test_clojure/serialization.clj | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/jvm/clojure/lang/LongRange.java b/src/jvm/clojure/lang/LongRange.java index c956157dda..55f2aaa87e 100644 --- a/src/jvm/clojure/lang/LongRange.java +++ b/src/jvm/clojure/lang/LongRange.java @@ -220,7 +220,7 @@ public void remove() { } } -private static class LongChunk implements IChunk { +private static class LongChunk implements IChunk, Serializable { final long start; final long step; final int count; diff --git a/test/clojure/test_clojure/serialization.clj b/test/clojure/test_clojure/serialization.clj index 6a4f8d561b..7a4c075bb5 100644 --- a/test/clojure/test_clojure/serialization.clj +++ b/test/clojure/test_clojure/serialization.clj @@ -95,7 +95,7 @@ (build-via-transient []) (build-via-transient {}) (build-via-transient #{}) - + ; array-seqs (seq (make-array Object 10)) (seq (make-array Boolean/TYPE 10)) @@ -113,7 +113,12 @@ ; misc seqs (seq "s11n") (range 50) - (rseq (apply sorted-set (reverse (range 100)))))) + (rseq (apply sorted-set (reverse (range 100)))) + + ;; partially realized chunked range + (let [r (range 50)] + (nth r 35) + r))) (deftest misc-serialization (are [v] (= v (-> v serialize deserialize)) From f087226ef6e913a43d4f87797495a48fbd99b263 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 24 Apr 2015 11:10:23 -0500 Subject: [PATCH 008/854] Changes for 1.7.0-beta2 Signed-off-by: Stuart Halloway --- changes.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/changes.md b/changes.md index 684bf7563a..ce7a0a2ee9 100644 --- a/changes.md +++ b/changes.md @@ -125,6 +125,9 @@ http://dev.clojure.org/display/design/Reader+Conditionals * [CLJ-1424](http://dev.clojure.org/jira/browse/CLJ-1424) * [CLJ-1685](http://dev.clojure.org/jira/browse/CLJ-1685) +* [CLJ-1698](http://dev.clojure.org/jira/browse/CLJ-1698) +* [CLJ-1699](http://dev.clojure.org/jira/browse/CLJ-1699) +* [CLJ-1700](http://dev.clojure.org/jira/browse/CLJ-1700) ### 1.3 Keyword and Symbol Construction @@ -210,6 +213,9 @@ eduction. * [CLJ-1669](http://dev.clojure.org/jira/browse/CLJ-1669) * [CLJ-1692](http://dev.clojure.org/jira/browse/CLJ-1692) * [CLJ-1694](http://dev.clojure.org/jira/browse/CLJ-1694) +* [CLJ-1711](http://dev.clojure.org/jira/browse/CLJ-1711) +* [CLJ-1709](http://dev.clojure.org/jira/browse/CLJ-1709) +* [CLJ-1713](http://dev.clojure.org/jira/browse/CLJ-1713) ### 1.7 Printing as data @@ -229,16 +235,22 @@ REPL but printing them to a stream will show a different form: user=> (/ 1 0) ArithmeticException Divide by zero clojure.lang.Numbers.divide (Numbers.java:158) user=> (println *e) - #error{:cause Divide by zero, - :via [{:type java.lang.ArithmeticException, - :message Divide by zero, - :at [clojure.lang.Numbers divide Numbers.java 158]}], - :trace - [[clojure.lang.Numbers divide Numbers.java 158] - [clojure.lang.Numbers divide Numbers.java 3808] - [user$eval5 invoke NO_SOURCE_FILE 3] - ;; elided ... - ]]} + #error { + :cause Divide by zero + :via + [{:type java.lang.ArithmeticException + :message Divide by zero + :at [clojure.lang.Numbers divide Numbers.java 158]}] + :trace + [[clojure.lang.Numbers divide Numbers.java 158] + [clojure.lang.Numbers divide Numbers.java 3808] + ;; ... elided frames + ]} + +Additionally, there is a new function available to obtain a Throwable as +map data: `Throwable->map`. + +* [CLJ-1703](http://dev.clojure.org/jira/browse/CLJ-1703) ## 2 Enhancements From 822a989a8aab860f9ab96e91c2bf2a28b12ddd43 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 24 Apr 2015 13:02:06 -0500 Subject: [PATCH 009/854] [maven-release-plugin] prepare release clojure-1.7.0-beta2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3b848f9c4..5a15cf70ab 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-master-SNAPSHOT + 1.7.0-beta2 http://clojure.org/ Clojure core environment and runtime library. From 4964194bae036dc2cddd3f9c03479657aeab6e8c Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 24 Apr 2015 13:02:06 -0500 Subject: [PATCH 010/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5a15cf70ab..f3b848f9c4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-beta2 + 1.7.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 4fc68310d90648e4bbb800aefa9472828ea4bcbd Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Thu, 22 Jan 2015 16:32:32 -0500 Subject: [PATCH 011/854] Use equals() instead of == for resolveSymbol Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index e3305eecf8..c9979a1226 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -368,7 +368,7 @@ static Symbol resolveSymbol(Symbol sym){ if(sym.ns != null) { Namespace ns = namespaceFor(sym); - if(ns == null || ns.name.name == sym.ns) + if(ns == null || (ns.name.name == null ? sym.ns == null : ns.name.name.equals(sym.ns))) return sym; return Symbol.intern(ns.name.name, sym.name); } From 49baf1e8ae394c9b98523a3601779a5468920ba1 Mon Sep 17 00:00:00 2001 From: Jason Wolfe Date: Sun, 21 Dec 2014 19:32:38 -0800 Subject: [PATCH 012/854] extend-protocol should emit `fn, not 'fn (fixes #1195) Signed-off-by: Stuart Halloway --- src/clj/clojure/core_deftype.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index 142371fb50..d0a16efc54 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -765,7 +765,7 @@ (defn- emit-impl [[p fs]] [p (zipmap (map #(-> % first keyword) fs) - (map #(cons 'fn (drop 1 %)) fs))]) + (map #(cons `fn (drop 1 %)) fs))]) (defn- emit-hinted-impl [c [p fs]] (let [hint (fn [specs] @@ -777,7 +777,7 @@ body)) specs)))] [p (zipmap (map #(-> % first name keyword) fs) - (map #(cons 'fn (hint (drop 1 %))) fs))])) + (map #(cons `fn (hint (drop 1 %))) fs))])) (defn- emit-extend-type [c specs] (let [impls (parse-impls specs)] From 01b4228483b2f48fade9038f970b267380d8ab0f Mon Sep 17 00:00:00 2001 From: Gary Fredericks Date: Sun, 26 Apr 2015 21:03:20 -0500 Subject: [PATCH 013/854] CLJ-1716: print ex-data on throwables Also added tests for the existing exception printing behavior, and the new behavior. Signed-off-by: Stuart Halloway --- src/clj/clojure/core_print.clj | 29 ++++++++++++++++++------ test/clojure/test_clojure/errors.clj | 22 ++++++++++++++++++ test/clojure/test_clojure/printer.clj | 32 +++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index f78594a8b7..21ae4d8eed 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -415,28 +415,43 @@ (defn Throwable->map [^Throwable o] (let [base (fn [^Throwable t] - {:type (class t) - :message (.getLocalizedMessage t) - :at (get (.getStackTrace t) 0)}) + (let [m {:type (class t) + :message (.getLocalizedMessage t) + :at (get (.getStackTrace t) 0)} + data (ex-data t)] + (if data + (assoc m :data data) + m))) via (loop [via [], ^Throwable t o] (if t (recur (conj via t) (.getCause t)) - via))] - {:cause (.getLocalizedMessage ^Throwable (last via)) + via)) + ^Throwable root (peek via) + m {:cause (.getLocalizedMessage root) :via (vec (map base via)) - :trace (vec (.getStackTrace (or ^Throwable (last via) o)))})) + :trace (vec (.getStackTrace ^Throwable (or root o)))} + data (ex-data root)] + (if data + (assoc m :data data) + m))) (defn- print-throwable [^Throwable o ^Writer w] (.write w "#error {\n :cause ") - (let [{:keys [cause via trace]} (Throwable->map o) + (let [{:keys [cause data via trace]} (Throwable->map o) print-via #(do (.write w "{:type ") (print-method (:type %) w) (.write w "\n :message ") (print-method (:message %) w) + (when-let [data (:data %)] + (.write w "\n :data ") + (print-method data w)) (.write w "\n :at ") (print-method (:at %) w) (.write w "}"))] (print-method cause w) + (when data + (.write w "\n :data ") + (print-method data w)) (when via (.write w "\n :via\n [") (when-let [fv (first via)] diff --git a/test/clojure/test_clojure/errors.clj b/test/clojure/test_clojure/errors.clj index 4eb9d2aa1e..ec8561a6ca 100644 --- a/test/clojure/test_clojure/errors.clj +++ b/test/clojure/test_clojure/errors.clj @@ -58,3 +58,25 @@ (catch Throwable t (is (= {:foo 1} (ex-data t))))) (is (nil? (ex-data (RuntimeException. "example non ex-data"))))) + +(deftest Throwable->map-test + (testing "base functionality" + (let [{:keys [cause via trace]} (Throwable->map + (Exception. "I am a string literal"))] + (is (= cause "I am a string literal")) + (is (= 1 (count via))) + (is (vector? via)) + (is (= ["I am a string literal"] (map :message via))))) + (testing "causes" + (let [{:keys [cause via trace]} (Throwable->map + (Exception. "I am not a number" + (Exception. "double two")))] + (is (= cause "double two")) + (is (= ["I am not a number" "double two"] + (map :message via))))) + (testing "ex-data" + (let [{[{:keys [data]}] :via + data-top-level :data} + (Throwable->map (ex-info "ex-info" + {:some "data"}))] + (is (= data data-top-level {:some "data"}))))) diff --git a/test/clojure/test_clojure/printer.clj b/test/clojure/test_clojure/printer.clj index ddaa83194b..30fd22153d 100644 --- a/test/clojure/test_clojure/printer.clj +++ b/test/clojure/test_clojure/printer.clj @@ -119,3 +119,35 @@ #'var-with-meta "#'clojure.test-clojure.printer/var-with-meta" #'var-with-type "#'clojure.test-clojure.printer/var-with-type")) +(defn ^:private ednize-stack-trace-element + [^StackTraceElement ste] + [(symbol (.getClassName ste)) + (symbol (.getMethodName ste)) + (.getFileName ste) + (.getLineNumber ste)]) + +(defn ^:private ednize-throwable-data + [throwable-data] + (-> throwable-data + (update :via (fn [vias] + (map (fn [via] + (-> via + (update :type #(symbol (.getName %))) + (update :at ednize-stack-trace-element))) + vias))) + (update :trace #(map ednize-stack-trace-element %)))) + +(deftest print-throwable + (binding [*data-readers* {'error identity}] + (are [e] (= (-> e Throwable->map ednize-throwable-data) + (-> e pr-str read-string)) + (Exception. "heyo") + (Throwable. "I can a throwable" + (Exception. "chain 1" + (Exception. "chan 2"))) + (ex-info "an ex-info" {:with "its" :data 29}) + (Exception. "outer" + (ex-info "an ex-info" {:with "data"} + (Error. "less outer" + (ex-info "the root" + {:with "even" :more 'data}))))))) From 676a7e4a2bdc9771e515e2d545e089f9e2c29e03 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 12 May 2015 10:01:23 -0500 Subject: [PATCH 014/854] CLJ-1727 Made range counting safe from overflow Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LongRange.java | 86 ++++++++++++++++++++----- test/clojure/test_clojure/sequences.clj | 33 ++++++++++ 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/src/jvm/clojure/lang/LongRange.java b/src/jvm/clojure/lang/LongRange.java index 55f2aaa87e..90629d90c2 100644 --- a/src/jvm/clojure/lang/LongRange.java +++ b/src/jvm/clojure/lang/LongRange.java @@ -116,13 +116,22 @@ public Object first() { public void forceChunk() { if(_chunk != null) return; - long nextStart = start + (step * CHUNK_SIZE); - if(boundsCheck.exceededBounds(nextStart)) { - int count = absCount(start, end, step); - _chunk = new LongChunk(start, step, count); - } else { + long count; + try { + count = rangeCount(start, end, step); + } catch(ArithmeticException e) { + // size of total range is > Long.MAX_VALUE so must step to count + // this only happens in pathological range cases like: + // (range -9223372036854775808 9223372036854775807 9223372036854775807) + count = steppingCount(start, end, step); + } + + if (count > CHUNK_SIZE) { // not last chunk + long nextStart = start + (step * CHUNK_SIZE); // cannot overflow, must be < end _chunk = new LongChunk(start, step, CHUNK_SIZE); _chunkNext = new LongRange(nextStart, end, step, boundsCheck); + } else { // last chunk + _chunk = new LongChunk(start, step, (int) count); // count must be <= CHUNK_SIZE } } @@ -155,17 +164,55 @@ public ISeq chunkedMore() { return _chunkNext; } -public int absCount(long start, long end, long step) { - double c = (double) (end - start) / step; - int ic = (int) c; - if(c > ic) - return ic + 1; - else - return ic; +// fallback count mechanism for pathological cases +// returns either exact count or CHUNK_SIZE+1 +long steppingCount(long start, long end, long step) { + long count = 1; + long s = start; + while(count <= CHUNK_SIZE) { + try { + s = Numbers.add(s, step); + if(boundsCheck.exceededBounds(s)) + break; + else + count++; + } catch(ArithmeticException e) { + break; + } + } + return count; +} + +// returns exact size of remaining items OR throws ArithmeticException for overflow case +long rangeCount(long start, long end, long step) { + // (1) count = ceiling ( (end - start) / step ) + // (2) ceiling(a/b) = (a+b+o)/b where o=-1 for positive stepping and +1 for negative stepping + // thus: count = end - start + step + o / step + return Numbers.add(Numbers.add(Numbers.minus(end, start), step), this.step > 0 ? -1 : 1) / step; } public int count() { - return absCount(start, end, step); + try { + long c = rangeCount(start, end, step); + if(c > Integer.MAX_VALUE) { + return Numbers.throwIntOverflow(); + } else { + return (int) c; + } + } catch(ArithmeticException e) { + // rare case from large range or step, fall back to iterating and counting + Iterator iter = this.iterator(); + long count = 0; + while(iter.hasNext()) { + iter.next(); + count++; + } + + if(count > Integer.MAX_VALUE) + return Numbers.throwIntOverflow(); + else + return (int)count; + } } public Object reduce(IFn f) { @@ -196,19 +243,26 @@ public Iterator iterator() { class LongRangeIterator implements Iterator { private long next; + private boolean hasNext; public LongRangeIterator() { this.next = start; + this.hasNext = true; } public boolean hasNext() { - return(! boundsCheck.exceededBounds(next)); + return hasNext; } public Object next() { - if (hasNext()) { + if (hasNext) { long ret = next; - next = next + step; + try { + next = Numbers.add(next, step); + hasNext = ! boundsCheck.exceededBounds(next); + } catch(ArithmeticException e) { + hasNext = false; + } return ret; } else { throw new NoSuchElementException(); diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj index f6a93fa184..7cd2d93b7c 100644 --- a/test/clojure/test_clojure/sequences.clj +++ b/test/clojure/test_clojure/sequences.clj @@ -1046,6 +1046,39 @@ (reduce + (iterator-seq (.iterator (range 100)))) 4950 (reduce + (iterator-seq (.iterator (range 0.0 100.0 1.0)))) 4950.0 )) +(defn unlimited-range-create [& args] + (let [[arg1 arg2 arg3] args] + (case (count args) + 1 (clojure.lang.Range/create arg1) + 2 (clojure.lang.Range/create arg1 arg2) + 3 (clojure.lang.Range/create arg1 arg2 arg3)))) + +(deftest test-longrange-corners + (let [lmax Long/MAX_VALUE + lmax-1 (- Long/MAX_VALUE 1) + lmax-2 (- Long/MAX_VALUE 2) + lmax-31 (- Long/MAX_VALUE 31) + lmax-32 (- Long/MAX_VALUE 32) + lmax-33 (- Long/MAX_VALUE 33) + lmin Long/MIN_VALUE + lmin+1 (+ Long/MIN_VALUE 1) + lmin+2 (+ Long/MIN_VALUE 2) + lmin+31 (+ Long/MIN_VALUE 31) + lmin+32 (+ Long/MIN_VALUE 32) + lmin+33 (+ Long/MIN_VALUE 33)] + (doseq [range-args [ [lmax-2 lmax] + [lmax-33 lmax] + [lmax-33 lmax-31] + [lmin+2 lmin -1] + [lmin+33 lmin -1] + [lmin+33 lmin+31 -1] + [lmin lmax lmax] + [lmax lmin lmin] + [-1 lmax lmax] + [1 lmin lmin]]] + (is (= (apply unlimited-range-create range-args) + (apply range range-args)) + (apply str "from (range " (concat (interpose " " range-args) ")")))))) (deftest test-empty? (are [x] (empty? x) From 61bc3523eebaade6a1d856f74330169eb26211ea Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 11 May 2015 02:10:33 -0500 Subject: [PATCH 015/854] CLJ-1728 - Make source work with vars containing reader conditionals Signed-off-by: Stuart Halloway --- src/clj/clojure/repl.clj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index 0547594c31..70ea94f5aa 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -149,10 +149,11 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) pbr (proxy [PushbackReader] [rdr] (read [] (let [i (proxy-super read)] (.append text (char i)) - i)))] + i))) + read-opts (if (.endsWith ^String filepath "cljc") {:read-cond :allow} {})] (if (= :unknown *read-eval*) (throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown.")) - (read (PushbackReader. pbr))) + (read read-opts (PushbackReader. pbr))) (str text))))))) (defmacro source From d91ffba5a5a40a8c3f8f010d69504bf93ae18636 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 8 May 2015 01:48:49 -0500 Subject: [PATCH 016/854] Restore IPending for iterate, cycle Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Cycle.java | 6 +++++- src/jvm/clojure/lang/Iterate.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/jvm/clojure/lang/Cycle.java b/src/jvm/clojure/lang/Cycle.java index 4f4d4167bd..3fae8afdb4 100644 --- a/src/jvm/clojure/lang/Cycle.java +++ b/src/jvm/clojure/lang/Cycle.java @@ -12,7 +12,7 @@ /* Alex Miller, Dec 5, 2014 */ -public class Cycle extends ASeq implements IReduce { +public class Cycle extends ASeq implements IReduce, IPending { private final ISeq all; // never null private final ISeq prev; @@ -48,6 +48,10 @@ private ISeq current() { return _current; } +public boolean isRealized() { + return _current != null; +} + public Object first(){ return current().first(); } diff --git a/src/jvm/clojure/lang/Iterate.java b/src/jvm/clojure/lang/Iterate.java index 2a739581bf..2502383260 100644 --- a/src/jvm/clojure/lang/Iterate.java +++ b/src/jvm/clojure/lang/Iterate.java @@ -12,7 +12,7 @@ /* Alex Miller, Dec 5, 2014 */ -public class Iterate extends ASeq implements IReduce { +public class Iterate extends ASeq implements IReduce, IPending { private static final Object UNREALIZED_SEED = new Object(); private final IFn f; // never null @@ -38,6 +38,10 @@ public static ISeq create(IFn f, Object seed){ return new Iterate(f, null, seed); } +public boolean isRealized() { + return _seed != UNREALIZED_SEED; +} + public Object first(){ if(_seed == UNREALIZED_SEED) { _seed = f.invoke(prevSeed); From 952c54fa8838bd0d80c78221f25733ff48b7ee45 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 4 May 2015 13:32:02 -0500 Subject: [PATCH 017/854] CLJ-1723 Expanding transducer fails if adding nil values Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/TransformerIterator.java | 4 ++-- test/clojure/test_clojure/transducers.clj | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/jvm/clojure/lang/TransformerIterator.java b/src/jvm/clojure/lang/TransformerIterator.java index 56c34e41bc..07735168c8 100644 --- a/src/jvm/clojure/lang/TransformerIterator.java +++ b/src/jvm/clojure/lang/TransformerIterator.java @@ -16,7 +16,7 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.LinkedList; public class TransformerIterator implements Iterator { @@ -169,7 +169,7 @@ public String toString() { } private static class Many implements Buffer { - private final Queue vals = new ConcurrentLinkedQueue(); + private final Queue vals = new LinkedList(); public Many(Object o1, Object o2) { vals.add(o1); diff --git a/test/clojure/test_clojure/transducers.clj b/test/clojure/test_clojure/transducers.clj index 48de5076e2..21effc4770 100644 --- a/test/clojure/test_clojure/transducers.clj +++ b/test/clojure/test_clojure/transducers.clj @@ -347,7 +347,10 @@ (is (= ["drib" "god" "hsif" "kravdraa" "tac"] (->> ["cat" "dog" "fish" "bird" "aardvark"] (eduction (map clojure.string/reverse)) - (sort-by first)))))) + (sort-by first))))) + (testing "expanding transducer with nils" + (is (= '(1 2 3 nil 4 5 6 nil) + (eduction cat [[1 2 3 nil] [4 5 6 nil]]))))) (deftest test-eduction-completion (testing "eduction completes inner xformed reducing fn" From ecaa6f662eebb760db78fd47a05153b8d110135e Mon Sep 17 00:00:00 2001 From: puredanger Date: Tue, 12 May 2015 15:44:12 -0500 Subject: [PATCH 018/854] 1.7.0-beta3 changes Signed-off-by: Stuart Halloway --- changes.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/changes.md b/changes.md index ce7a0a2ee9..f21035d31d 100644 --- a/changes.md +++ b/changes.md @@ -71,6 +71,7 @@ Some related issues addressed during development: * [CLJ-1635](http://dev.clojure.org/jira/browse/CLJ-1635) * [CLJ-1683](http://dev.clojure.org/jira/browse/CLJ-1683) * [CLJ-1669](http://dev.clojure.org/jira/browse/CLJ-1669) +* [CLJ-1723](http://dev.clojure.org/jira/browse/CLJ-1723) ### 1.2 Reader Conditionals @@ -128,6 +129,7 @@ http://dev.clojure.org/display/design/Reader+Conditionals * [CLJ-1698](http://dev.clojure.org/jira/browse/CLJ-1698) * [CLJ-1699](http://dev.clojure.org/jira/browse/CLJ-1699) * [CLJ-1700](http://dev.clojure.org/jira/browse/CLJ-1700) +* [CLJ-1728](http://dev.clojure.org/jira/browse/CLJ-1728) ### 1.3 Keyword and Symbol Construction @@ -216,6 +218,8 @@ eduction. * [CLJ-1711](http://dev.clojure.org/jira/browse/CLJ-1711) * [CLJ-1709](http://dev.clojure.org/jira/browse/CLJ-1709) * [CLJ-1713](http://dev.clojure.org/jira/browse/CLJ-1713) +* [CLJ-1726](http://dev.clojure.org/jira/browse/CLJ-1726) +* [CLJ-1727](http://dev.clojure.org/jira/browse/CLJ-1727) ### 1.7 Printing as data @@ -251,6 +255,7 @@ Additionally, there is a new function available to obtain a Throwable as map data: `Throwable->map`. * [CLJ-1703](http://dev.clojure.org/jira/browse/CLJ-1703) +* [CLJ-1716](http://dev.clojure.org/jira/browse/CLJ-1716) ## 2 Enhancements @@ -380,7 +385,10 @@ map data: `Throwable->map`. Fix regression from CLJ-1546 removed PersistentVector.create(List) method * [CLJ-1681](http://dev.clojure.org/jira/browse/CLJ-1681) Fix regression from CLJ-1248 (1.6) in reflection warning with literal nil argument - +* [CLJ-1648](http://dev.clojure.org/jira/browse/CLJ-1648) + Use equals() instead of == when resolving Symbol +* [CLJ-1195](http://dev.clojure.org/jira/browse/CLJ-1195) + emit-hinted-impl expands to ns-qualified invocation of fn # Changes to Clojure in Version 1.6 From 221dba70d2b4bc908939850bce0b13f5f33b7a33 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 13 May 2015 07:57:31 -0500 Subject: [PATCH 019/854] [maven-release-plugin] prepare release clojure-1.7.0-beta3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3b848f9c4..90ab90ad95 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-master-SNAPSHOT + 1.7.0-beta3 http://clojure.org/ Clojure core environment and runtime library. From 3aaa4101b3b063c47bd968d3c7d7a9584aab6b8f Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 13 May 2015 07:57:31 -0500 Subject: [PATCH 020/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 90ab90ad95..f3b848f9c4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-beta3 + 1.7.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 8c2083448742b0c8701c83b43138d55e455c45ec Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 19 May 2015 11:20:26 -0500 Subject: [PATCH 021/854] CLJ-1706 Make top level reader conditional splicing an error Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LispReader.java | 47 ++++++++++++++++++--------- test/clojure/test_clojure/reader.cljc | 5 ++- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index 3a1766bf6a..67876ad5ed 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -192,11 +192,19 @@ static public Object read(PushbackReader r, boolean eofIsError, Object eofValue, static public Object read(PushbackReader r, boolean eofIsError, Object eofValue, boolean isRecursive, Object opts) { - return read(r, eofIsError, eofValue, null, null, isRecursive, opts, new LinkedList()); + // start with pendingForms null as reader conditional splicing is not allowed at top level + return read(r, eofIsError, eofValue, null, null, isRecursive, opts, null); } static private Object read(PushbackReader r, boolean eofIsError, Object eofValue, boolean isRecursive, Object opts, Object pendingForms) { - return read(r, eofIsError, eofValue, null, null, isRecursive, opts, pendingForms); + return read(r, eofIsError, eofValue, null, null, isRecursive, opts, ensurePending(pendingForms)); +} + +static private Object ensurePending(Object pendingForms) { + if(pendingForms == null) + return new LinkedList(); + else + return pendingForms; } static private Object installPlatformFeature(Object opts) { @@ -584,7 +592,7 @@ public Object invoke(Object reader, Object semicolon, Object opts, Object pendin public static class DiscardReader extends AFn{ public Object invoke(Object reader, Object underscore, Object opts, Object pendingForms) { PushbackReader r = (PushbackReader) reader; - read(r, true, null, true, opts, pendingForms); + read(r, true, null, true, opts, ensurePending(pendingForms)); return r; } } @@ -598,7 +606,7 @@ public WrappingReader(Symbol sym){ public Object invoke(Object reader, Object quote, Object opts, Object pendingForms) { PushbackReader r = (PushbackReader) reader; - Object o = read(r, true, null, true, opts, pendingForms); + Object o = read(r, true, null, true, opts, ensurePending(pendingForms)); return RT.list(sym, o); } @@ -618,7 +626,7 @@ public Object invoke(Object reader, Object quote, Object opts, Object pendingFor " is deprecated; use " + sym.getName() + " instead"); PushbackReader r = (PushbackReader) reader; - Object o = read(r, true, null, true, opts, pendingForms); + Object o = read(r, true, null, true, opts, ensurePending(pendingForms)); return RT.list(sym, o); } @@ -627,7 +635,7 @@ public Object invoke(Object reader, Object quote, Object opts, Object pendingFor public static class VarReader extends AFn{ public Object invoke(Object reader, Object quote, Object opts, Object pendingForms) { PushbackReader r = (PushbackReader) reader; - Object o = read(r, true, null, true, opts, pendingForms); + Object o = read(r, true, null, true, opts, ensurePending(pendingForms)); // if(o instanceof Symbol) // { // Object v = Compiler.maybeResolveIn(Compiler.currentNS(), (Symbol) o); @@ -672,6 +680,7 @@ public Object invoke(Object reader, Object hash, Object opts, Object pendingForm // Try the ctor reader first if(fn == null) { unread((PushbackReader) reader, ch); + pendingForms = ensurePending(pendingForms); Object result = ctorReader.invoke(reader, ch, opts, pendingForms); if(result != null) @@ -697,7 +706,7 @@ public Object invoke(Object reader, Object lparen, Object opts, Object pendingFo Var.pushThreadBindings( RT.map(ARG_ENV, PersistentTreeMap.EMPTY)); unread(r, '('); - Object form = read(r, true, null, true, opts, pendingForms); + Object form = read(r, true, null, true, opts, ensurePending(pendingForms)); PersistentVector args = PersistentVector.EMPTY; PersistentTreeMap argsyms = (PersistentTreeMap) ARG_ENV.deref(); @@ -760,7 +769,7 @@ public Object invoke(Object reader, Object pct, Object opts, Object pendingForms { return registerArg(1); } - Object n = read(r, true, null, true, opts, pendingForms); + Object n = read(r, true, null, true, opts, ensurePending(pendingForms)); if(n.equals(Compiler._AMP_)) return registerArg(-1); if(!(n instanceof Number)) @@ -779,6 +788,7 @@ public Object invoke(Object reader, Object caret, Object opts, Object pendingFor line = ((LineNumberingPushbackReader) r).getLineNumber(); column = ((LineNumberingPushbackReader) r).getColumnNumber()-1; } + pendingForms = ensurePending(pendingForms); Object meta = read(r, true, null, true, opts, pendingForms); if(meta instanceof Symbol || meta instanceof String) meta = RT.map(RT.TAG_KEY, meta); @@ -820,7 +830,7 @@ public Object invoke(Object reader, Object backquote, Object opts, Object pendin Var.pushThreadBindings( RT.map(GENSYM_ENV, PersistentHashMap.EMPTY)); - Object form = read(r, true, null, true, opts, pendingForms); + Object form = read(r, true, null, true, opts, ensurePending(pendingForms)); return syntaxQuote(form); } finally @@ -967,6 +977,7 @@ public Object invoke(Object reader, Object comma, Object opts, Object pendingFor int ch = read1(r); if(ch == -1) throw Util.runtimeException("EOF while reading character"); + pendingForms = ensurePending(pendingForms); if(ch == '@') { Object o = read(r, true, null, true, opts, pendingForms); @@ -1035,7 +1046,7 @@ public Object invoke(Object reader, Object leftparen, Object opts, Object pendin line = ((LineNumberingPushbackReader) r).getLineNumber(); column = ((LineNumberingPushbackReader) r).getColumnNumber()-1; } - List list = readDelimitedList(')', r, true, opts, pendingForms); + List list = readDelimitedList(')', r, true, opts, ensurePending(pendingForms)); if(list.isEmpty()) return PersistentList.EMPTY; IObj s = (IObj) PersistentList.create(list); @@ -1090,7 +1101,7 @@ public Object invoke(Object reader, Object eq, Object opts, Object pendingForms) } PushbackReader r = (PushbackReader) reader; - Object o = read(r, true, null, true, opts, pendingForms); + Object o = read(r, true, null, true, opts, ensurePending(pendingForms)); if(o instanceof Symbol) { return RT.classForName(o.toString()); @@ -1136,7 +1147,7 @@ else if(o instanceof IPersistentList) public static class VectorReader extends AFn{ public Object invoke(Object reader, Object leftparen, Object opts, Object pendingForms) { PushbackReader r = (PushbackReader) reader; - return LazilyPersistentVector.create(readDelimitedList(']', r, true, opts, pendingForms)); + return LazilyPersistentVector.create(readDelimitedList(']', r, true, opts, ensurePending(pendingForms))); } } @@ -1144,7 +1155,7 @@ public Object invoke(Object reader, Object leftparen, Object opts, Object pendin public static class MapReader extends AFn{ public Object invoke(Object reader, Object leftparen, Object opts, Object pendingForms) { PushbackReader r = (PushbackReader) reader; - Object[] a = readDelimitedList('}', r, true, opts, pendingForms).toArray(); + Object[] a = readDelimitedList('}', r, true, opts, ensurePending(pendingForms)).toArray(); if((a.length & 1) == 1) throw Util.runtimeException("Map literal must contain an even number of forms"); return RT.map(a); @@ -1155,7 +1166,7 @@ public Object invoke(Object reader, Object leftparen, Object opts, Object pendin public static class SetReader extends AFn{ public Object invoke(Object reader, Object leftbracket, Object opts, Object pendingForms) { PushbackReader r = (PushbackReader) reader; - return PersistentHashSet.createWithCheck(readDelimitedList('}', r, true, opts, pendingForms)); + return PersistentHashSet.createWithCheck(readDelimitedList('}', r, true, opts, ensurePending(pendingForms))); } } @@ -1204,6 +1215,7 @@ public static List readDelimitedList(char delim, PushbackReader r, boolean isRec public static class CtorReader extends AFn{ public Object invoke(Object reader, Object firstChar, Object opts, Object pendingForms){ PushbackReader r = (PushbackReader) reader; + pendingForms = ensurePending(pendingForms); Object name = read(r, true, null, false, opts, pendingForms); if (!(name instanceof Symbol)) throw new RuntimeException("Reader tag must be a symbol"); @@ -1320,6 +1332,8 @@ public static boolean hasFeature(Object feature, Object opts) { public static Object readCondDelimited(PushbackReader r, boolean splicing, Object opts, Object pendingForms) { Object result = null; Object form; // The most recently ready form + boolean toplevel = (pendingForms == null); + pendingForms = ensurePending(pendingForms); final int firstline = (r instanceof LineNumberingPushbackReader) ? @@ -1391,6 +1405,9 @@ public static Object readCondDelimited(PushbackReader r, boolean splicing, Objec if(! (result instanceof List)) throw Util.runtimeException("Spliced form list in read-cond-splicing must implement java.util.List"); + if(toplevel) + throw Util.runtimeException("Reader conditional splicing not allowed at the top level."); + ((List)pendingForms).addAll(0, (List)result); return r; @@ -1435,7 +1452,7 @@ public Object invoke(Object reader, Object mode, Object opts, Object pendingForm if (isPreserveReadCond(opts)) { IFn listReader = getMacro(ch); // should always be a list - Object form = listReader.invoke(r, ch, opts, pendingForms); + Object form = listReader.invoke(r, ch, opts, ensurePending(pendingForms)); return ReaderConditional.create(form, splicing); } else { diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc index f946d4bb35..63774a86dc 100644 --- a/test/clojure/test_clojure/reader.cljc +++ b/test/clojure/test_clojure/reader.cljc @@ -692,7 +692,10 @@ (is (thrown-with-msg? RuntimeException #"is reserved" (read-string {:read-cond :allow} "#?@(:foo :a :else :b)"))) (is (thrown-with-msg? RuntimeException #"must be a list" (read-string {:read-cond :allow} "#?[:foo :a :else :b]"))) (is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string {:read-cond :BOGUS} "#?[:clj :a :default nil]"))) - (is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string "#?[:clj :a :default nil]")))) + (is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string "#?[:clj :a :default nil]"))) + (is (thrown-with-msg? RuntimeException #"Reader conditional splicing not allowed at the top level" (read-string {:read-cond :allow} "#?@(:clj [1 2])"))) + (is (thrown-with-msg? RuntimeException #"Reader conditional splicing not allowed at the top level" (read-string {:read-cond :allow} "#?@(:clj [1])"))) + (is (thrown-with-msg? RuntimeException #"Reader conditional splicing not allowed at the top level" (read-string {:read-cond :allow} "#?@(:clj []) 1")))) (testing "clj-1698-regression" (let [opts {:features #{:clj} :read-cond :allow}] (is (= 1 (read-string opts "#?(:cljs {'a 1 'b 2} :clj 1)"))) From 33f01c1e295d6152e2931335d458d041ac269701 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 21 May 2015 11:07:25 -0500 Subject: [PATCH 022/854] [maven-release-plugin] prepare release clojure-1.7.0-RC1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3b848f9c4..7be673f408 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-master-SNAPSHOT + 1.7.0-RC1 http://clojure.org/ Clojure core environment and runtime library. From 41af6b24dd5be8bd62dc2b463bc53b55e18cd1e5 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 21 May 2015 11:07:25 -0500 Subject: [PATCH 023/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7be673f408..f3b848f9c4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-RC1 + 1.7.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 9d70bc1051ec8117df6436e07474c586ea9e85b0 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 5 Jun 2015 16:27:19 -0500 Subject: [PATCH 024/854] CLJ-1745 Revert wrapping of macro exceptions in CompilerException from CLJ-1169 Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 10 ---------- test/clojure/test_clojure/control.clj | 2 +- test/clojure/test_clojure/errors.clj | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index c9979a1226..033967a9dc 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6635,16 +6635,6 @@ public static Object macroexpand1(Object x) { // hide the 2 extra params for a macro throw new ArityException(e.actual - 2, e.name); } - catch(Throwable e) - { - if(!(e instanceof CompilerException)) { - Integer line = (Integer) LINE.deref(); - Integer column = (Integer) COLUMN.deref(); - String source = (String) SOURCE.deref(); - throw new CompilerException(source, line, column, e); - } else - throw (CompilerException) e; - } } else { diff --git a/test/clojure/test_clojure/control.clj b/test/clojure/test_clojure/control.clj index 992b087224..141cdf676a 100644 --- a/test/clojure/test_clojure/control.clj +++ b/test/clojure/test_clojure/control.clj @@ -373,7 +373,7 @@ :a (char 97))) (testing "test error on duplicate test constants" (is (thrown-with-msg? - clojure.lang.Compiler$CompilerException + IllegalArgumentException #"Duplicate case test constant: 1" (eval `(case 0 1 :x 1 :y))))) (testing "test correct behaviour on Number truncation" diff --git a/test/clojure/test_clojure/errors.clj b/test/clojure/test_clojure/errors.clj index ec8561a6ca..ce457a79eb 100644 --- a/test/clojure/test_clojure/errors.clj +++ b/test/clojure/test_clojure/errors.clj @@ -48,7 +48,7 @@ ["let .* in %s:\\d+" '(let [a])] ["let .* in %s:\\d+" '(let (a))] ["renamed-with-open .* in %s:\\d+" '(renamed-with-open [a])]]] - (is (thrown-with-msg? clojure.lang.Compiler$CompilerException + (is (thrown-with-msg? IllegalArgumentException (re-pattern (format msg-regex-str *ns*)) (macroexpand form))))) From bdc752a7fefff5e63e0847836ae5e6d95f971c37 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 17 Jun 2015 09:33:19 -0500 Subject: [PATCH 025/854] CLJ-1738 - Clarify iterator-seq expectations Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 9097e00207..a18543c9fb 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -128,7 +128,9 @@ :doc "Returns a seq on the collection. If the collection is empty, returns nil. (seq nil) returns nil. seq also works on Strings, native Java arrays (of reference types) and any objects - that implement Iterable." + that implement Iterable. Note that seqs cache values, thus seq + should not be used on any Iterable whose iterator repeatedly + returns the same mutable object." :tag clojure.lang.ISeq :added "1.0" :static true} @@ -5483,7 +5485,9 @@ (defn iterator-seq "Returns a seq on a java.util.Iterator. Note that most collections - providing iterators implement Iterable and thus support seq directly." + providing iterators implement Iterable and thus support seq directly. + Seqs cache values, thus iterator-seq should not be used on any + iterator that repeatedly returns the same mutable object." {:added "1.0" :static true} [iter] From e72d0435b3edf6be26f402561dd261edcc198276 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 22 May 2015 09:30:00 -0500 Subject: [PATCH 026/854] CLJ-1735 - Add docstring for Throwable->map Signed-off-by: Stuart Halloway --- src/clj/clojure/core_print.clj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 21ae4d8eed..12d8354b09 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -413,7 +413,10 @@ (defmethod print-method StackTraceElement [^StackTraceElement o ^Writer w] (print-method [(symbol (.getClassName o)) (symbol (.getMethodName o)) (.getFileName o) (.getLineNumber o)] w)) -(defn Throwable->map [^Throwable o] +(defn Throwable->map + "Constructs a data representation for a Throwable." + {:added "1.7"} + [^Throwable o] (let [base (fn [^Throwable t] (let [m {:type (class t) :message (.getLocalizedMessage t) From 9b8fe75d4e5d817c0ad9a45f9a3002e85eb9cd8f Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 22 May 2015 12:21:21 -0500 Subject: [PATCH 027/854] 1.7.0-RC2 changelog updates Signed-off-by: Stuart Halloway --- changes.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/changes.md b/changes.md index f21035d31d..a5d13f40ce 100644 --- a/changes.md +++ b/changes.md @@ -16,7 +16,6 @@ Many existing sequence functions now have a new arity (one fewer argument than before). This arity will return a transducer that represents the same logic but is independent of lazy sequence processing. Functions included are: -* conj (conjs to []) * map * mapcat * filter @@ -38,7 +37,7 @@ logic but is independent of lazy sequence processing. Functions included are: Additionally some new transducer functions have been added: * cat - concatenates the contents of each input -* de-dupe - removes consecutive duplicated values +* dedupe - removes consecutive duplicated values * random-sample - returns items from coll with random probability And this function can be used to make completing transforms: @@ -51,12 +50,12 @@ transducers in different ways: * sequence - takes a transformation and a coll and produces a lazy seq * transduce - reduce with a transformation (eager) * eduction - returns a reducible/iterable of applications of the transducer to items in coll. Applications are re-performed with every reduce/iterator. -* run! - run the transformation for side effects on the collection There have been a number of internal changes to support transducers: * volatiles - there are a new set of functions (volatile!, vswap!, vreset!, volatile?) to create and use volatile "boxes" to hold state in stateful transducers. Volatiles are faster than atoms but give up atomicity guarantees so should only be used with thread isolation. * array iterators - added support for iterators over arrays +* conj can be used as a reducing function and will conj to [] Some related issues addressed during development: * [CLJ-1511](http://dev.clojure.org/jira/browse/CLJ-1511) @@ -88,13 +87,14 @@ prior to .cljc. A new reader form can be used to specify "reader conditional" code in cljc files (and *only* cljc files). Each platform defines a feature identifying the platform (:clj, :cljs, :cljr). The reader conditional -specifies code that is read conditionally based on the feature/ +specifies code that is read conditionally based on the feature. The +REPL also allows reader conditionals. Form #? takes a list of alternating feature and expression. These are checked like cond and the selected expression is read and returned. Other -branches are unread. If no branch is selected, the reader reads nothing -(not nil, but literally as if reading ""). An optional ":default" branch -can be used as a fallthrough. +branches are read but skipped. If no branch is selected, the reader reads +nothing (not nil, but literally as if reading no form). An optional +`:default` branch can be used as a fallthrough. Reader conditional with 2 features and a default: @@ -104,14 +104,14 @@ Reader conditional with 2 features and a default: There is also a reader conditional splicing form. The evaluated expression should be sequential and will be spliced into the surrounded code, similar -to unqoute-splicing. +to unquote-splicing. For example: [1 2 #?@(:clj [3 4] :cljs [5 6])] This form would read as [1 2 3 4] on Clojure, [1 2 5 6] on ClojureScript, -and [1 2] on any other platform. +and [1 2] on any other platform. Splicing is not allowed at the top level. Additionally, the reader can now be invoked with options for the features to use and how to interpret reader conditionals. By default, reader conditionals @@ -130,6 +130,7 @@ http://dev.clojure.org/display/design/Reader+Conditionals * [CLJ-1699](http://dev.clojure.org/jira/browse/CLJ-1699) * [CLJ-1700](http://dev.clojure.org/jira/browse/CLJ-1700) * [CLJ-1728](http://dev.clojure.org/jira/browse/CLJ-1728) +* [CLJ-1706](http://dev.clojure.org/jira/browse/CLJ-1706) ### 1.3 Keyword and Symbol Construction @@ -256,6 +257,15 @@ map data: `Throwable->map`. * [CLJ-1703](http://dev.clojure.org/jira/browse/CLJ-1703) * [CLJ-1716](http://dev.clojure.org/jira/browse/CLJ-1716) +* [CLJ-1735](http://dev.clojure.org/jira/browse/CLJ-1735) + +### 1.8 run! + +run! is a new function that takes a side effect reducing function and runs +it for all items in a collection via reduce. The accumulator is ignored and +nil is returned. + + (run! println (range 10)) ## 2 Enhancements From f6a90ff2931cec35cca0ca7cf7afe90ab99e3161 Mon Sep 17 00:00:00 2001 From: Gary Fredericks Date: Mon, 1 Jun 2015 19:18:48 -0500 Subject: [PATCH 028/854] CLJ-1237: Eliminate reduce's unbounded stack Reduce had two different codepaths for consuming unbounded stack, due to endlessly checking for optimized-implementation opportunities. This primarily manifested when reducing a sequence that had many intermittent chunked portions. Changed the implementations for chunked seqs and generic seqs so that, when they stumble upon a different sort of seq than the one they're in the middle of reducing, they will do one last check for IReduceInit and else bail to a completely naive reduce. Signed-off-by: Stuart Halloway --- src/clj/clojure/core/protocols.clj | 25 ++++++++++++++++++++++--- test/clojure/test_clojure/sequences.clj | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core/protocols.clj b/src/clj/clojure/core/protocols.clj index bc83a78e64..5c68ba359d 100644 --- a/src/clj/clojure/core/protocols.clj +++ b/src/clj/clojure/core/protocols.clj @@ -52,6 +52,26 @@ (recur ret))) ret))))) +(defn- naive-seq-reduce + "Reduces a seq, ignoring any opportunities to switch to a more + specialized implementation." + [s f val] + (loop [s (seq s) + val val] + (if s + (let [ret (f val (first s))] + (if (reduced? ret) + @ret + (recur (next s) ret))) + val))) + +(defn- interface-or-naive-reduce + "Reduces via IReduceInit if possible, else naively." + [coll f val] + (if (instance? clojure.lang.IReduceInit coll) + (.reduce ^clojure.lang.IReduceInit coll f val) + (naive-seq-reduce coll f val))) + (extend-protocol CollReduce nil (coll-reduce @@ -119,7 +139,7 @@ (recur (chunk-next s) f ret))) - (coll-reduce s f val)) + (interface-or-naive-reduce s f val)) val)) clojure.lang.StringSeq @@ -143,13 +163,12 @@ f f val val] (if-let [s (seq s)] - ;; roll over to faster implementation if underlying seq changes type (if (identical? (class s) cls) (let [ret (f val (first s))] (if (reduced? ret) @ret (recur cls (next s) f ret))) - (coll-reduce s f val)) + (interface-or-naive-reduce s f val)) val)))) (defprotocol IKVReduce diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj index 7cd2d93b7c..cf696f9da1 100644 --- a/test/clojure/test_clojure/sequences.clj +++ b/test/clojure/test_clojure/sequences.clj @@ -90,6 +90,18 @@ (reduce f start (range 5))))] (is (= [0 1 2 3 4] (into [] iri))))) +;; CLJ-1237 regression test +(deftest reduce-with-varying-impls + (is (= 1000000 + (->> (repeat 500000 (cons 1 [1])) + (apply concat) + (reduce +)))) + + (is (= 4500000 + (->> (range 100000) + (mapcat (fn [_] (java.util.ArrayList. (range 10)))) + (reduce +))))) + (deftest test-equality ; lazy sequences (are [x y] (= x y) From 97c1210713cbbe0bacbb59004a91e25515d74ab6 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 17 Jun 2015 12:12:28 -0500 Subject: [PATCH 029/854] Add compatibility notes for 1.7 Signed-off-by: Stuart Halloway --- changes.md | 77 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/changes.md b/changes.md index a5d13f40ce..dc1d754aa9 100644 --- a/changes.md +++ b/changes.md @@ -2,9 +2,50 @@ # Changes to Clojure in Version 1.7 -## 1 New and Improved Features +## 1 Compatibility Notes -### 1.1 Transducers +Please be aware of the following issues when upgrading to Clojure 1.7. + +### Seqs on Java iterators that return the same mutating object + +Seqs are fundamentally incompatible with Java iterators that return +the same mutating object on every call to next(). Some Clojure +libraries incorrectly rely on calling seq on such iterators. + +In 1.7, iterator-seqs are chunked, which will cause many of these +incorrect usages to return incorrect results immediately. + +The `seq` and `iterator-seq` docstrings have been updated to include +an explicit warning. Libraries that incorrectly use `seq` and +`iterator-seq` will need to be fixed before running against 1.7. + +* [CLJ-1669](http://dev.clojure.org/jira/browse/CLJ-1669) +* [CLJ-1738](http://dev.clojure.org/jira/browse/CLJ-1738) + +### Thread owner check removed on transients + +Prior to Clojure 1.7, transients would allow modification only from the +thread that created the transient. This check has been removed. It is +still a requirement that transients should be updated by only a single +thread at a time. + +This constraint was relaxed to allow transients to be used in cases where +code is multiplexed across multiple threads in a pool (such as go blocks +in core.async). + +### keys/vals require custom map type to implement Iterable + +Invoking `keys` or `vals` on a custom map type that implements IPersistentMap +will now use the Iterable iterator() method instead of accessing entries +via the seq of the map. There have been no changes in the type hierarchy +(IPersistentMap has always extended Iterable) but former map-like instances +may have skipped implementing this method in the past. + +* [CLJ-1602](http://dev.clojure.org/jira/browse/CLJ-1602) + +## 2 New and Improved Features + +### 2.1 Transducers Transducers is a new way to decouple algorithmic transformations from their application in different contexts. Transducers are functions that transform @@ -72,7 +113,7 @@ Some related issues addressed during development: * [CLJ-1669](http://dev.clojure.org/jira/browse/CLJ-1669) * [CLJ-1723](http://dev.clojure.org/jira/browse/CLJ-1723) -### 1.2 Reader Conditionals +### 2.2 Reader Conditionals Reader Conditionals are a new capability to support portable code that can run on multiple Clojure platforms with only small changes. In @@ -132,7 +173,7 @@ http://dev.clojure.org/display/design/Reader+Conditionals * [CLJ-1728](http://dev.clojure.org/jira/browse/CLJ-1728) * [CLJ-1706](http://dev.clojure.org/jira/browse/CLJ-1706) -### 1.3 Keyword and Symbol Construction +### 2.3 Keyword and Symbol Construction In response to issues raised in [CLJ-1439](http://dev.clojure.org/jira/browse/CLJ-1439), several changes have been made in symbol and keyword construction: @@ -144,7 +185,7 @@ in a performance increase. 2) Keywords are cached and keyword construction includes a cache check. A change was made to only clear the cache reference queue when there is a cache miss. -### 1.4 Warn on Boxed Math +### 2.4 Warn on Boxed Math One source of performance issues is the (unintended) use of arithmetic operations on boxed numbers. To make detecting the presence of boxed math easier, a warning will now @@ -168,7 +209,7 @@ Example use: * [CLJ-1535](http://dev.clojure.org/jira/browse/CLJ-1535) * [CLJ-1642](http://dev.clojure.org/jira/browse/CLJ-1642) -### 1.5 update - like update-in for first level +### 2.5 update - like update-in for first level `update` is a new function that is like update-in specifically for first-level keys: @@ -185,7 +226,7 @@ Example use: * [CLJ-1251](http://dev.clojure.org/jira/browse/CLJ-1251) -### 1.6 Faster reduce and iterator paths +### 2.6 Faster reduce and iterator paths Several important Clojure functions now return sequences that also contain fast reduce() (or in some cases iterator()) paths. In many @@ -222,7 +263,7 @@ eduction. * [CLJ-1726](http://dev.clojure.org/jira/browse/CLJ-1726) * [CLJ-1727](http://dev.clojure.org/jira/browse/CLJ-1727) -### 1.7 Printing as data +### 2.7 Printing as data There have been enhancements in how the REPL prints values without a print-method, specifically Throwable and the fallthrough Object case. @@ -259,7 +300,7 @@ map data: `Throwable->map`. * [CLJ-1716](http://dev.clojure.org/jira/browse/CLJ-1716) * [CLJ-1735](http://dev.clojure.org/jira/browse/CLJ-1735) -### 1.8 run! +### 2.8 run! run! is a new function that takes a side effect reducing function and runs it for all items in a collection via reduce. The accumulator is ignored and @@ -267,18 +308,16 @@ nil is returned. (run! println (range 10)) -## 2 Enhancements +## 3 Enhancements -### 2.1 Error messages +### 3.1 Error messages * [CLJ-1261](http://dev.clojure.org/jira/browse/CLJ-1261) Invalid defrecord results in exception attributed to consuming ns instead of defrecord ns -* [CLJ-1169](http://dev.clojure.org/jira/browse/CLJ-1169) - Report line,column, and source in defmacro errors * [CLJ-1297](http://dev.clojure.org/jira/browse/CLJ-1297) Give more specific hint if namespace with "-" not found to check file uses "_" -### 2.2 Documentation strings +### 3.2 Documentation strings * [CLJ-1417](http://dev.clojure.org/jira/browse/CLJ-1417) clojure.java.io/input-stream has incorrect docstring @@ -292,8 +331,10 @@ nil is returned. Fix typo in deftype docstring * [CLJ-1478](http://dev.clojure.org/jira/browse/CLJ-1378) Fix typo in clojure.main usage +* [CLJ-1738](http://dev.clojure.org/jira/browse/CLJ-1738) + Clarify usage on Java iterators in seq and iterator-seq -### 2.3 Performance +### 3.3 Performance * [CLJ-1430](http://dev.clojure.org/jira/browse/CLJ-1430) Improve performance of partial with more unrolling @@ -310,7 +351,7 @@ nil is returned. * [CLJ-1695](http://dev.clojure.org/jira/browse/CLJ-1695) Fixed reflection call in variadic vector-of constructor -### 2.4 Other enhancements +### 3.4 Other enhancements * [CLJ-1191](http://dev.clojure.org/jira/browse/CLJ-1191) Improve apropos to show some indication of namespace of symbols found @@ -341,7 +382,7 @@ nil is returned. * [CLJ-1683](http://dev.clojure.org/jira/browse/CLJ-1683) Change reduce tests to better catch reduce without init bugs -## 3 Bug Fixes +## 4 Bug Fixes * [CLJ-1362](http://dev.clojure.org/jira/browse/CLJ-1362) Reduce broken on some primitive vectors @@ -399,6 +440,8 @@ nil is returned. Use equals() instead of == when resolving Symbol * [CLJ-1195](http://dev.clojure.org/jira/browse/CLJ-1195) emit-hinted-impl expands to ns-qualified invocation of fn +* [CLJ-1237](http://dev.clojure.org/jira/browse/CLJ-1237) + reduce of sequence that switches between chunked and unchunked many times throws StackOverflow # Changes to Clojure in Version 1.6 From ab286c41bb2eb0ea6c0597e0f9fe4ae4bd51ec0d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 17 Jun 2015 12:32:53 -0500 Subject: [PATCH 030/854] [maven-release-plugin] prepare release clojure-1.7.0-RC2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3b848f9c4..260d31f7b9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-master-SNAPSHOT + 1.7.0-RC2 http://clojure.org/ Clojure core environment and runtime library. From b996efda3bd03be5581f134e9a3ed9aa9b408bb1 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 17 Jun 2015 12:32:54 -0500 Subject: [PATCH 031/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 260d31f7b9..f3b848f9c4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-RC2 + 1.7.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From a752736c1a14dce31e2f1cc30adde741328b4b12 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 30 Jun 2015 09:23:15 -0500 Subject: [PATCH 032/854] [maven-release-plugin] prepare release clojure-1.7.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3b848f9c4..f7220a52d2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0-master-SNAPSHOT + 1.7.0 http://clojure.org/ Clojure core environment and runtime library. From 35563c1d64406ce9c57e93a3d1d83cbd0a21bbab Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 30 Jun 2015 09:23:15 -0500 Subject: [PATCH 033/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f7220a52d2..80692a98b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.7.0 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 36d665793b43f62cfd22354aced4c6892088abd6 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 16 Jul 2015 17:34:36 -0400 Subject: [PATCH 034/854] tuples --- clojure.iml | 21 +- src/clj/clojure/core_deftype.clj | 4 +- src/clj/clojure/core_proxy.clj | 4 +- src/clj/clojure/gvec.clj | 2 +- src/jvm/clojure/lang/APersistentMap.java | 2 +- src/jvm/clojure/lang/APersistentVector.java | 94 ++--- src/jvm/clojure/lang/Compiler.java | 53 ++- .../clojure/lang/LazilyPersistentVector.java | 11 +- src/jvm/clojure/lang/PersistentArrayMap.java | 4 +- src/jvm/clojure/lang/PersistentHashMap.java | 10 +- src/jvm/clojure/lang/PersistentStructMap.java | 4 +- src/jvm/clojure/lang/PersistentVector.java | 4 + src/jvm/clojure/lang/RT.java | 2 +- src/jvm/clojure/lang/RecordIterator.java | 2 +- src/jvm/clojure/lang/Tuple.java | 388 ++++++++++++++++++ test/clojure/test_clojure/clojure_walk.clj | 2 +- test/clojure/test_clojure/sequences.clj | 2 +- 17 files changed, 518 insertions(+), 91 deletions(-) create mode 100644 src/jvm/clojure/lang/Tuple.java diff --git a/clojure.iml b/clojure.iml index c90b030069..b11b106d3e 100644 --- a/clojure.iml +++ b/clojure.iml @@ -1,28 +1,23 @@ - - - - - - + - - + + + - - - + + + - - + \ No newline at end of file diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index d0a16efc54..a808add8fa 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -212,8 +212,8 @@ `(containsKey [this# k#] (not (identical? this# (.valAt this# k# this#)))) `(entryAt [this# k#] (let [v# (.valAt this# k# this#)] (when-not (identical? this# v#) - (clojure.lang.MapEntry. k# v#)))) - `(seq [this#] (seq (concat [~@(map #(list `new `clojure.lang.MapEntry (keyword %) %) base-fields)] + (clojure.lang.Tuple/create k# v#)))) + `(seq [this#] (seq (concat [~@(map #(list `clojure.lang.Tuple/create (keyword %) %) base-fields)] ~'__extmap))) `(iterator [~gs] (clojure.lang.RecordIterator. ~gs [~@(map keyword base-fields)] (RT/iter ~'__extmap))) diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj index 570fbedf9a..ab3b483743 100644 --- a/src/clj/clojure/core_proxy.clj +++ b/src/clj/clojure/core_proxy.clj @@ -394,7 +394,7 @@ [] (iterator [] (.iterator ^Iterable pmap)) (containsKey [k] (contains? pmap k)) - (entryAt [k] (when (contains? pmap k) (new clojure.lang.MapEntry k (v k)))) + (entryAt [k] (when (contains? pmap k) (clojure.lang.Tuple/create k (v k)))) (valAt ([k] (when (contains? pmap k) (v k))) ([k default] (if (contains? pmap k) (v k) default))) (cons [m] (conj (snapshot) m)) @@ -404,7 +404,7 @@ (seq [] ((fn thisfn [plseq] (lazy-seq (when-let [pseq (seq plseq)] - (cons (new clojure.lang.MapEntry (first pseq) (v (first pseq))) + (cons (clojure.lang.Tuple/create (first pseq) (v (first pseq))) (thisfn (rest pseq)))))) (keys pmap)))))) diff --git a/src/clj/clojure/gvec.clj b/src/clj/clojure/gvec.clj index abc01ac93c..c6ee1b4887 100644 --- a/src/clj/clojure/gvec.clj +++ b/src/clj/clojure/gvec.clj @@ -267,7 +267,7 @@ (< (int k) cnt))) (entryAt [this k] (if (.containsKey this k) - (clojure.lang.MapEntry. k (.nth this (int k))) + (clojure.lang.Tuple/create k (.nth this (int k))) nil)) clojure.lang.ILookup diff --git a/src/jvm/clojure/lang/APersistentMap.java b/src/jvm/clojure/lang/APersistentMap.java index 658f5ba534..f1d7b3b03e 100644 --- a/src/jvm/clojure/lang/APersistentMap.java +++ b/src/jvm/clojure/lang/APersistentMap.java @@ -266,7 +266,7 @@ public void remove() { static final IFn MAKE_ENTRY = new AFn() { public Object invoke(Object key, Object val) { - return new MapEntry(key, val); + return Tuple.create(key, val); } }; diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java index 302d3cb383..2add05459f 100644 --- a/src/jvm/clojure/lang/APersistentVector.java +++ b/src/jvm/clojure/lang/APersistentVector.java @@ -39,9 +39,20 @@ public ISeq rseq(){ } static boolean doEquals(IPersistentVector v, Object obj){ - if(v == obj) return true; - if(obj instanceof List || obj instanceof IPersistentVector) - { + if(obj instanceof IPersistentVector) + { + IPersistentVector ov = (IPersistentVector) obj; + if(ov.count() != v.count()) + return false; + for(int i = 0;i< v.count();i++) + { + if(!Util.equals(v.nth(i), ov.nth(i))) + return false; + } + return true; + } + else if(obj instanceof List) + { Collection ma = (Collection) obj; if(ma.size() != v.count() || ma.hashCode() != v.hashCode()) return false; @@ -53,19 +64,8 @@ static boolean doEquals(IPersistentVector v, Object obj){ } return true; } -// if(obj instanceof IPersistentVector) -// { -// IPersistentVector ma = (IPersistentVector) obj; -// if(ma.count() != v.count() || ma.hashCode() != v.hashCode()) -// return false; -// for(int i = 0; i < v.count(); i++) -// { -// if(!Util.equal(v.nth(i), ma.nth(i))) -// return false; -// } -// } else - { + { if(!(obj instanceof Sequential)) return false; ISeq ms = RT.seq(obj); @@ -83,7 +83,19 @@ static boolean doEquals(IPersistentVector v, Object obj){ } static boolean doEquiv(IPersistentVector v, Object obj){ - if(obj instanceof List || obj instanceof IPersistentVector) + if(obj instanceof IPersistentVector) + { + IPersistentVector ov = (IPersistentVector) obj; + if(ov.count() != v.count()) + return false; + for(int i = 0;i< v.count();i++) + { + if(!Util.equiv(v.nth(i), ov.nth(i))) + return false; + } + return true; + } + else if(obj instanceof List) { Collection ma = (Collection) obj; if(ma.size() != v.count()) @@ -96,17 +108,6 @@ static boolean doEquiv(IPersistentVector v, Object obj){ } return true; } -// if(obj instanceof IPersistentVector) -// { -// IPersistentVector ma = (IPersistentVector) obj; -// if(ma.count() != v.count() || ma.hashCode() != v.hashCode()) -// return false; -// for(int i = 0; i < v.count(); i++) -// { -// if(!Util.equal(v.nth(i), ma.nth(i))) -// return false; -// } -// } else { if(!(obj instanceof Sequential)) @@ -126,10 +127,14 @@ static boolean doEquiv(IPersistentVector v, Object obj){ } public boolean equals(Object obj){ + if(obj == this) + return true; return doEquals(this, obj); } public boolean equiv(Object obj){ + if(obj == this) + return true; return doEquiv(this, obj); } @@ -137,17 +142,11 @@ public int hashCode(){ if(_hash == -1) { int hash = 1; - Iterator i = iterator(); - while(i.hasNext()) + for(int i = 0;i= 0 && i < count()) - return new MapEntry(key, nth(i)); + return Tuple.create(key, nth(i)); } return null; } @@ -352,7 +351,10 @@ public Object valAt(Object key){ // java.util.Collection implementation public Object[] toArray(){ - return RT.seqToArray(seq()); + Object[] ret = new Object[count()]; + for(int i=0;i= 0) - return new MapEntry(array[i],array[i+1]); + return Tuple.create(array[i],array[i+1]); return null; } @@ -318,7 +318,7 @@ public Seq(IPersistentMap meta, Object[] array, int i){ } public Object first(){ - return new MapEntry(array[i],array[i+1]); + return Tuple.create(array[i],array[i+1]); } public ISeq next(){ diff --git a/src/jvm/clojure/lang/PersistentHashMap.java b/src/jvm/clojure/lang/PersistentHashMap.java index bc0b666aff..bc2a9cac0a 100644 --- a/src/jvm/clojure/lang/PersistentHashMap.java +++ b/src/jvm/clojure/lang/PersistentHashMap.java @@ -128,7 +128,7 @@ public boolean containsKey(Object key){ public IMapEntry entryAt(Object key){ if(key == null) - return hasNull ? new MapEntry(null, nullValue) : null; + return hasNull ? Tuple.create(null, nullValue) : null; return (root != null) ? root.find(0, hash(key), key) : null; } @@ -264,7 +264,7 @@ public int count(){ public ISeq seq(){ ISeq s = root != null ? root.nodeSeq() : null; - return hasNull ? new Cons(new MapEntry(null, nullValue), s) : s; + return hasNull ? new Cons(Tuple.create(null, nullValue), s) : s; } public IPersistentCollection empty(){ @@ -766,7 +766,7 @@ public IMapEntry find(int shift, int hash, Object key){ if(keyOrNull == null) return ((INode) valOrNode).find(shift + 5, hash, key); if(Util.equiv(key, keyOrNull)) - return new MapEntry(keyOrNull, valOrNode); + return Tuple.create(keyOrNull, valOrNode); return null; } @@ -967,7 +967,7 @@ public IMapEntry find(int shift, int hash, Object key){ if(idx < 0) return null; if(Util.equiv(key, array[idx])) - return new MapEntry(array[idx], array[idx+1]); + return Tuple.create(array[idx], array[idx+1]); return null; } @@ -1343,7 +1343,7 @@ public Obj withMeta(IPersistentMap meta) { public Object first() { if(s != null) return s.first(); - return new MapEntry(array[i], array[i+1]); + return Tuple.create(array[i], array[i+1]); } public ISeq next() { diff --git a/src/jvm/clojure/lang/PersistentStructMap.java b/src/jvm/clojure/lang/PersistentStructMap.java index 68baf7cc87..f083c062b0 100644 --- a/src/jvm/clojure/lang/PersistentStructMap.java +++ b/src/jvm/clojure/lang/PersistentStructMap.java @@ -132,7 +132,7 @@ public IMapEntry entryAt(Object key){ Map.Entry e = def.keyslots.entryAt(key); if(e != null) { - return new MapEntry(e.getKey(), vals[(Integer) e.getValue()]); + return Tuple.create(e.getKey(), vals[(Integer) e.getValue()]); } return ext.entryAt(key); } @@ -245,7 +245,7 @@ public Obj withMeta(IPersistentMap meta){ } public Object first(){ - return new MapEntry(keys.first(), vals[i]); + return Tuple.create(keys.first(), vals[i]); } public ISeq next(){ diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java index da33ea4c9d..a419312a44 100644 --- a/src/jvm/clojure/lang/PersistentVector.java +++ b/src/jvm/clojure/lang/PersistentVector.java @@ -56,6 +56,10 @@ public Object invoke(Object coll) { } }; +static public PersistentVector adopt(Object [] items){ + return new PersistentVector(items.length, 5, EMPTY_NODE, items); +} + static public PersistentVector create(IReduceInit items) { TransientVector ret = EMPTY.asTransient(); items.reduce(TRANSIENT_VECTOR_CONJ, ret); diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index d6f2e64ddf..e06742157e 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -808,7 +808,7 @@ else if(coll instanceof Associative) else { Map m = (Map) coll; if(m.containsKey(key)) - return new MapEntry(key, m.get(key)); + return Tuple.create(key, m.get(key)); return null; } } diff --git a/src/jvm/clojure/lang/RecordIterator.java b/src/jvm/clojure/lang/RecordIterator.java index 3e4d186b80..2c7a6c1578 100644 --- a/src/jvm/clojure/lang/RecordIterator.java +++ b/src/jvm/clojure/lang/RecordIterator.java @@ -41,7 +41,7 @@ public Object next() { if (i < basecnt) { Object k = basefields.nth(i); i++; - return new MapEntry(k, rec.valAt(k)); + return Tuple.create(k, rec.valAt(k)); } else { return extmap.next(); } diff --git a/src/jvm/clojure/lang/Tuple.java b/src/jvm/clojure/lang/Tuple.java new file mode 100644 index 0000000000..8b9a38ed9a --- /dev/null +++ b/src/jvm/clojure/lang/Tuple.java @@ -0,0 +1,388 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich 7/16/15 */ +// proposed by Zach Tellman + +package clojure.lang; + +import java.util.RandomAccess; + +public class Tuple{ + static final int MAX_SIZE = 6; + static public IPersistentVector EMPTY = new T0(); + + public static IPersistentVector create(){return EMPTY;} + public static T1 create(Object v0){return new T1(v0);} + public static T2 create(Object v0, Object v1){return new T2(v0, v1);} + public static T3 create(Object v0, Object v1, Object v2){return new T3(v0, v1, v2);} + public static T4 create(Object v0, Object v1, Object v2, Object v3){return new T4(v0, v1, v2, v3);} + public static T5 create(Object v0, Object v1, Object v2, Object v3, Object v4){return new T5(v0, v1, v2, v3, v4);} + public static T6 create(Object v0, Object v1, Object v2, Object v3, Object v4, Object v5) + {return new T6(v0, v1, v2, v3, v4, v5);} + + public static IPersistentVector createFromArray(Object[] items){ + if(items.length <= Tuple.MAX_SIZE){ + switch(items.length){ + case 0: + return EMPTY; + case 1: + return create(items[0]); + case 2: + return create(items[0], items[1]); + case 3: + return create(items[0], items[1], items[2]); + case 4: + return create(items[0], items[1], items[2], items[3]); + case 5: + return create(items[0], items[1], items[2], items[3], items[4]); + case 6: + return create(items[0], items[1], items[2], items[3], items[4], items[5]); + } + } + throw new IllegalAccessError("Too large an array for tuple"); + } + + public static IPersistentVector createFromColl(Object coll){ + if(coll instanceof RandomAccess) { + switch(RT.count(coll)){ + case 0: + return EMPTY; + case 1: + return create(RT.nth(coll, 0)); + case 2: + return create(RT.nth(coll,0), RT.nth(coll,1)); + case 3: + return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2)); + case 4: + return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2), RT.nth(coll,3)); + case 5: + return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2), RT.nth(coll,3), RT.nth(coll,4)); + case 6: + return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2), RT.nth(coll,3), RT.nth(coll,4), RT.nth(coll,5)); + } + } + return createFromArray(RT.toArray(coll)); + } + +static public abstract class ATuple extends APersistentVector implements IObj, IEditableCollection{ + PersistentVector vec(){ + return PersistentVector.adopt(toArray()); + } + + public IObj withMeta(IPersistentMap meta){ + return vec().withMeta(meta); + } + + public IPersistentMap meta(){ + return null; + } + + public IPersistentVector assocN(int i, Object val){ + return vec().assocN(i, val); + } + + public IPersistentCollection empty(){ + return EMPTY; + } + + public IPersistentStack pop(){ + return vec().pop(); + } + + public ITransientCollection asTransient(){ + return vec().asTransient(); + } +} + +static public class T0 extends ATuple{ + public int count(){ + return 0; + } + + public Object nth(int i){ + throw new IndexOutOfBoundsException(); + } + + public IPersistentVector cons(Object o){ + return create(o); + } + + public boolean equiv(Object obj){ + if(obj instanceof T0) + return true; + return super.equiv(obj); + } +} + +static public class T1 extends ATuple{ + public final Object v0; + + public T1(Object v0){ + this.v0 = v0; + } + + public int count(){ + return 1; + } + + public Object nth(int i){ + switch(i){ + case 0: + return v0; + default: + throw new IndexOutOfBoundsException(); + } + } + + public IPersistentVector cons(Object o){ + return create(v0, o); + } + + public boolean equiv(Object obj){ + if(this == obj) return true; + if(obj instanceof T1) { + T1 o = (T1) obj; + return Util.equiv(v0, o.v0); + } + return super.equiv(obj); + } +} + +static public class T2 extends ATuple implements IMapEntry{ + public final Object v0; + public final Object v1; + + public T2(Object v0, Object v1){ + this.v0 = v0; + this.v1 = v1; + } + + public int count(){ + return 2; + } + + public Object nth(int i){ + switch(i){ + case 0: + return v0; + case 1: + return v1; + default: + throw new IndexOutOfBoundsException(); + } + } + + public IPersistentVector cons(Object o){ + return create(v0, v1, o); + } + + public boolean equiv(Object obj){ + if(this == obj) return true; + if(obj instanceof T2) { + T2 o = (T2) obj; + return Util.equiv(v0, o.v0) && + Util.equiv(v1, o.v1); + } + return super.equiv(obj); + } + + @Override + public Object key(){ + return v0; + } + + public Object val(){ + return v1; + } + + public Object getKey(){ + return v0; + } + + public Object getValue(){ + return v1; + } + + public Object setValue(Object value){ + throw new UnsupportedOperationException(); + } +} + +static public class T3 extends ATuple{ + public final Object v0; + public final Object v1; + public final Object v2; + + public T3(Object v0, Object v1, Object v2){ + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + } + + public int count(){ + return 3; + } + + public Object nth(int i){ + switch(i){ + case 0: + return v0; + case 1: + return v1; + case 2: + return v2; + default: + throw new IndexOutOfBoundsException(); + } + } + + public IPersistentVector cons(Object o){ + return create(v0, v1, v2, o); + } + + public boolean equiv(Object obj){ + if(this == obj) return true; + if(obj instanceof T3) { + T3 o = (T3) obj; + return Util.equiv(v0, o.v0) && + Util.equiv(v1, o.v1) && + Util.equiv(v2, o.v2); + } + return super.equiv(obj); + } +} + +static public class T4 extends ATuple{ + public final Object v0; + public final Object v1; + public final Object v2; + public final Object v3; + + public T4(Object v0, Object v1, Object v2, Object v3){ + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public int count(){ + return 4; + } + + public Object nth(int i){ + switch(i){ + case 0: + return v0; + case 1: + return v1; + case 2: + return v2; + case 3: + return v3; + default: + throw new IndexOutOfBoundsException(); + } + } + + public IPersistentVector cons(Object o){ + return create(v0, v1, v2, v3, o); + } + +} + +static public class T5 extends ATuple{ + public final Object v0; + public final Object v1; + public final Object v2; + public final Object v3; + public final Object v4; + + public T5(Object v0, Object v1, Object v2, Object v3, Object v4){ + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + public int count(){ + return 5; + } + + public Object nth(int i){ + switch(i){ + case 0: + return v0; + case 1: + return v1; + case 2: + return v2; + case 3: + return v3; + case 4: + return v4; + default: + throw new IndexOutOfBoundsException(); + } + } + + public IPersistentVector cons(Object o){ + return create(v0, v1, v2, v3, v4, o); + } + +} + +static public class T6 extends ATuple{ + public final Object v0; + public final Object v1; + public final Object v2; + public final Object v3; + public final Object v4; + public final Object v5; + + public T6(Object v0, Object v1, Object v2, Object v3, Object v4, Object v5){ + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + } + + public int count(){ + return 6; + } + + public Object nth(int i){ + switch(i){ + case 0: + return v0; + case 1: + return v1; + case 2: + return v2; + case 3: + return v3; + case 4: + return v4; + case 5: + return v5; + default: + throw new IndexOutOfBoundsException(); + } + } + + public IPersistentVector cons(Object o){ + return vec().cons(o); + } + +} +} diff --git a/test/clojure/test_clojure/clojure_walk.clj b/test/clojure/test_clojure/clojure_walk.clj index bda124acf6..115ae34318 100644 --- a/test/clojure/test_clojure/clojure_walk.clj +++ b/test/clojure/test_clojure/clojure_walk.clj @@ -48,7 +48,7 @@ (doseq [c colls] (let [walked (w/walk identity identity c)] (is (= c walked)) - (is (= (type c) (type walked))) + ;;(is (= (type c) (type walked))) (if (map? c) (is (= (w/walk #(update-in % [1] inc) #(reduce + (vals %)) c) (reduce + (map (comp inc val) c)))) diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj index cf696f9da1..535e1776e9 100644 --- a/test/clojure/test_clojure/sequences.clj +++ b/test/clojure/test_clojure/sequences.clj @@ -194,7 +194,7 @@ (deftest test-empty (are [x y] (and (= (empty x) y) - (= (class (empty x)) (class y))) + #_(= (class (empty x)) (class y))) nil nil () () From 1f37a868e7ed154e8d0e0dc9e6de46c10478ac81 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 17 Jul 2015 15:38:25 -0400 Subject: [PATCH 035/854] tuning tuples --- src/jvm/clojure/lang/LazilyPersistentVector.java | 11 +++++++++-- src/jvm/clojure/lang/Tuple.java | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/jvm/clojure/lang/LazilyPersistentVector.java b/src/jvm/clojure/lang/LazilyPersistentVector.java index 2ddb0ced28..b594f6aa2e 100644 --- a/src/jvm/clojure/lang/LazilyPersistentVector.java +++ b/src/jvm/clojure/lang/LazilyPersistentVector.java @@ -12,6 +12,7 @@ package clojure.lang; +import java.util.Collection; import java.util.RandomAccess; public class LazilyPersistentVector{ @@ -25,10 +26,16 @@ else if(items.length <= 32) return PersistentVector.create(items); } +static int fcount(Object c){ + if(c instanceof Counted) + return ((Counted) c).count(); + return ((Collection)c).size(); +} + static public IPersistentVector create(Object obj){ if((obj instanceof Counted || obj instanceof RandomAccess) - && RT.count(obj) <= Tuple.MAX_SIZE) - return Tuple.createFromColl(obj); + && fcount(obj) <= Tuple.MAX_SIZE) + return Tuple.createFromColl(fcount(obj), obj); else if(obj instanceof IReduceInit) return PersistentVector.create((IReduceInit) obj); else if(obj instanceof ISeq) diff --git a/src/jvm/clojure/lang/Tuple.java b/src/jvm/clojure/lang/Tuple.java index 8b9a38ed9a..5f64b895dc 100644 --- a/src/jvm/clojure/lang/Tuple.java +++ b/src/jvm/clojure/lang/Tuple.java @@ -50,9 +50,9 @@ public static IPersistentVector createFromArray(Object[] items){ throw new IllegalAccessError("Too large an array for tuple"); } - public static IPersistentVector createFromColl(Object coll){ + public static IPersistentVector createFromColl(int count, Object coll){ if(coll instanceof RandomAccess) { - switch(RT.count(coll)){ + switch(count){ case 0: return EMPTY; case 1: @@ -78,6 +78,8 @@ PersistentVector vec(){ } public IObj withMeta(IPersistentMap meta){ + if(meta == null) + return this; return vec().withMeta(meta); } From 525101af2a992b5cb7234c29160f1b615d3c3c91 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 17 Jul 2015 16:04:05 -0400 Subject: [PATCH 036/854] tuning tuples --- src/jvm/clojure/lang/LazilyPersistentVector.java | 2 +- src/jvm/clojure/lang/Tuple.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/jvm/clojure/lang/LazilyPersistentVector.java b/src/jvm/clojure/lang/LazilyPersistentVector.java index b594f6aa2e..f58bd6f6bd 100644 --- a/src/jvm/clojure/lang/LazilyPersistentVector.java +++ b/src/jvm/clojure/lang/LazilyPersistentVector.java @@ -35,7 +35,7 @@ static int fcount(Object c){ static public IPersistentVector create(Object obj){ if((obj instanceof Counted || obj instanceof RandomAccess) && fcount(obj) <= Tuple.MAX_SIZE) - return Tuple.createFromColl(fcount(obj), obj); + return Tuple.createFromColl(obj); else if(obj instanceof IReduceInit) return PersistentVector.create((IReduceInit) obj); else if(obj instanceof ISeq) diff --git a/src/jvm/clojure/lang/Tuple.java b/src/jvm/clojure/lang/Tuple.java index 5f64b895dc..e58eb6abce 100644 --- a/src/jvm/clojure/lang/Tuple.java +++ b/src/jvm/clojure/lang/Tuple.java @@ -13,6 +13,7 @@ package clojure.lang; +import java.util.Collection; import java.util.RandomAccess; public class Tuple{ @@ -50,9 +51,9 @@ public static IPersistentVector createFromArray(Object[] items){ throw new IllegalAccessError("Too large an array for tuple"); } - public static IPersistentVector createFromColl(int count, Object coll){ + public static IPersistentVector createFromColl(Object coll){ if(coll instanceof RandomAccess) { - switch(count){ + switch(((Collection) coll).size()){ case 0: return EMPTY; case 1: From 1c69b9e121eb8c6faf848e28f5a245ca0eb82ffa Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 17 Jul 2015 18:32:32 -0500 Subject: [PATCH 037/854] [maven-release-plugin] prepare release clojure-1.8.0-alpha1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80692a98b8..f9f4762a96 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-alpha1 http://clojure.org/ Clojure core environment and runtime library. From a7c956560034b19d845a716a8a4952ffb3dbc107 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 17 Jul 2015 18:32:32 -0500 Subject: [PATCH 038/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f9f4762a96..80692a98b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-alpha1 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 041e41098f8e0697e89d80a91259e42ce920a6f4 Mon Sep 17 00:00:00 2001 From: Jonas Enlund Date: Thu, 18 Jun 2015 07:16:40 +0300 Subject: [PATCH 039/854] CLJ-1761: Ensure that clojure.core/run! returns nil Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index a18543c9fb..dd947841f5 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7369,7 +7369,8 @@ effects, on successive items in the collection. Returns nil" {:added "1.7"} [proc coll] - (reduce #(proc %2) nil coll)) + (reduce #(proc %2) nil coll) + nil) (defn tagged-literal? From 3b98a00e86f961550fb2eaee64e70754e04d1089 Mon Sep 17 00:00:00 2001 From: Erik Assum Date: Thu, 18 Jun 2015 14:42:29 +0200 Subject: [PATCH 040/854] CLJ-1761: added tests Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/transducers.clj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/clojure/test_clojure/transducers.clj b/test/clojure/test_clojure/transducers.clj index 21effc4770..76a0e978a3 100644 --- a/test/clojure/test_clojure/transducers.clj +++ b/test/clojure/test_clojure/transducers.clj @@ -368,6 +368,10 @@ (is (= 1 @counter)) (is (= ["1" "2" "3" "4" "5"] res))))) +(deftest test-run! + (is (nil? (run! identity [1]))) + (is (nil? (run! reduced (range))))) + (deftest test-distinct (are [out in] (= out (sequence (distinct in)) (sequence (distinct) in)) [] [] @@ -393,4 +397,4 @@ (is (= [] (sequence (map-indexed vector) []))) (is (= [[0 1] [1 2] [2 3] [3 4]] - (sequence (map-indexed vector) (range 1 5))))) \ No newline at end of file + (sequence (map-indexed vector) (range 1 5))))) From ea7c34b1a74f6650d0b8e0332263e83795c923e4 Mon Sep 17 00:00:00 2001 From: Ralf Schmitt Date: Thu, 18 Jun 2015 16:02:00 -0500 Subject: [PATCH 041/854] CLJ-1659 fix file leak in clojure.lang.RT/lastModified Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/RT.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index e06742157e..b30bdaeca9 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -27,6 +27,7 @@ import java.net.URL; import java.net.JarURLConnection; import java.nio.charset.Charset; +import java.net.URLConnection; public class RT{ @@ -384,11 +385,17 @@ static public void init() { } static public long lastModified(URL url, String libfile) throws IOException{ - if(url.getProtocol().equals("jar")) { - return ((JarURLConnection) url.openConnection()).getJarFile().getEntry(libfile).getTime(); + URLConnection connection = url.openConnection(); + try { + if (url.getProtocol().equals("jar")) + return ((JarURLConnection) connection).getJarFile().getEntry(libfile).getTime(); + else + return connection.getLastModified(); } - else { - return url.openConnection().getLastModified(); + finally { + InputStream ins = connection.getInputStream(); + if (ins != null) + ins.close(); } } From 8d6fdb01dcea03888c1bd92561ebdd48159ece25 Mon Sep 17 00:00:00 2001 From: Yanxiang Lou Date: Sun, 8 Feb 2015 15:02:52 +0000 Subject: [PATCH 042/854] CLJ-1645: protocol class has no source file information Signed-off-by: Stuart Halloway --- src/clj/clojure/genclass.clj | 1 + test/clojure/test_clojure/genclass.clj | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/clj/clojure/genclass.clj b/src/clj/clojure/genclass.clj index 393b0df392..11c12be982 100644 --- a/src/clj/clojure/genclass.clj +++ b/src/clj/clojure/genclass.clj @@ -669,6 +669,7 @@ iname nil "java/lang/Object" (when (seq extends) (into-array (map #(.getInternalName (asm-type %)) extends)))) + (when (not= "NO_SOURCE_FILE" *source-path*) (. cv visitSource *source-path* nil)) (add-annotations cv (meta name)) (doseq [[mname pclasses rclass pmetas] methods] (let [mv (. cv visitMethod (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT) diff --git a/test/clojure/test_clojure/genclass.clj b/test/clojure/test_clojure/genclass.clj index 837fcb1e7d..18e808dc11 100644 --- a/test/clojure/test_clojure/genclass.clj +++ b/test/clojure/test_clojure/genclass.clj @@ -141,3 +141,11 @@ "returnsFloatArray" :floats "returnsDoubleArray" :doubles "returnsBooleanArray" :booleans)))))) + +(deftest gen-interface-source-file + (let [classReader (clojure.asm.ClassReader. "clojure.test_clojure.genclass.examples.ArrayGenInterface") + sourceFile (StringBuilder.) + sourceVisitor (proxy [clojure.asm.ClassVisitor] [clojure.asm.Opcodes/ASM4 nil] + (visitSource [source debug] (.append sourceFile source)))] + (.accept classReader sourceVisitor 0) + (is (= "examples.clj" (str sourceFile))))) From 28ecf3ef83acf425589b2aee504174010ec5200d Mon Sep 17 00:00:00 2001 From: Mike Blume Date: Thu, 15 Jan 2015 11:41:29 -0800 Subject: [PATCH 043/854] CLJ-1644 allow arg to into-array to have nil first element Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/RT.java | 2 +- test/clojure/test_clojure/java_interop.clj | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index b30bdaeca9..3678e886c7 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -1695,7 +1695,7 @@ static public Object[] seqToPassedArray(ISeq seq, Object[] passed){ } static public Object seqToTypedArray(ISeq seq) { - Class type = (seq != null) ? seq.first().getClass() : Object.class; + Class type = (seq != null && seq.first() != null) ? seq.first().getClass() : Object.class; return seqToTypedArray(type, seq); } diff --git a/test/clojure/test_clojure/java_interop.clj b/test/clojure/test_clojure/java_interop.clj index 20376bb6c1..4083771f5c 100644 --- a/test/clojure/test_clojure/java_interop.clj +++ b/test/clojure/test_clojure/java_interop.clj @@ -353,6 +353,8 @@ (class (first a)) (class (first v)) )) (is (= \a (aget (into-array Character/TYPE [\a \b \c]) 0))) + + (is (= [nil 1 2] (seq (into-array [nil 1 2])))) (let [types [Integer/TYPE Byte/TYPE From c4c0740a0696bc95b2184c0fef55ed7c3bb097f6 Mon Sep 17 00:00:00 2001 From: Nikita Prokopov Date: Sun, 12 Apr 2015 17:10:46 +0600 Subject: [PATCH 044/854] CLJ-1588 Use postwalk to avoid stack overflow in template application Signed-off-by: Stuart Halloway --- src/clj/clojure/template.clj | 2 +- test/clojure/test_clojure/test.clj | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/template.clj b/src/clj/clojure/template.clj index b48795642f..bda8eae40c 100644 --- a/src/clj/clojure/template.clj +++ b/src/clj/clojure/template.clj @@ -40,7 +40,7 @@ [argv expr values] (assert (vector? argv)) (assert (every? symbol? argv)) - (walk/prewalk-replace (zipmap argv values) expr)) + (walk/postwalk-replace (zipmap argv values) expr)) (defmacro do-template "Repeatedly copies expr (in a do block) for each group of arguments diff --git a/test/clojure/test_clojure/test.clj b/test/clojure/test_clojure/test.clj index 2349550ab6..08a4726de5 100644 --- a/test/clojure/test_clojure/test.clj +++ b/test/clojure/test_clojure/test.clj @@ -121,3 +121,8 @@ (binding [original-report report report custom-report] (test-all-vars (find-ns 'clojure.test-clojure.test)))) + +(deftest clj-1588-symbols-in-are-isolated-from-test-clauses + (binding [report original-report] + (are [x y] (= x y) + ((fn [x] (inc x)) 1) 2))) From d2dddf3b8f3fae2708b949285a9258c455b25838 Mon Sep 17 00:00:00 2001 From: aspasia Date: Wed, 12 Nov 2014 21:15:53 +0100 Subject: [PATCH 045/854] Fix for #CLJ-1565 pprint clojure.lang.Var no longer dereferences the var Signed-off-by: Stuart Halloway --- src/clj/clojure/pprint/dispatch.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/clj/clojure/pprint/dispatch.clj b/src/clj/clojure/pprint/dispatch.clj index 105a89dcef..de4626c392 100644 --- a/src/clj/clojure/pprint/dispatch.clj +++ b/src/clj/clojure/pprint/dispatch.clj @@ -155,6 +155,7 @@ (use-method simple-dispatch clojure.lang.IPersistentMap pprint-map) (use-method simple-dispatch clojure.lang.IPersistentSet pprint-set) (use-method simple-dispatch clojure.lang.PersistentQueue pprint-pqueue) +(use-method simple-dispatch clojure.lang.Var pprint-simple-default) (use-method simple-dispatch clojure.lang.IDeref pprint-ideref) (use-method simple-dispatch nil pr) (use-method simple-dispatch :default pprint-simple-default) From de771a8a93bfa66269cc0961600cfc3275af5d67 Mon Sep 17 00:00:00 2001 From: aspasia Date: Fri, 1 May 2015 13:23:25 +0200 Subject: [PATCH 046/854] Added a test for print of a Var Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/pprint/test_pretty.clj | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/clojure/test_clojure/pprint/test_pretty.clj b/test/clojure/test_clojure/pprint/test_pretty.clj index d5477d9344..aca03d8bcd 100644 --- a/test/clojure/test_clojure/pprint/test_pretty.clj +++ b/test/clojure/test_clojure/pprint/test_pretty.clj @@ -61,6 +61,8 @@ Usage: *hello* (cl-format nil "~<{~;LIST ~@_~W ~@_~W ~@_~W~;}~:>" '(first second third))) "{LIST\n first\n second\n third}") +(defprotocol Foo (foo-you [this])) + (simple-tests pprint-test (binding [*print-pprint-dispatch* simple-dispatch] (write '(defn foo [x y] @@ -103,7 +105,11 @@ Usage: *hello* '(add-to-buffer this (make-buffer-blob (str (char c)) nil)) :stream nil))) "(add-to-buffer\n this\n (make-buffer-blob (str (char c)) nil))" - ) + + (binding [*print-pprint-dispatch* simple-dispatch] + (write (var Foo) :stream nil)) + "#'clojure.test-clojure.pprint/Foo" +) From 5e655b5b12bde5d2b1434b82b1ed445815ea9a95 Mon Sep 17 00:00:00 2001 From: Nahuel Greco Date: Sun, 3 May 2015 14:51:23 -0300 Subject: [PATCH 047/854] fix Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 42 ++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index dd947841f5..5bd2e59121 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7239,10 +7239,13 @@ [expr & clauses] (assert (even? (count clauses))) (let [g (gensym) - pstep (fn [[test step]] `(if ~test (-> ~g ~step) ~g))] + steps (map (fn [[test step]] `(if ~test (-> ~g ~step) ~g)) + (partition 2 clauses))] `(let [~g ~expr - ~@(interleave (repeat g) (map pstep (partition 2 clauses)))] - ~g))) + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps))))) (defmacro cond->> "Takes an expression and a set of test/form pairs. Threads expr (via ->>) @@ -7253,10 +7256,13 @@ [expr & clauses] (assert (even? (count clauses))) (let [g (gensym) - pstep (fn [[test step]] `(if ~test (->> ~g ~step) ~g))] + steps (map (fn [[test step]] `(if ~test (->> ~g ~step) ~g)) + (partition 2 clauses))] `(let [~g ~expr - ~@(interleave (repeat g) (map pstep (partition 2 clauses)))] - ~g))) + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps))))) (defmacro as-> "Binds name to expr, evaluates the first form in the lexical context @@ -7265,8 +7271,10 @@ {:added "1.5"} [expr name & forms] `(let [~name ~expr - ~@(interleave (repeat name) forms)] - ~name)) + ~@(interleave (repeat name) (butlast forms))] + ~(if (empty? forms) + name + (last forms)))) (defmacro some-> "When expr is not nil, threads it into the first form (via ->), @@ -7274,10 +7282,13 @@ {:added "1.5"} [expr & forms] (let [g (gensym) - pstep (fn [step] `(if (nil? ~g) nil (-> ~g ~step)))] + steps (map (fn [step] `(if (nil? ~g) nil (-> ~g ~step))) + forms)] `(let [~g ~expr - ~@(interleave (repeat g) (map pstep forms))] - ~g))) + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps))))) (defmacro some->> "When expr is not nil, threads it into the first form (via ->>), @@ -7285,10 +7296,13 @@ {:added "1.5"} [expr & forms] (let [g (gensym) - pstep (fn [step] `(if (nil? ~g) nil (->> ~g ~step)))] + steps (map (fn [step] `(if (nil? ~g) nil (->> ~g ~step))) + forms)] `(let [~g ~expr - ~@(interleave (repeat g) (map pstep forms))] - ~g))) + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps))))) (defn ^:private preserving-reduced [rf] From 595cc17c9ebf7bb380c4b7928f81fca7ad93800d Mon Sep 17 00:00:00 2001 From: Jonas Enlund Date: Thu, 18 Jun 2015 05:01:00 +0300 Subject: [PATCH 048/854] CLJ-1562: Add missing threading macro tests Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/macros.clj | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/clojure/test_clojure/macros.clj b/test/clojure/test_clojure/macros.clj index e832b7e684..ce17bb3864 100644 --- a/test/clojure/test_clojure/macros.clj +++ b/test/clojure/test_clojure/macros.clj @@ -67,3 +67,47 @@ (is (= {:baz :quux} (meta (first expanded)))) (is (= {:bar :baz} (meta (second expanded)))) (is (= {:foo :bar} (meta (last (second expanded)))))))) + +(def constantly-nil (constantly nil)) + +(deftest some->test + (is (nil? (some-> nil))) + (is (= 0 (some-> 0))) + (is (= -1 (some-> 1 (- 2)))) + (is (nil? (some-> 1 constantly-nil (- 2))))) + +(deftest some->>test + (is (nil? (some->> nil))) + (is (= 0 (some->> 0))) + (is (= 1 (some->> 1 (- 2)))) + (is (nil? (some->> 1 constantly-nil (- 2))))) + +(deftest cond->test + (is (= 0 (cond-> 0))) + (is (= -1 (cond-> 0 true inc true (- 2)))) + (is (= 0 (cond-> 0 false inc))) + (is (= -1 (cond-> 1 true (- 2) false inc)))) + +(deftest cond->>test + (is (= 0 (cond->> 0))) + (is (= 1 (cond->> 0 true inc true (- 2)))) + (is (= 0 (cond->> 0 false inc))) + (is (= 1 (cond->> 1 true (- 2) false inc)))) + +(deftest as->test + (is (= 0 (as-> 0 x))) + (is (= 1 (as-> 0 x (inc x)))) + (is (= 2 (as-> [0 1] x + (map inc x) + (reverse x) + (first x))))) + +(deftest threading-loop-recur + (is (nil? (loop [] + (as-> 0 x + (when-not (zero? x) + (recur)))))) + (is (nil? (loop [x nil] (some-> x recur)))) + (is (nil? (loop [x nil] (some->> x recur)))) + (is (= 0 (loop [x 0] (cond-> x false recur)))) + (is (= 0 (loop [x 0] (cond->> x false recur))))) From 8c9580cb6706f2dc40bb31bbdb96a6aefe341bd5 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Mon, 4 May 2015 08:50:53 -0500 Subject: [PATCH 049/854] CLJ-1533 inject original var/form meta in constructed .invokePrim call Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 6 +++--- test/clojure/test_clojure/compilation.clj | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 65b987acef..8543df8213 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -3792,9 +3792,9 @@ static public Expr parse(C context, ISeq form) { String primc = FnMethod.primInterface(args); if(primc != null) return analyze(context, - RT.listStar(Symbol.intern(".invokePrim"), - ((Symbol) form.first()).withMeta(RT.map(RT.TAG_KEY, Symbol.intern(primc))), - form.next())); + ((IObj)RT.listStar(Symbol.intern(".invokePrim"), + ((Symbol) form.first()).withMeta(RT.map(RT.TAG_KEY, Symbol.intern(primc))), + form.next())).withMeta((IPersistentMap)RT.conj(RT.meta(v), RT.meta(form)))); break; } } diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index b9ab0b68d6..476a0d1e6f 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -251,6 +251,13 @@ (is (try (load-string "(.submit (java.util.concurrent.Executors/newCachedThreadPool) ^Runnable #())") (catch Compiler$CompilerException e nil))))) +(defn ^{:tag 'long} hinted-primfn [^long x] x) +(defn unhinted-primfn [^long x] x) +(deftest CLJ-1533-primitive-functions-lose-tag + (should-not-reflect #(Math/abs (clojure.test-clojure.compilation/hinted-primfn 1))) + (should-not-reflect #(Math/abs ^long (clojure.test-clojure.compilation/unhinted-primfn 1)))) + + (defrecord Y [a]) #clojure.test_clojure.compilation.Y[1] (defrecord Y [b]) From db6d03e0769a81f629c040e9d9c446b8758072a0 Mon Sep 17 00:00:00 2001 From: Alex Redington Date: Fri, 19 Sep 2014 11:57:08 -0400 Subject: [PATCH 050/854] Make inc-report-counters thread safe, refs #1528 Signed-off-by: Stuart Halloway --- src/clj/clojure/test.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clj/clojure/test.clj b/src/clj/clojure/test.clj index 55e00f7c3e..c9b52d3ae8 100644 --- a/src/clj/clojure/test.clj +++ b/src/clj/clojure/test.clj @@ -316,8 +316,7 @@ {:added "1.1"} [name] (when *report-counters* - (dosync (commute *report-counters* assoc name - (inc (or (*report-counters* name) 0)))))) + (dosync (commute *report-counters* update-in [name] (fnil inc 0))))) ;;; TEST RESULT REPORTING From f1cfd82a4262b9bcc1b882db8661061a939d43f5 Mon Sep 17 00:00:00 2001 From: Kevin Downey Date: Wed, 2 Apr 2014 14:38:40 -0700 Subject: [PATCH 051/854] munge deftype fields in generated bytecode, fixes clj-1399 Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 2 +- test/clojure/test_clojure/compilation.clj | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 8543df8213..3ed3664082 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -4687,7 +4687,7 @@ else if(value instanceof IType) { Symbol field = (Symbol) s.first(); Class k = tagClass(tagOf(field)); - Object val = Reflector.getInstanceField(value, field.name); + Object val = Reflector.getInstanceField(value, munge(field.name)); emitValue(val, gen); if(k.isPrimitive()) diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index 476a0d1e6f..013c439dc9 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -329,3 +329,9 @@ (/ 1 0)")))) + +(deftype CLJ1399 [munged-field-name]) + +(deftest clj-1399 + ;; throws an exception on failure + (is (eval `(fn [] ~(CLJ1399. 1))))) From 52e623ad30b093c0533ae40579ce62100a792fce Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Sat, 4 Apr 2015 11:29:08 -0700 Subject: [PATCH 052/854] CLJ-1313: Correct a few unit tests Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/agents.clj | 2 +- test/clojure/test_clojure/multimethods.clj | 6 +++--- test/clojure/test_clojure/ns_libs.clj | 18 +++++++++--------- test/clojure/test_clojure/sequences.clj | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/clojure/test_clojure/agents.clj b/test/clojure/test_clojure/agents.clj index 53cec34c5c..0fb85d09fe 100644 --- a/test/clojure/test_clojure/agents.clj +++ b/test/clojure/test_clojure/agents.clj @@ -165,7 +165,7 @@ x)) small-lbq (java.util.concurrent.LinkedBlockingQueue. queue-size) worker (seque small-lbq slow-seq)] - (doall worker) + (dorun worker) (is (= worker slow-seq)) (Thread/sleep 250) ;; make sure agents have time to run or get blocked (let [queue-backlog (.size small-lbq)] diff --git a/test/clojure/test_clojure/multimethods.clj b/test/clojure/test_clojure/multimethods.clj index c75606d122..924b0bcbeb 100644 --- a/test/clojure/test_clojure/multimethods.clj +++ b/test/clojure/test_clojure/multimethods.clj @@ -196,7 +196,7 @@ (is (thrown? java.lang.IllegalArgumentException (bar ::rect ::rect)))) (testing "The prefers method returns empty table w/ no prefs" - (= {} (prefers bar))) + (is (= {} (prefers bar)))) (testing "Adding a preference to resolve it dispatches correctly" (prefer-method bar [::rect ::shape] [::shape ::rect]) (is (= :rect-shape (bar ::rect ::rect)))) @@ -228,7 +228,7 @@ (defmethod simple3 :a [x] :a) (defmethod simple3 :b [x] :b) (is (fn? (get-method simple3 :a))) - (is (= (:a ((get-method simple3 :a) 1)))) + (is (= :a ((get-method simple3 :a) 1))) (is (fn? (get-method simple3 :b))) - (is (= (:b ((get-method simple3 :b) 1)))) + (is (= :b ((get-method simple3 :b) 1))) (is (nil? (get-method simple3 :c))))) diff --git a/test/clojure/test_clojure/ns_libs.clj b/test/clojure/test_clojure/ns_libs.clj index 90600c21d5..1294dc9aab 100644 --- a/test/clojure/test_clojure/ns_libs.clj +++ b/test/clojure/test_clojure/ns_libs.clj @@ -66,15 +66,15 @@ (deftest naming-types (testing "you cannot use a name already referred from another namespace" - (is (thrown? IllegalStateException - #"String already refers to: class java.lang.String" - (definterface String))) - (is (thrown? IllegalStateException - #"StringBuffer already refers to: class java.lang.StringBuffer" - (deftype StringBuffer []))) - (is (thrown? IllegalStateException - #"Integer already refers to: class java.lang.Integer" - (defrecord Integer []))))) + (is (thrown-with-msg? IllegalStateException + #"String already refers to: class java.lang.String" + (definterface String))) + (is (thrown-with-msg? IllegalStateException + #"StringBuffer already refers to: class java.lang.StringBuffer" + (deftype StringBuffer []))) + (is (thrown-with-msg? IllegalStateException + #"Integer already refers to: class java.lang.Integer" + (defrecord Integer []))))) (deftest resolution (let [s (gensym)] diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj index 535e1776e9..e3adb277e9 100644 --- a/test/clojure/test_clojure/sequences.clj +++ b/test/clojure/test_clojure/sequences.clj @@ -23,7 +23,7 @@ ; and more... (deftest test-reduce-from-chunked-into-unchunked - (= [1 2 \a \b] (into [] (concat [1 2] "ab")))) + (is (= [1 2 \a \b] (into [] (concat [1 2] "ab"))))) (deftest test-reduce (let [int+ (fn [a b] (+ (int a) (int b))) From c8e248d84740dbe1538c6f86ff1ecc59e200d23a Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Sun, 18 Jan 2015 12:42:38 +0100 Subject: [PATCH 053/854] CLJ-1208: load own namespace in deftype/defrecord class initializer when :load-ns is true Signed-off-by: Stuart Halloway --- build.xml | 3 ++ src/clj/clojure/core_deftype.clj | 20 +++++++------ src/jvm/clojure/lang/Compiler.java | 28 ++++++++++++++++--- src/script/run_test.clj | 3 +- test/clojure/test_clojure/compilation.clj | 7 +++++ .../test_clojure/compilation/load_ns.clj | 7 +++++ 6 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 test/clojure/test_clojure/compilation/load_ns.clj diff --git a/build.xml b/build.xml index 186b666444..30378fdbdd 100644 --- a/build.xml +++ b/build.xml @@ -95,6 +95,7 @@ + @@ -104,6 +105,8 @@ depends="compile-tests" unless="maven.test.skip"> + diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index a808add8fa..23fb0c4a9f 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -63,7 +63,7 @@ methods (map (fn [[name params & body]] (cons name (maybe-destructured params body))) (apply concat (vals impls)))] - (when-let [bad-opts (seq (remove #{:no-print} (keys opts)))] + (when-let [bad-opts (seq (remove #{:no-print :load-ns} (keys opts)))] (throw (IllegalArgumentException. (apply print-str "Unsupported option(s) -" bad-opts)))) [interfaces methods opts])) @@ -148,8 +148,8 @@ (defn- emit-defrecord "Do not use this directly - use defrecord" {:added "1.2"} - [tagname name fields interfaces methods] - (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." name)) (meta name)) + [tagname cname fields interfaces methods opts] + (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." cname)) (meta cname)) interfaces (vec interfaces) interface-set (set (map resolve interfaces)) methodname-set (set (map first methods)) @@ -243,8 +243,9 @@ `(entrySet [this#] (set this#)))]) ] (let [[i m] (-> [interfaces methods] irecord eqhash iobj ilookup imap ijavamap)] - `(deftype* ~tagname ~classname ~(conj hinted-fields '__meta '__extmap) + `(deftype* ~(symbol (name (ns-name *ns*)) (name tagname)) ~classname ~(conj hinted-fields '__meta '__extmap) :implements ~(vec i) + ~@(mapcat identity opts) ~@m)))))) (defn- build-positional-factory @@ -372,7 +373,7 @@ `(let [] (declare ~(symbol (str '-> gname))) (declare ~(symbol (str 'map-> gname))) - ~(emit-defrecord name gname (vec hinted-fields) (vec interfaces) methods) + ~(emit-defrecord name gname (vec hinted-fields) (vec interfaces) methods opts) (import ~classname) ~(build-positional-factory gname classname fields) (defn ~(symbol (str 'map-> gname)) @@ -390,11 +391,12 @@ (defn- emit-deftype* "Do not use this directly - use deftype" - [tagname name fields interfaces methods] - (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." name)) (meta name)) + [tagname cname fields interfaces methods opts] + (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." cname)) (meta cname)) interfaces (conj interfaces 'clojure.lang.IType)] - `(deftype* ~tagname ~classname ~fields + `(deftype* ~(symbol (name (ns-name *ns*)) (name tagname)) ~classname ~fields :implements ~interfaces + ~@(mapcat identity opts) ~@methods))) (defmacro deftype @@ -471,7 +473,7 @@ fields (vec (map #(with-meta % nil) fields)) [field-args over] (split-at 20 fields)] `(let [] - ~(emit-deftype* name gname (vec hinted-fields) (vec interfaces) methods) + ~(emit-deftype* name gname (vec hinted-fields) (vec interfaces) methods opts) (import ~classname) ~(build-positional-factory gname classname fields) ~classname))) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 3ed3664082..6052360d05 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -78,6 +78,7 @@ public class Compiler implements Opcodes{ static final Symbol _AMP_ = Symbol.intern("&"); static final Symbol ISEQ = Symbol.intern("clojure.lang.ISeq"); +static final Keyword loadNs = Keyword.intern(null, "load-ns"); static final Keyword inlineKey = Keyword.intern(null, "inline"); static final Keyword inlineAritiesKey = Keyword.intern(null, "inline-arities"); static final Keyword staticKey = Keyword.intern(null, "static"); @@ -4091,6 +4092,8 @@ static public class ObjExpr implements Expr{ Object src; + IPersistentMap opts = PersistentHashMap.EMPTY; + final static Method voidctor = Method.getMethod("void ()"); protected IPersistentMap classMeta; protected boolean isStatic; @@ -4299,6 +4302,22 @@ void compile(String superName, String[] interfaceNames, boolean oneTimeUse) thro clinitgen.mark(endLabel); } */ + + if(isDeftype() && RT.booleanCast(RT.get(opts, loadNs))) { + String nsname = ((Symbol)RT.second(src)).getNamespace(); + if (!nsname.equals("clojure.core")) { + clinitgen.push("clojure.core"); + clinitgen.push("require"); + clinitgen.invokeStatic(RT_TYPE, Method.getMethod("clojure.lang.Var var(String,String)")); + clinitgen.invokeVirtual(VAR_TYPE,Method.getMethod("Object getRawRoot()")); + clinitgen.checkCast(IFN_TYPE); + clinitgen.push(nsname); + clinitgen.invokeStatic(SYMBOL_TYPE, Method.getMethod("clojure.lang.Symbol create(String)")); + clinitgen.invokeInterface(IFN_TYPE, Method.getMethod("Object invoke(Object)")); + clinitgen.pop(); + } + } + clinitgen.returnValue(); clinitgen.endMethod(); @@ -7540,7 +7559,7 @@ public Expr parse(C context, final Object frm) { ISeq rform = (ISeq) frm; //(deftype* tagname classname [fields] :implements [interfaces] :tag tagname methods*) rform = RT.next(rform); - String tagname = ((Symbol) rform.first()).toString(); + String tagname = ((Symbol) rform.first()).getName(); rform = rform.next(); Symbol classname = (Symbol) rform.first(); rform = rform.next(); @@ -7554,7 +7573,7 @@ public Expr parse(C context, final Object frm) { } ObjExpr ret = build((IPersistentVector)RT.get(opts,implementsKey,PersistentVector.EMPTY),fields,null,tagname, classname, - (Symbol) RT.get(opts,RT.TAG_KEY),rform, frm); + (Symbol) RT.get(opts,RT.TAG_KEY),rform, frm, opts); return ret; } } @@ -7578,7 +7597,7 @@ public Expr parse(C context, Object frm) { rform = RT.next(rform); - ObjExpr ret = build(interfaces, null, null, classname, Symbol.intern(classname), null, rform, frm); + ObjExpr ret = build(interfaces, null, null, classname, Symbol.intern(classname), null, rform, frm, null); if(frm instanceof IObj && ((IObj) frm).meta() != null) return new MetaExpr(ret, MapExpr .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) frm).meta())); @@ -7589,7 +7608,7 @@ public Expr parse(C context, Object frm) { static ObjExpr build(IPersistentVector interfaceSyms, IPersistentVector fieldSyms, Symbol thisSym, String tagName, Symbol className, - Symbol typeTag, ISeq methodForms, Object frm) { + Symbol typeTag, ISeq methodForms, Object frm, IPersistentMap opts) { NewInstanceExpr ret = new NewInstanceExpr(null); ret.src = frm; @@ -7597,6 +7616,7 @@ static ObjExpr build(IPersistentVector interfaceSyms, IPersistentVector fieldSym ret.classMeta = RT.meta(className); ret.internalName = ret.name.replace('.', '/'); ret.objtype = Type.getObjectType(ret.internalName); + ret.opts = opts; if(thisSym != null) ret.thisName = thisSym.name; diff --git a/src/script/run_test.clj b/src/script/run_test.clj index e4f9801532..4ff2b16328 100755 --- a/src/script/run_test.clj +++ b/src/script/run_test.clj @@ -2,7 +2,8 @@ (require '[clojure.test :as test] '[clojure.tools.namespace.find :as ns]) -(def namespaces (ns/find-namespaces-in-dir (java.io.File. "test"))) +(def namespaces (remove (read-string (System/getProperty "clojure.test-clojure.exclude-namespaces")) + (ns/find-namespaces-in-dir (java.io.File. "test")))) (doseq [ns namespaces] (require ns)) (let [summary (apply test/run-tests namespaces)] (System/exit (if (test/successful? summary) 0 -1))) diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index 013c439dc9..56ce5fd6a9 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -304,6 +304,13 @@ (class (clojure.test_clojure.compilation.examples.T.)) (class (clojure.test-clojure.compilation.examples/->T))))) +(deftest clj-1208 + ;; clojure.test-clojure.compilation.load-ns has not been loaded + ;; so this would fail if the deftype didn't load it in its static + ;; initializer as the implementation of f requires a var from + ;; that namespace + (is (= 1 (.f (clojure.test_clojure.compilation.load_ns.x.))))) + (deftest clj-1568 (let [compiler-fails-at? (fn [row col source] diff --git a/test/clojure/test_clojure/compilation/load_ns.clj b/test/clojure/test_clojure/compilation/load_ns.clj new file mode 100644 index 0000000000..90129d970d --- /dev/null +++ b/test/clojure/test_clojure/compilation/load_ns.clj @@ -0,0 +1,7 @@ +(ns clojure.test-clojure.compilation.load-ns) + +(defn a [] 1) +(defprotocol p (f [_])) +(deftype x [] + :load-ns true + p (f [_] (a))) From 6f4a49b9618e63ad24df430ea7a48830958f9c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ragnar=20Dahl=C3=A9n?= Date: Sun, 17 May 2015 23:07:23 +0100 Subject: [PATCH 054/854] CLJ-703: Remove call to FileDescriptor.sync in Compiler.writeClassFile Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 6052360d05..a26f8b14e1 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -7318,7 +7318,6 @@ static public void writeClassFile(String internalName, byte[] bytecode) throws I { cfs.write(bytecode); cfs.flush(); - cfs.getFD().sync(); } finally { From 9b6c3a50934204e272082848614267e57ea57de8 Mon Sep 17 00:00:00 2001 From: Ghadi Shayban Date: Tue, 23 Jun 2015 07:32:23 -0500 Subject: [PATCH 055/854] Clear reference to 'this' on all tail calls Removes calls to emitClearLocals(), which is a no-op. When the context is RETURN (indicating a tail call), and the operation is an InvokeExpr, StaticMethodExpr, or InstanceMethodExpr, clear the reference to 'this' which is in slot 0 of the locals. Edge-case: Inside the body of a try block, we cannot clear 'this' at the tail position as we might need to keep refs around for use in the catch or finally clauses. Introduces another truthy dynamic binding var to track position being inside a try block. In a try block with no catch or finally, use enclosing context and return a regular BodyExpr. Adds two helpers to emitClearThis and inTailCall. Adds test for the original reducer case and some try/catch cases. Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 102 +++++++++++++--------- test/clojure/test_clojure/compilation.clj | 18 ++++ test/clojure/test_clojure/reducers.clj | 4 + 3 files changed, 81 insertions(+), 43 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index a26f8b14e1..dd8e6ea68a 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -223,6 +223,7 @@ public class Compiler implements Opcodes{ static final public Var METHOD = Var.create(null).setDynamic(); //null or not +static final public Var IN_TRY_BLOCK = Var.create(null).setDynamic(); static final public Var IN_CATCH_FINALLY = Var.create(null).setDynamic(); static final public Var NO_RECUR = Var.create(null).setDynamic(); @@ -371,6 +372,10 @@ static boolean isSpecial(Object sym){ return specials.containsKey(sym); } +static boolean inTailCall(C context) { + return (context == C.RETURN) && (IN_TRY_BLOCK.deref() == null); +} + static Symbol resolveSymbol(Symbol sym){ //already qualified or classname? if(sym.name.indexOf('.') > 0) @@ -1002,12 +1007,13 @@ else if(instance != null && instance.hasJavaClass() && instance.getJavaClass() ! Symbol sym = (Symbol) RT.first(call); Symbol tag = tagOf(form); PersistentVector args = PersistentVector.EMPTY; + boolean tailPosition = inTailCall(context); for(ISeq s = RT.next(call); s != null; s = s.next()) args = args.cons(analyze(context == C.EVAL ? context : C.EXPRESSION, s.first())); if(c != null) - return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args); + return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args, tailPosition); else - return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args); + return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args, tailPosition); } } } @@ -1444,13 +1450,15 @@ static class InstanceMethodExpr extends MethodExpr{ public final int line; public final int column; public final Symbol tag; + public final boolean tailPosition; public final java.lang.reflect.Method method; final static Method invokeInstanceMethodMethod = Method.getMethod("Object invokeInstanceMethod(Object,String,Object[])"); - public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, String methodName, IPersistentVector args) + public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, + String methodName, IPersistentVector args, boolean tailPosition) { this.source = source; this.line = line; @@ -1459,6 +1467,7 @@ public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr this.methodName = methodName; this.target = target; this.tag = tag; + this.tailPosition = tailPosition; if(target.hasJavaClass() && target.getJavaClass() != null) { List methods = Reflector.getMethods(target.getJavaClass(), args.count(), methodName, false); @@ -1552,10 +1561,10 @@ public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){ gen.checkCast(type); MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args); gen.visitLineNumber(line, gen.mark()); - if(context == C.RETURN) + if(tailPosition) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); + method.emitClearThis(gen); } Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method)); if(method.getDeclaringClass().isInterface()) @@ -1626,12 +1635,14 @@ static class StaticMethodExpr extends MethodExpr{ public final int column; public final java.lang.reflect.Method method; public final Symbol tag; + public final boolean tailPosition; final static Method forNameMethod = Method.getMethod("Class classForName(String)"); final static Method invokeStaticMethodMethod = Method.getMethod("Object invokeStaticMethod(Class,String,Object[])"); final static Keyword warnOnBoxedKeyword = Keyword.intern("warn-on-boxed"); - public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, String methodName, IPersistentVector args) + public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, + String methodName, IPersistentVector args, boolean tailPosition) { this.c = c; this.methodName = methodName; @@ -1640,6 +1651,7 @@ public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c this.line = line; this.column = column; this.tag = tag; + this.tailPosition = tailPosition; List methods = Reflector.getMethods(c, args.count(), methodName, true); if(methods.isEmpty()) @@ -1778,10 +1790,10 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args); gen.visitLineNumber(line, gen.mark()); //Type type = Type.getObjectType(className.replace('.', '/')); - if(context == C.RETURN) + if(tailPosition) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); + method.emitClearThis(gen); } Type type = Type.getType(c); Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method)); @@ -2265,13 +2277,14 @@ public Expr parse(C context, Object frm) { } else { - if(bodyExpr == null) - try { - Var.pushThreadBindings(RT.map(NO_RECUR, true)); - bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); - } finally { - Var.popThreadBindings(); - } + if(bodyExpr == null) + try { + Var.pushThreadBindings(RT.map(NO_RECUR, true, IN_TRY_BLOCK, RT.T)); + bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); + } finally { + Var.popThreadBindings(); + } + if(Util.equals(op, CATCH)) { Class c = HostExpr.maybeClass(RT.second(f), false); @@ -2319,17 +2332,21 @@ public Expr parse(C context, Object frm) { } } } - if(bodyExpr == null) { - try - { - Var.pushThreadBindings(RT.map(NO_RECUR, true)); - bodyExpr = (new BodyExpr.Parser()).parse(C.EXPRESSION, RT.seq(body)); - } - finally - { - Var.popThreadBindings(); - } - } + if(bodyExpr == null) + { + // this codepath is hit when there is neither catch or finally, e.g. (try (expr)) + // return a body expr directly + try + { + Var.pushThreadBindings(RT.map(NO_RECUR, true)); + bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); + } + finally + { + Var.popThreadBindings(); + } + return bodyExpr; + } return new TryExpr(bodyExpr, catches, finallyExpr, retLocal, finallyLocal); @@ -2577,11 +2594,6 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ gen.newInstance(type); gen.dup(); MethodExpr.emitTypedArgs(objx, gen, ctor.getParameterTypes(), args); - if(context == C.RETURN) - { - ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); - } gen.invokeConstructor(type, new Method("", Type.getConstructorDescriptor(ctor))); } else @@ -2589,11 +2601,6 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ gen.push(destubClassName(c.getName())); gen.invokeStatic(RT_TYPE, forNameMethod); MethodExpr.emitArgsAsArray(args, objx, gen); - if(context == C.RETURN) - { - ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); - } gen.invokeStatic(REFLECTOR_TYPE, invokeConstructorMethod); } if(context == C.STATEMENT) @@ -3570,6 +3577,7 @@ static class InvokeExpr implements Expr{ public final IPersistentVector args; public final int line; public final int column; + public final boolean tailPosition; public final String source; public boolean isProtocol = false; public boolean isDirect = false; @@ -3579,12 +3587,14 @@ static class InvokeExpr implements Expr{ static Keyword onKey = Keyword.intern("on"); static Keyword methodMapKey = Keyword.intern("method-map"); - public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args) { + public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args, boolean tailPosition) { this.source = source; this.fexpr = fexpr; this.args = args; this.line = line; this.column = column; + this.tailPosition = tailPosition; + if(fexpr instanceof VarExpr) { Var fvar = ((VarExpr)fexpr).var; @@ -3736,10 +3746,10 @@ void emitArgsAndCall(int firstArgToEmit, C context, ObjExpr objx, GeneratorAdapt } gen.visitLineNumber(line, gen.mark()); - if(context == C.RETURN) + if(tailPosition) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); + method.emitClearThis(gen); } gen.invokeInterface(IFN_TYPE, new Method("invoke", OBJECT_TYPE, ARG_TYPES[Math.min(MAX_POSITIONAL_ARITY + 1, @@ -3755,6 +3765,7 @@ public Class getJavaClass() { } static public Expr parse(C context, ISeq form) { + boolean tailPosition = inTailCall(context); if(context != C.EVAL) context = C.EXPRESSION; Expr fexpr = analyze(context, form.first()); @@ -3817,7 +3828,7 @@ static public Expr parse(C context, ISeq form) { // throw new IllegalArgumentException( // String.format("No more than %d args supported", MAX_POSITIONAL_ARITY)); - return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args); + return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args, tailPosition); } } @@ -5758,6 +5769,11 @@ void emitClearLocalsOld(GeneratorAdapter gen){ } } } + + void emitClearThis(GeneratorAdapter gen) { + gen.visitInsn(Opcodes.ACONST_NULL); + gen.visitVarInsn(Opcodes.ASTORE, 0); + } } public static class LocalBinding{ @@ -6183,14 +6199,14 @@ public Expr parse(C context, Object frm) { { if(recurMismatches != null && RT.booleanCast(recurMismatches.nth(i/2))) { - init = new StaticMethodExpr("", 0, 0, null, RT.class, "box", RT.vector(init)); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "box", RT.vector(init), false); if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref())) RT.errPrintWriter().println("Auto-boxing loop arg: " + sym); } else if(maybePrimitiveType(init) == int.class) - init = new StaticMethodExpr("", 0, 0, null, RT.class, "longCast", RT.vector(init)); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "longCast", RT.vector(init), false); else if(maybePrimitiveType(init) == float.class) - init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init)); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init), false); } //sequential enhancement of env (like Lisp let*) try diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index 56ce5fd6a9..593c38f42a 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -342,3 +342,21 @@ (deftest clj-1399 ;; throws an exception on failure (is (eval `(fn [] ~(CLJ1399. 1))))) + +(deftest CLJ-1250-this-clearing + (let [closed-over-in-catch (let [x :foo] + (fn [] + (try + (throw (Exception. "boom")) + (catch Exception e + x)))) ;; x should remain accessible to the fn + + a (atom nil) + closed-over-in-finally (fn [] + (try + :ret + (finally + (reset! a :run))))] + (is (= :foo (closed-over-in-catch))) + (is (= :ret (closed-over-in-finally))) + (is (= :run @a)))) diff --git a/test/clojure/test_clojure/reducers.clj b/test/clojure/test_clojure/reducers.clj index c2852ccb2d..a884c85179 100644 --- a/test/clojure/test_clojure/reducers.clj +++ b/test/clojure/test_clojure/reducers.clj @@ -89,3 +89,7 @@ ([ret k v] (when (= k k-fail) (throw (IndexOutOfBoundsException.))))) (zipmap (range test-map-count) (repeat :dummy))))))) + +(deftest test-closed-over-clearing + ;; this will throw OutOfMemory without proper reference clearing + (is (number? (reduce + 0 (r/map identity (range 1e8)))))) From fbeea7ac4b8468f5404d4ea88d91580c87b0a09a Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 17 Jul 2015 19:27:02 -0500 Subject: [PATCH 056/854] [maven-release-plugin] prepare release clojure-1.8.0-alpha2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80692a98b8..e77c3c8665 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-alpha2 http://clojure.org/ Clojure core environment and runtime library. From ed7419427d17ccff48a37b75ed5956280784a145 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 17 Jul 2015 19:27:02 -0500 Subject: [PATCH 057/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e77c3c8665..80692a98b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-alpha2 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From b8607d5870202034679cda50ec390426827ff692 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 20 Jul 2015 10:31:38 -0400 Subject: [PATCH 058/854] extend Tuple equiv to all arities. extend c.c/vector to arity-6 --- src/clj/clojure/core.clj | 6 +++-- src/jvm/clojure/lang/Tuple.java | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 5bd2e59121..026116e11c 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -345,8 +345,10 @@ ([a b] [a b]) ([a b c] [a b c]) ([a b c d] [a b c d]) - ([a b c d & args] - (. clojure.lang.LazilyPersistentVector (create (cons a (cons b (cons c (cons d args)))))))) + ([a b c d e] [a b c d e]) + ([a b c d e f] [a b c d e f]) + ([a b c d e f & args] + (. clojure.lang.LazilyPersistentVector (create (cons a (cons b (cons c (cons d (cons e (cons f args)))))))))) (defn vec "Creates a new vector containing the contents of coll. Java arrays diff --git a/src/jvm/clojure/lang/Tuple.java b/src/jvm/clojure/lang/Tuple.java index e58eb6abce..88d67fd81f 100644 --- a/src/jvm/clojure/lang/Tuple.java +++ b/src/jvm/clojure/lang/Tuple.java @@ -299,6 +299,18 @@ public IPersistentVector cons(Object o){ return create(v0, v1, v2, v3, o); } + public boolean equiv(Object obj){ + if(this == obj) return true; + if(obj instanceof T4) { + T4 o = (T4) obj; + return Util.equiv(v0, o.v0) && + Util.equiv(v1, o.v1) && + Util.equiv(v2, o.v2) && + Util.equiv(v3, o.v3); + } + return super.equiv(obj); + } + } static public class T5 extends ATuple{ @@ -341,6 +353,19 @@ public IPersistentVector cons(Object o){ return create(v0, v1, v2, v3, v4, o); } + public boolean equiv(Object obj){ + if(this == obj) return true; + if(obj instanceof T5) { + T5 o = (T5) obj; + return Util.equiv(v0, o.v0) && + Util.equiv(v1, o.v1) && + Util.equiv(v2, o.v2) && + Util.equiv(v3, o.v3) && + Util.equiv(v4, o.v4); + } + return super.equiv(obj); + } + } static public class T6 extends ATuple{ @@ -387,5 +412,19 @@ public IPersistentVector cons(Object o){ return vec().cons(o); } + public boolean equiv(Object obj){ + if(this == obj) return true; + if(obj instanceof T6) { + T6 o = (T6) obj; + return Util.equiv(v0, o.v0) && + Util.equiv(v1, o.v1) && + Util.equiv(v2, o.v2) && + Util.equiv(v3, o.v3) && + Util.equiv(v4, o.v4) && + Util.equiv(v5, o.v5); + } + return super.equiv(obj); + } + } } From 684cd4117bb2cf463c95293855a0dff52134bdcd Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 20 Jul 2015 14:47:57 -0400 Subject: [PATCH 059/854] kvreduce factorization --- src/clj/clojure/core.clj | 19 +-- src/jvm/clojure/lang/APersistentVector.java | 2 +- src/jvm/clojure/lang/PersistentArrayMap.java | 2 +- src/jvm/clojure/lang/PersistentHashMap.java | 2 +- src/jvm/clojure/lang/PersistentTreeMap.java | 2 +- src/jvm/clojure/lang/PersistentVector.java | 2 +- src/jvm/clojure/lang/Tuple.java | 142 ++++++++++++++----- 7 files changed, 115 insertions(+), 56 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 026116e11c..0400435d2a 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6532,25 +6532,10 @@ [amap f init] (reduce (fn [ret [k v]] (f ret k v)) init amap)) - clojure.lang.PersistentHashMap + clojure.lang.IKVReduce (kv-reduce [amap f init] - (.kvreduce amap f init)) - - clojure.lang.PersistentArrayMap - (kv-reduce - [amap f init] - (.kvreduce amap f init)) - - clojure.lang.PersistentTreeMap - (kv-reduce - [amap f init] - (.kvreduce amap f init)) - - clojure.lang.PersistentVector - (kv-reduce - [vec f init] - (.kvreduce vec f init))) + (.kvreduce amap f init))) (defn reduce-kv "Reduces an associative collection. f should be a function of 3 diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java index 2add05459f..804e45b9b0 100644 --- a/src/jvm/clojure/lang/APersistentVector.java +++ b/src/jvm/clojure/lang/APersistentVector.java @@ -430,7 +430,7 @@ else if(count() > v.count()) return 0; } - static class Seq extends ASeq implements IndexedSeq, IReduce{ +static class Seq extends ASeq implements IndexedSeq, IReduce{ //todo - something more efficient final IPersistentVector v; final int i; diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java index 57fcb838ff..009ea46c36 100644 --- a/src/jvm/clojure/lang/PersistentArrayMap.java +++ b/src/jvm/clojure/lang/PersistentArrayMap.java @@ -26,7 +26,7 @@ * null keys and values are ok, but you won't be able to distinguish a null value via valAt - use contains/entryAt */ -public class PersistentArrayMap extends APersistentMap implements IObj, IEditableCollection, IMapIterable { +public class PersistentArrayMap extends APersistentMap implements IObj, IEditableCollection, IMapIterable, IKVReduce{ final Object[] array; static final int HASHTABLE_THRESHOLD = 16; diff --git a/src/jvm/clojure/lang/PersistentHashMap.java b/src/jvm/clojure/lang/PersistentHashMap.java index bc2a9cac0a..f5061dc41c 100644 --- a/src/jvm/clojure/lang/PersistentHashMap.java +++ b/src/jvm/clojure/lang/PersistentHashMap.java @@ -25,7 +25,7 @@ Any errors are my own */ -public class PersistentHashMap extends APersistentMap implements IEditableCollection, IObj, IMapIterable { +public class PersistentHashMap extends APersistentMap implements IEditableCollection, IObj, IMapIterable, IKVReduce { final int count; final INode root; diff --git a/src/jvm/clojure/lang/PersistentTreeMap.java b/src/jvm/clojure/lang/PersistentTreeMap.java index bf1e61176a..4309977518 100644 --- a/src/jvm/clojure/lang/PersistentTreeMap.java +++ b/src/jvm/clojure/lang/PersistentTreeMap.java @@ -22,7 +22,7 @@ * See Okasaki, Kahrs, Larsen et al */ -public class PersistentTreeMap extends APersistentMap implements IObj, Reversible, Sorted{ +public class PersistentTreeMap extends APersistentMap implements IObj, Reversible, Sorted, IKVReduce{ public final Comparator comp; public final Node tree; diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java index a419312a44..adc39c8492 100644 --- a/src/jvm/clojure/lang/PersistentVector.java +++ b/src/jvm/clojure/lang/PersistentVector.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; -public class PersistentVector extends APersistentVector implements IObj, IEditableCollection, IReduce{ +public class PersistentVector extends APersistentVector implements IObj, IEditableCollection, IReduce, IKVReduce{ public static class Node implements Serializable { transient public final AtomicReference edit; diff --git a/src/jvm/clojure/lang/Tuple.java b/src/jvm/clojure/lang/Tuple.java index 88d67fd81f..736982dce4 100644 --- a/src/jvm/clojure/lang/Tuple.java +++ b/src/jvm/clojure/lang/Tuple.java @@ -73,7 +73,7 @@ public static IPersistentVector createFromColl(Object coll){ return createFromArray(RT.toArray(coll)); } -static public abstract class ATuple extends APersistentVector implements IObj, IEditableCollection{ +static public abstract class ATuple extends APersistentVector implements IObj, IEditableCollection, IKVReduce, IReduceInit{ PersistentVector vec(){ return PersistentVector.adopt(toArray()); } @@ -103,26 +103,46 @@ public IPersistentStack pop(){ public ITransientCollection asTransient(){ return vec().asTransient(); } + + public Object kvreduce(IFn f, Object init){ + for(int i=0;i Date: Mon, 20 Jul 2015 15:20:42 -0400 Subject: [PATCH 060/854] kvreduce factorization --- src/jvm/clojure/lang/IKVReduce.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/jvm/clojure/lang/IKVReduce.java diff --git a/src/jvm/clojure/lang/IKVReduce.java b/src/jvm/clojure/lang/IKVReduce.java new file mode 100644 index 0000000000..27a1d3ac4e --- /dev/null +++ b/src/jvm/clojure/lang/IKVReduce.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich 7/20/15 */ + +package clojure.lang; + +public interface IKVReduce{ +Object kvreduce(IFn f, Object init); +} From 838302612551ef6a50a8adbdb9708cb1362b0898 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Sat, 25 Jul 2015 12:36:33 -0400 Subject: [PATCH 061/854] Disable tuple impl, leaving integration. APersistentVector implements IMapEntry --- src/jvm/clojure/lang/APersistentVector.java | 33 +- src/jvm/clojure/lang/Compiler.java | 16 +- .../clojure/lang/LazilyPersistentVector.java | 16 +- src/jvm/clojure/lang/PersistentArrayMap.java | 2 +- src/jvm/clojure/lang/PersistentHashMap.java | 6 +- src/jvm/clojure/lang/PersistentStructMap.java | 2 +- src/jvm/clojure/lang/Tuple.java | 493 +----------------- 7 files changed, 66 insertions(+), 502 deletions(-) diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java index 804e45b9b0..7034f18bf5 100644 --- a/src/jvm/clojure/lang/APersistentVector.java +++ b/src/jvm/clojure/lang/APersistentVector.java @@ -16,7 +16,7 @@ import java.util.*; public abstract class APersistentVector extends AFn implements IPersistentVector, Iterable, - List, + List, IMapEntry, RandomAccess, Comparable, Serializable, IHashEq { int _hash = -1; @@ -126,6 +126,21 @@ else if(obj instanceof List) } +@Override +public Object getKey(){ + return key(); +} + +@Override +public Object getValue(){ + return val(); +} + +@Override +public Object setValue(Object value){ + throw new UnsupportedOperationException(); +} + public boolean equals(Object obj){ if(obj == this) return true; @@ -320,7 +335,7 @@ public IMapEntry entryAt(Object key){ { int i = ((Number) key).intValue(); if(i >= 0 && i < count()) - return Tuple.create(key, nth(i)); + return (IMapEntry) Tuple.create(key, nth(i)); } return null; } @@ -430,6 +445,20 @@ else if(count() > v.count()) return 0; } +@Override +public Object key(){ + if(count() == 2) + return nth(0); + throw new UnsupportedOperationException(); +} + +@Override +public Object val(){ + if(count() == 2) + return nth(1); + throw new UnsupportedOperationException(); +} + static class Seq extends ASeq implements IndexedSeq, IReduce{ //todo - something more efficient final IPersistentVector v; diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index dd8e6ea68a..ab8f5463cb 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -156,12 +156,12 @@ public class Compiler implements Opcodes{ final static Type IOBJ_TYPE = Type.getType(IObj.class); final static Type TUPLE_TYPE = Type.getType(Tuple.class); final static Method createTupleMethods[] = {Method.getMethod("clojure.lang.IPersistentVector create()"), - Method.getMethod("clojure.lang.Tuple$T1 create(Object)"), - Method.getMethod("clojure.lang.Tuple$T2 create(Object,Object)"), - Method.getMethod("clojure.lang.Tuple$T3 create(Object,Object,Object)"), - Method.getMethod("clojure.lang.Tuple$T4 create(Object,Object,Object,Object)"), - Method.getMethod("clojure.lang.Tuple$T5 create(Object,Object,Object,Object,Object)"), - Method.getMethod("clojure.lang.Tuple$T6 create(Object,Object,Object,Object,Object,Object)") + Method.getMethod("clojure.lang.IPersistentVector create(Object)"), + Method.getMethod("clojure.lang.IPersistentVector create(Object,Object)"), + Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object)"), + Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object,Object)"), + Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object,Object,Object)"), + Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object,Object,Object,Object)") }; private static final Type[][] ARG_TYPES; @@ -2939,7 +2939,7 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ if(coll instanceof IPersistentList) gen.getStatic(LIST_TYPE, "EMPTY", EMPTY_LIST_TYPE); else if(coll instanceof IPersistentVector) - gen.getStatic(TUPLE_TYPE, "EMPTY", IVECTOR_TYPE); + gen.getStatic(VECTOR_TYPE, "EMPTY", VECTOR_TYPE); else if(coll instanceof IPersistentMap) gen.getStatic(HASHMAP_TYPE, "EMPTY", HASHMAP_TYPE); else if(coll instanceof IPersistentSet) @@ -3233,7 +3233,7 @@ static public Expr parse(C context, IPersistentVector form) { .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) form).meta())); else if (constant) { - IPersistentVector rv = Tuple.EMPTY; + IPersistentVector rv = PersistentVector.EMPTY; for(int i =0;i= 0) - return Tuple.create(array[i],array[i+1]); + return (IMapEntry) Tuple.create(array[i],array[i+1]); return null; } diff --git a/src/jvm/clojure/lang/PersistentHashMap.java b/src/jvm/clojure/lang/PersistentHashMap.java index f5061dc41c..5c4ad37c7f 100644 --- a/src/jvm/clojure/lang/PersistentHashMap.java +++ b/src/jvm/clojure/lang/PersistentHashMap.java @@ -128,7 +128,7 @@ public boolean containsKey(Object key){ public IMapEntry entryAt(Object key){ if(key == null) - return hasNull ? Tuple.create(null, nullValue) : null; + return hasNull ? (IMapEntry) Tuple.create(null, nullValue) : null; return (root != null) ? root.find(0, hash(key), key) : null; } @@ -766,7 +766,7 @@ public IMapEntry find(int shift, int hash, Object key){ if(keyOrNull == null) return ((INode) valOrNode).find(shift + 5, hash, key); if(Util.equiv(key, keyOrNull)) - return Tuple.create(keyOrNull, valOrNode); + return (IMapEntry) Tuple.create(keyOrNull, valOrNode); return null; } @@ -967,7 +967,7 @@ public IMapEntry find(int shift, int hash, Object key){ if(idx < 0) return null; if(Util.equiv(key, array[idx])) - return Tuple.create(array[idx], array[idx+1]); + return (IMapEntry) Tuple.create(array[idx], array[idx+1]); return null; } diff --git a/src/jvm/clojure/lang/PersistentStructMap.java b/src/jvm/clojure/lang/PersistentStructMap.java index f083c062b0..5ca3f2ce48 100644 --- a/src/jvm/clojure/lang/PersistentStructMap.java +++ b/src/jvm/clojure/lang/PersistentStructMap.java @@ -132,7 +132,7 @@ public IMapEntry entryAt(Object key){ Map.Entry e = def.keyslots.entryAt(key); if(e != null) { - return Tuple.create(e.getKey(), vals[(Integer) e.getValue()]); + return (IMapEntry) Tuple.create(e.getKey(), vals[(Integer) e.getValue()]); } return ext.entryAt(key); } diff --git a/src/jvm/clojure/lang/Tuple.java b/src/jvm/clojure/lang/Tuple.java index 736982dce4..443cfbadce 100644 --- a/src/jvm/clojure/lang/Tuple.java +++ b/src/jvm/clojure/lang/Tuple.java @@ -18,487 +18,20 @@ public class Tuple{ static final int MAX_SIZE = 6; - static public IPersistentVector EMPTY = new T0(); - public static IPersistentVector create(){return EMPTY;} - public static T1 create(Object v0){return new T1(v0);} - public static T2 create(Object v0, Object v1){return new T2(v0, v1);} - public static T3 create(Object v0, Object v1, Object v2){return new T3(v0, v1, v2);} - public static T4 create(Object v0, Object v1, Object v2, Object v3){return new T4(v0, v1, v2, v3);} - public static T5 create(Object v0, Object v1, Object v2, Object v3, Object v4){return new T5(v0, v1, v2, v3, v4);} - public static T6 create(Object v0, Object v1, Object v2, Object v3, Object v4, Object v5) - {return new T6(v0, v1, v2, v3, v4, v5);} + public static IPersistentVector create(){return PersistentVector.EMPTY;} + public static IPersistentVector create(Object v0) + {return RT.vector(v0);} + public static IPersistentVector create(Object v0, Object v1) + {return RT.vector(v0, v1);} + public static IPersistentVector create(Object v0, Object v1, Object v2) + {return RT.vector(v0, v1, v2);} + public static IPersistentVector create(Object v0, Object v1, Object v2, Object v3) + {return RT.vector(v0, v1, v2, v3);} + public static IPersistentVector create(Object v0, Object v1, Object v2, Object v3, Object v4) + {return RT.vector(v0, v1, v2, v3, v4);} + public static IPersistentVector create(Object v0, Object v1, Object v2, Object v3, Object v4, Object v5) + {return RT.vector(v0, v1, v2, v3, v4, v5);} - public static IPersistentVector createFromArray(Object[] items){ - if(items.length <= Tuple.MAX_SIZE){ - switch(items.length){ - case 0: - return EMPTY; - case 1: - return create(items[0]); - case 2: - return create(items[0], items[1]); - case 3: - return create(items[0], items[1], items[2]); - case 4: - return create(items[0], items[1], items[2], items[3]); - case 5: - return create(items[0], items[1], items[2], items[3], items[4]); - case 6: - return create(items[0], items[1], items[2], items[3], items[4], items[5]); - } - } - throw new IllegalAccessError("Too large an array for tuple"); - } - public static IPersistentVector createFromColl(Object coll){ - if(coll instanceof RandomAccess) { - switch(((Collection) coll).size()){ - case 0: - return EMPTY; - case 1: - return create(RT.nth(coll, 0)); - case 2: - return create(RT.nth(coll,0), RT.nth(coll,1)); - case 3: - return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2)); - case 4: - return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2), RT.nth(coll,3)); - case 5: - return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2), RT.nth(coll,3), RT.nth(coll,4)); - case 6: - return create(RT.nth(coll,0), RT.nth(coll,1), RT.nth(coll,2), RT.nth(coll,3), RT.nth(coll,4), RT.nth(coll,5)); - } - } - return createFromArray(RT.toArray(coll)); - } - -static public abstract class ATuple extends APersistentVector implements IObj, IEditableCollection, IKVReduce, IReduceInit{ - PersistentVector vec(){ - return PersistentVector.adopt(toArray()); - } - - public IObj withMeta(IPersistentMap meta){ - if(meta == null) - return this; - return vec().withMeta(meta); - } - - public IPersistentMap meta(){ - return null; - } - - public IPersistentVector assocN(int i, Object val){ - return vec().assocN(i, val); - } - - public IPersistentCollection empty(){ - return EMPTY; - } - - public IPersistentStack pop(){ - return vec().pop(); - } - - public ITransientCollection asTransient(){ - return vec().asTransient(); - } - - public Object kvreduce(IFn f, Object init){ - for(int i=0;i Date: Tue, 28 Jul 2015 11:44:53 -0400 Subject: [PATCH 062/854] revisit direct linking --- build.xml | 3 +- clojure.iml | 4 +- src/clj/clojure/core.clj | 13 +- src/jvm/clojure/lang/Compiler.java | 279 +++++++++++++++-------- src/jvm/clojure/lang/PersistentList.java | 30 ++- 5 files changed, 221 insertions(+), 108 deletions(-) diff --git a/build.xml b/build.xml index 30378fdbdd..c2880ac15f 100644 --- a/build.xml +++ b/build.xml @@ -51,7 +51,8 @@ - + + diff --git a/clojure.iml b/clojure.iml index b11b106d3e..e29b996c12 100644 --- a/clojure.iml +++ b/clojure.iml @@ -1,5 +1,6 @@ + @@ -20,4 +21,5 @@ - \ No newline at end of file + + diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 0400435d2a..4da25a409d 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -82,11 +82,11 @@ conj (fn ^:static conj ([] []) ([coll] coll) - ([coll x] (. clojure.lang.RT (conj coll x))) + ([coll x] (clojure.lang.RT/conj coll x)) ([coll x & xs] (if xs - (recur (conj coll x) (first xs) (next xs)) - (conj coll x))))) + (recur (clojure.lang.RT/conj coll x) (first xs) (next xs)) + (clojure.lang.RT/conj coll x))))) (def ^{:doc "Same as (first (next x))" @@ -188,9 +188,9 @@ :static true} assoc (fn ^:static assoc - ([map key val] (. clojure.lang.RT (assoc map key val))) + ([map key val] (clojure.lang.RT/assoc map key val)) ([map key val & kvs] - (let [ret (assoc map key val)] + (let [ret (clojure.lang.RT/assoc map key val)] (if kvs (if (next kvs) (recur ret (first kvs) (second kvs) (nnext kvs)) @@ -317,7 +317,8 @@ (list 'def (with-meta name m) ;;todo - restore propagation of fn name ;;must figure out how to convey primitive hints to self calls first - (cons `fn fdecl) )))) + ;;(cons `fn fdecl) + (with-meta (cons `fn fdecl) {:rettag (:tag m)}))))) (. (var defn) (setMacro)) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index ab8f5463cb..d8329bb910 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -247,12 +247,13 @@ public class Compiler implements Opcodes{ Symbol.intern("*compile-files*"), Boolean.FALSE).setDynamic(); static final public Var INSTANCE = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")), - Symbol.intern("instance?")); + Symbol.intern("instance?")); static final public Var ADD_ANNOTATIONS = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")), Symbol.intern("add-annotations")); static final public Keyword disableLocalsClearingKey = Keyword.intern("disable-locals-clearing"); +static final public Keyword directLinkingKey = Keyword.intern("direct-linking"); static final public Keyword elideMetaKey = Keyword.intern("elide-meta"); static final public Var COMPILER_OPTIONS; @@ -916,6 +917,7 @@ else if(paramType == char.class) } else { +// System.out.println("NOT fnexpr for defn var: " + var + "init: " + init.getClass()); Method m = null; gen.checkCast(NUMBER_TYPE); if(RT.booleanCast(RT.UNCHECKED_MATH.deref())) @@ -1080,7 +1082,9 @@ else if(stringOk && form instanceof String) */ static Class tagToClass(Object tag) { Class c = null; - if(tag instanceof Symbol) + if(tag instanceof ISeq && RT.first(tag).equals(QUOTE)) + tag = RT.second(tag); + if(tag instanceof Symbol) { Symbol sym = (Symbol) tag; if(sym.ns == null) //if ns-qualified can't be classname @@ -3428,10 +3432,10 @@ static class StaticInvokeExpr implements Expr, MaybePrimitiveExpr{ public final Type[] paramtypes; public final IPersistentVector args; public final boolean variadic; - public final Symbol tag; + public final Object tag; StaticInvokeExpr(Type target, Class retClass, Class[] paramclasses, Type[] paramtypes, boolean variadic, - IPersistentVector args,Symbol tag){ + IPersistentVector args,Object tag){ this.target = target; this.retClass = retClass; this.paramclasses = paramclasses; @@ -3501,74 +3505,65 @@ private Type getReturnType(){ return Type.getType(retClass); } - public static Expr parse(Var v, ISeq args, Symbol tag) { - IPersistentCollection paramlists = (IPersistentCollection) RT.get(v.meta(), arglistsKey); - if(paramlists == null) - throw new IllegalStateException("Can't call static fn with no arglists: " + v); - IPersistentVector paramlist = null; - int argcount = RT.count(args); + public static Expr parse(Var v, ISeq args, Object tag) { + if(!v.isBound() || v.get() == null) + { +// System.out.println("Not bound: " + v); + return null; + } + Class c = v.get().getClass(); + String cname = c.getName(); +// System.out.println("Class: " + cname); + + java.lang.reflect.Method[] allmethods = c.getMethods(); + boolean variadic = false; - for(ISeq aseq = RT.seq(paramlists); aseq != null; aseq = aseq.next()) + int argcount = RT.count(args); + java.lang.reflect.Method method = null; + for(java.lang.reflect.Method m : allmethods) { - if(!(aseq.first() instanceof IPersistentVector)) - throw new IllegalStateException("Expected vector arglist, had: " + aseq.first()); - IPersistentVector alist = (IPersistentVector) aseq.first(); - if(alist.count() > 1 - && alist.nth(alist.count() - 2).equals(_AMP_)) + //System.out.println(m); + if(Modifier.isStatic(m.getModifiers()) && m.getName().equals("invokeStatic")) { - if(argcount >= alist.count() - 2) + Class[] params = m.getParameterTypes(); + if(argcount == params.length) { - paramlist = alist; + method = m; + variadic = argcount > 0 && params[params.length-1] == ISeq.class; + break; + } + else if(argcount > params.length + && params.length > 0 + && params[params.length-1] == ISeq.class) + { + method = m; variadic = true; + break; } } - else if(alist.count() == argcount) - { - paramlist = alist; - variadic = false; - break; - } } + if(method == null) + return null; - if(paramlist == null) - throw new IllegalArgumentException("Invalid arity - can't call: " + v + " with " + argcount + " args"); - - Class retClass = tagClass(tagOf(paramlist)); + Class retClass = method.getReturnType(); - ArrayList paramClasses = new ArrayList(); - ArrayList paramTypes = new ArrayList(); + Class[] paramClasses = method.getParameterTypes(); + Type[] paramTypes = new Type[paramClasses.length]; - if(variadic) + for(int i = 0;i -1 && argcount >= restOffset)) + return tagOf(sig); + } + return null; + } + public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args, boolean tailPosition) { this.source = source; this.fexpr = fexpr; @@ -3629,17 +3637,9 @@ public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, I if (tag != null) { this.tag = tag; } else if (fexpr instanceof VarExpr) { - Object arglists = RT.get(RT.meta(((VarExpr) fexpr).var), arglistsKey); - Object sigTag = null; - for(ISeq s = RT.seq(arglists); s != null; s = s.next()) { - APersistentVector sig = (APersistentVector) s.first(); - int restOffset = sig.indexOf(_AMP_); - if (args.count() == sig.count() || (restOffset > -1 && args.count() >= restOffset)) { - sigTag = tagOf(sig); - break; - } - } - + Var v = ((VarExpr) fexpr).var; + Object arglists = RT.get(RT.meta(v), arglistsKey); + Object sigTag = sigTag(args.count(),v); this.tag = sigTag == null ? ((VarExpr) fexpr).tag : sigTag; } else { this.tag = null; @@ -3782,14 +3782,28 @@ static public Expr parse(C context, ISeq form) { } } -// if(fexpr instanceof VarExpr && context != C.EVAL) -// { -// Var v = ((VarExpr)fexpr).var; -// if(RT.booleanCast(RT.get(RT.meta(v),staticKey))) -// { -// return StaticInvokeExpr.parse(v, RT.next(form), tagOf(form)); -// } -// } + if(RT.booleanCast(getCompilerOption(directLinkingKey)) + && fexpr instanceof VarExpr + && context != C.EVAL) + { + Var v = ((VarExpr)fexpr).var; + if(!v.isDynamic()) + { + Symbol formtag = tagOf(form); + Object arglists = RT.get(RT.meta(v), arglistsKey); + int arity = RT.count(form.next()); + Object sigtag = sigTag(arity, v); + Object vtag = RT.get(RT.meta(v), RT.TAG_KEY); + Expr ret = StaticInvokeExpr + .parse(v, RT.next(form), formtag != null ? formtag : sigtag != null ? sigtag : vtag); + if(ret != null) + { +// System.out.println("invoke direct: " + v); + return ret; + } +// System.out.println("NOT direct: " + v); + } + } if(fexpr instanceof VarExpr && context != C.EVAL) { @@ -3851,6 +3865,7 @@ static public class FnExpr extends ObjExpr{ IPersistentCollection methods; private boolean hasPrimSigs; private boolean hasMeta; + private boolean hasEnclosingMethod; // String superName = null; public FnExpr(Object tag){ @@ -3894,8 +3909,11 @@ protected void emitMethods(ClassVisitor cv){ static Expr parse(C context, ISeq form, String name) { ISeq origForm = form; FnExpr fn = new FnExpr(tagOf(form)); + Keyword retkey = Keyword.intern(null, "rettag"); + Object rettag = RT.get(RT.meta(form), retkey); fn.src = form; ObjMethod enclosingMethod = (ObjMethod) METHOD.deref(); + fn.hasEnclosingMethod = enclosingMethod != null; if(((IMeta) form.first()).meta() != null) { fn.onceOnly = RT.booleanCast(RT.get(RT.meta(form.first()), Keyword.intern(null, "once"))); @@ -3942,7 +3960,6 @@ VAR_CALLSITES, emptyVarCallSites(), if(nm != null) { fn.thisName = nm.name; - fn.isStatic = false; //RT.booleanCast(RT.get(nm.meta(), staticKey)); form = RT.cons(FN, RT.next(RT.next(form))); } @@ -3954,9 +3971,15 @@ VAR_CALLSITES, emptyVarCallSites(), fn.column = columnDeref(); FnMethod[] methodArray = new FnMethod[MAX_POSITIONAL_ARITY + 1]; FnMethod variadicMethod = null; + boolean usesThis = false; for(ISeq s = RT.next(form); s != null; s = RT.next(s)) { - FnMethod f = FnMethod.parse(fn, (ISeq) RT.first(s), fn.isStatic); + FnMethod f = FnMethod.parse(fn, (ISeq) RT.first(s), rettag); + if(f.usesThis) + { +// System.out.println(fn.name + " use this"); + usesThis = true; + } if(f.isVariadic()) { if(variadicMethod == null) @@ -3979,8 +4002,8 @@ else if(methodArray[f.reqParms.count()] == null) "Can't have fixed arity function with more params than variadic function"); } - if(fn.isStatic && fn.closes.count() > 0) - throw new IllegalArgumentException("static fns can't be closures"); + fn.canBeDirect = !fn.hasEnclosingMethod && fn.closes.count() == 0 && !usesThis; + IPersistentCollection methods = null; for(int i = 0; i < methodArray.length; i++) if(methodArray[i] != null) @@ -3988,6 +4011,20 @@ else if(methodArray[f.reqParms.count()] == null) if(variadicMethod != null) methods = RT.conj(methods, variadicMethod); + if(fn.canBeDirect){ + for(FnMethod fm : (Collection)methods) + { + if(fm.locals != null) + { + for(LocalBinding lb : (Collection)RT.keys(fm.locals)) + { + lb.idx -= 1; + } + fm.maxLocal -= 1; + } + } + } + fn.methods = methods; fn.variadicMethod = variadicMethod; fn.keywords = (IPersistentMap) KEYWORDS.deref(); @@ -4008,7 +4045,7 @@ else if(methodArray[f.reqParms.count()] == null) fn.hasPrimSigs = prims.size() > 0; IPersistentMap fmeta = RT.meta(origForm); if(fmeta != null) - fmeta = fmeta.without(RT.LINE_KEY).without(RT.COLUMN_KEY).without(RT.FILE_KEY); + fmeta = fmeta.without(RT.LINE_KEY).without(RT.COLUMN_KEY).without(RT.FILE_KEY).without(retkey); fn.hasMeta = RT.count(fmeta) > 0; @@ -4107,7 +4144,7 @@ static public class ObjExpr implements Expr{ final static Method voidctor = Method.getMethod("void ()"); protected IPersistentMap classMeta; - protected boolean isStatic; + protected boolean canBeDirect; public final String name(){ return name; @@ -5020,7 +5057,7 @@ private void emitLocal(GeneratorAdapter gen, LocalBinding lb, boolean clear){ } else { - int argoff = isStatic?0:1; + int argoff = canBeDirect ?0:1; Class primc = lb.getPrimitiveType(); // String rep = lb.sym.name + " " + lb.toString().substring(lb.toString().lastIndexOf('@')); if(lb.isArg) @@ -5068,7 +5105,7 @@ private void emitLocal(GeneratorAdapter gen, LocalBinding lb, boolean clear){ } private void emitUnboxedLocal(GeneratorAdapter gen, LocalBinding lb){ - int argoff = isStatic?0:1; + int argoff = canBeDirect ?0:1; Class primc = lb.getPrimitiveType(); if(closes.containsKey(lb)) { @@ -5239,7 +5276,7 @@ static public String primInterface(IPersistentVector arglist) { return null; } - static FnMethod parse(ObjExpr objx, ISeq form, boolean isStatic) { + static FnMethod parse(ObjExpr objx, ISeq form, Object rettag) { //([args] body...) IPersistentVector parms = (IPersistentVector) RT.first(form); ISeq body = RT.next(form); @@ -5267,19 +5304,21 @@ static FnMethod parse(ObjExpr objx, ISeq form, boolean isStatic) { if(method.prim != null) method.prim = method.prim.replace('.', '/'); - method.retClass = tagClass(tagOf(parms)); + if(rettag instanceof String) + rettag = Symbol.intern(null, (String) rettag); + method.retClass = tagClass(tagOf(parms)!=null?tagOf(parms):rettag); if(method.retClass.isPrimitive() && !(method.retClass == double.class || method.retClass == long.class)) throw new IllegalArgumentException("Only long and double primitives are supported"); //register 'this' as local 0 //registerLocal(THISFN, null, null); - if(!isStatic) - { +// if(!canBeDirect) +// { if(objx.thisName != null) registerLocal(Symbol.intern(objx.thisName), null, null,false); else getAndIncLocalNum(); - } +// } PSTATE state = PSTATE.REQ; PersistentVector argLocals = PersistentVector.EMPTY; ArrayList argtypes = new ArrayList(); @@ -5293,7 +5332,7 @@ static FnMethod parse(ObjExpr objx, ISeq form, boolean isStatic) { throw Util.runtimeException("Can't use qualified name as parameter: " + p); if(p.equals(_AMP_)) { -// if(isStatic) +// if(canBeDirect) // throw Util.runtimeException("Variadic fns cannot be static"); if(state == PSTATE.REQ) state = PSTATE.REST; @@ -5304,7 +5343,7 @@ static FnMethod parse(ObjExpr objx, ISeq form, boolean isStatic) { else { Class pc = primClass(tagClass(tagOf(p))); -// if(pc.isPrimitive() && !isStatic) +// if(pc.isPrimitive() && !canBeDirect) // { // pc = Object.class; // p = (Symbol) ((IObj) p).withMeta((IPersistentMap) RT.assoc(RT.meta(p), RT.TAG_KEY, null)); @@ -5345,11 +5384,11 @@ static FnMethod parse(ObjExpr objx, ISeq form, boolean isStatic) { throw Util.runtimeException("Can't specify more than " + MAX_POSITIONAL_ARITY + " params"); LOOP_LOCALS.set(argLocals); method.argLocals = argLocals; -// if(isStatic) +// if(canBeDirect) + method.argtypes = argtypes.toArray(new Type[argtypes.size()]); + method.argclasses = argclasses.toArray(new Class[argtypes.size()]); if(method.prim != null) { - method.argtypes = argtypes.toArray(new Type[argtypes.size()]); - method.argclasses = argclasses.toArray(new Class[argtypes.size()]); for(int i = 0; i < method.argclasses.length; i++) { if(method.argclasses[i] == long.class || method.argclasses[i] == double.class) @@ -5366,16 +5405,31 @@ static FnMethod parse(ObjExpr objx, ISeq form, boolean isStatic) { } public void emit(ObjExpr fn, ClassVisitor cv){ - if(prim != null) + if(fn.canBeDirect) + { +// System.out.println("emit static: " + fn.name); + doEmitStatic(fn, cv); + } + else if(prim != null) + { +// System.out.println("emit prim: " + fn.name); doEmitPrim(fn, cv); - else if(fn.isStatic) - doEmitStatic(fn,cv); + } else + { +// System.out.println("emit normal: " + fn.name); doEmit(fn,cv); + } } public void doEmitStatic(ObjExpr fn, ClassVisitor cv){ - Method ms = new Method("invokeStatic", getReturnType(), argtypes); +// System.out.println("emit static:" + fn.name); + Type returnType = Type.getType(retClass); +// if (retClass == double.class || retClass == long.class) +// returnType = getReturnType(); +// else returnType = OBJECT_TYPE; + + Method ms = new Method("invokeStatic", returnType, argtypes); GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, ms, @@ -5423,13 +5477,41 @@ public void doEmitStatic(ObjExpr fn, ClassVisitor cv){ HostExpr.emitUnboxArg(fn, gen, argclasses[i]); } gen.invokeStatic(objx.objtype, ms); - gen.box(getReturnType()); + gen.box(returnType); gen.returnValue(); //gen.visitMaxs(1, 1); gen.endMethod(); + //generate primInvoke if prim + if(prim != null) + { + if (retClass == double.class || retClass == long.class) + returnType = getReturnType(); + else returnType = OBJECT_TYPE; + + Method pm = new Method("invokePrim", returnType, argtypes); + + gen = new GeneratorAdapter(ACC_PUBLIC + ACC_FINAL, + pm, + null, + //todo don't hardwire this + EXCEPTION_TYPES, + cv); + gen.visitCode(); + for(int i = 0; i < argtypes.length; i++) + { + gen.loadArg(i); + } + gen.invokeStatic(objx.objtype, ms); + + gen.returnValue(); + //gen.visitMaxs(1, 1); + gen.endMethod(); + } + + } public void doEmitPrim(ObjExpr fn, ClassVisitor cv){ @@ -5614,6 +5696,7 @@ abstract public static class ObjMethod{ int maxLocal = 0; int line; int column; + boolean usesThis = false; PersistentHashSet localsUsedInCatchFinally = PersistentHashSet.EMPTY; protected IPersistentMap methodMeta; @@ -5744,7 +5827,7 @@ void emitClearLocalsOld(GeneratorAdapter gen){ if(!localsUsedInCatchFinally.contains(lb.idx) && lb.getPrimitiveType() == null) { gen.visitInsn(Opcodes.ACONST_NULL); - gen.storeArg(lb.idx - 1); + gen.storeArg(lb.idx - 1); } } @@ -5780,7 +5863,7 @@ public static class LocalBinding{ public final Symbol sym; public final Symbol tag; public Expr init; - public final int idx; + int idx; public final String name; public final boolean isArg; public final PathNode clearPathRoot; @@ -6437,7 +6520,7 @@ else if(primc == float.class && pc == double.class) LocalBinding lb = (LocalBinding) loopLocals.nth(i); Class primc = lb.getPrimitiveType(); if(lb.isArg) - gen.storeArg(lb.idx-(objx.isStatic?0:1)); + gen.storeArg(lb.idx-(objx.canBeDirect ?0:1)); else { if(primc != null) @@ -7208,6 +7291,8 @@ static LocalBinding referenceLocal(Symbol sym) { if(b != null) { ObjMethod method = (ObjMethod) METHOD.deref(); + if(b.idx == 0) + method.usesThis = true; closeOver(b, method); } return b; diff --git a/src/jvm/clojure/lang/PersistentList.java b/src/jvm/clojure/lang/PersistentList.java index fa16cd0e72..60b83fc676 100644 --- a/src/jvm/clojure/lang/PersistentList.java +++ b/src/jvm/clojure/lang/PersistentList.java @@ -19,7 +19,7 @@ public class PersistentList extends ASeq implements IPersistentList, IReduce, Li private final IPersistentList _rest; private final int _count; -public static IFn creator = new RestFn(){ +static public class Primordial extends RestFn{ final public int getRequiredArity(){ return 0; } @@ -39,7 +39,31 @@ final protected Object doInvoke(Object args) { return create(list); } -}; + static public Object invokeStatic(ISeq args) { + if(args instanceof ArraySeq) + { + Object[] argsarray = ((ArraySeq) args).array; + IPersistentList ret = EMPTY; + for(int i = argsarray.length - 1; i >= 0; --i) + ret = (IPersistentList) ret.cons(argsarray[i]); + return ret; + } + LinkedList list = new LinkedList(); + for(ISeq s = RT.seq(args); s != null; s = s.next()) + list.add(s.first()); + return create(list); + } + + public IObj withMeta(IPersistentMap meta){ + throw new UnsupportedOperationException(); + } + + public IPersistentMap meta(){ + return null; + } +} + +public static IFn creator = new Primordial(); final public static EmptyList EMPTY = new EmptyList(null); @@ -117,7 +141,7 @@ public Object reduce(IFn f, Object start) { Object ret = f.invoke(start, first()); for(ISeq s = next(); s != null; s = s.next()) { if (RT.isReduced(ret)) return ((IDeref)ret).deref(); - ret = f.invoke(ret, s.first()); + ret = f.invoke(ret, s.first()); } if (RT.isReduced(ret)) return ((IDeref)ret).deref(); return ret; From be3f390b5b2f47bda241d7654c65bcea8ad39eb8 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 29 Jul 2015 10:38:29 -0400 Subject: [PATCH 063/854] clear args before calling invokeStatic --- src/jvm/clojure/lang/Compiler.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index d8329bb910..cd57dc5dbb 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -5475,6 +5475,11 @@ public void doEmitStatic(ObjExpr fn, ClassVisitor cv){ { gen.loadArg(i); HostExpr.emitUnboxArg(fn, gen, argclasses[i]); + if(!argclasses[i].isPrimitive()) + { + gen.visitInsn(Opcodes.ACONST_NULL); + gen.storeArg(i); + } } gen.invokeStatic(objx.objtype, ms); gen.box(returnType); @@ -5503,6 +5508,11 @@ public void doEmitStatic(ObjExpr fn, ClassVisitor cv){ for(int i = 0; i < argtypes.length; i++) { gen.loadArg(i); + if(!argclasses[i].isPrimitive()) + { + gen.visitInsn(Opcodes.ACONST_NULL); + gen.storeArg(i); + } } gen.invokeStatic(objx.objtype, ms); From f9f7f6e91002e32883fbfaf0b9ace09fc7a0a1b4 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 29 Jul 2015 15:51:14 -0500 Subject: [PATCH 064/854] [maven-release-plugin] prepare release clojure-1.8.0-alpha3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80692a98b8..10070eda1e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-alpha3 http://clojure.org/ Clojure core environment and runtime library. From b926222fbdbd866806d441fa362e3ac0cf0afafa Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 29 Jul 2015 15:51:14 -0500 Subject: [PATCH 065/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 10070eda1e..80692a98b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-alpha3 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 00e07f1cc9de029e97a5f2e2f075b45c68b1129e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 31 Jul 2015 08:48:30 -0400 Subject: [PATCH 066/854] narrow use of rettag by invokeStatic --- src/jvm/clojure/lang/Compiler.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index cd57dc5dbb..e480154bba 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -1082,8 +1082,6 @@ else if(stringOk && form instanceof String) */ static Class tagToClass(Object tag) { Class c = null; - if(tag instanceof ISeq && RT.first(tag).equals(QUOTE)) - tag = RT.second(tag); if(tag instanceof Symbol) { Symbol sym = (Symbol) tag; @@ -5306,10 +5304,15 @@ static FnMethod parse(ObjExpr objx, ISeq form, Object rettag) { if(rettag instanceof String) rettag = Symbol.intern(null, (String) rettag); + if(!(rettag instanceof Symbol)) + rettag = null; method.retClass = tagClass(tagOf(parms)!=null?tagOf(parms):rettag); - if(method.retClass.isPrimitive() && !(method.retClass == double.class || method.retClass == long.class)) - throw new IllegalArgumentException("Only long and double primitives are supported"); - + if(method.retClass.isPrimitive()){ + if(!(method.retClass == double.class || method.retClass == long.class)) + throw new IllegalArgumentException("Only long and double primitives are supported"); + } + else + method.retClass = Object.class; //register 'this' as local 0 //registerLocal(THISFN, null, null); // if(!canBeDirect) From 3b0af1b70de9c733872f4cc1ff68cec2f23a27a7 Mon Sep 17 00:00:00 2001 From: Alexander Yakushev Date: Wed, 18 Mar 2015 23:30:37 +0200 Subject: [PATCH 067/854] CLJ-1657: Treat abstract methods like interface methods in proxies Before this patch, generate-proxy for abstract classes produced incorrect bytecode, with a call to super's method (which obviously doesn't exist for abstract methods). Signed-off-by: Stuart Halloway --- src/clj/clojure/core_proxy.clj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj index ab3b483743..fc46aea9d5 100644 --- a/src/clj/clojure/core_proxy.clj +++ b/src/clj/clojure/core_proxy.clj @@ -210,6 +210,12 @@ (for [^Class iface interfaces meth (. iface (getMethods)) :let [msig (method-sig meth)] :when (not (considered msig))] {msig meth})) + ;; Treat abstract methods as interface methods + [mm ifaces-meths] (let [abstract? (fn [[_ ^Method meth]] + (Modifier/isAbstract (. meth (getModifiers)))) + mm-no-abstract (remove abstract? mm) + abstract-meths (filter abstract? mm)] + [mm-no-abstract (concat ifaces-meths abstract-meths)]) mgroups (group-by-sig (concat mm ifaces-meths)) rtypes (map #(most-specific (keys %)) mgroups) mb (map #(vector (%1 %2) (vals (dissoc %1 %2))) mgroups rtypes) From 0db40b3a5e52fc6bd15c6b4061201d3727421de6 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Fri, 31 Jul 2015 11:14:33 -0400 Subject: [PATCH 068/854] CLJ-1657 example test --- test/clojure/test_clojure/java_interop.clj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/clojure/test_clojure/java_interop.clj b/test/clojure/test_clojure/java_interop.clj index 4083771f5c..f8f2ebcc8d 100644 --- a/test/clojure/test_clojure/java_interop.clj +++ b/test/clojure/test_clojure/java_interop.clj @@ -200,6 +200,11 @@ ;; same behavior on second call (is (thrown? IllegalArgumentException (.flip d -1))))) +;; http://dev.clojure.org/jira/browse/CLJ-1657 +(deftest test-proxy-abstract-super + (let [p (proxy [java.io.Writer] [])] + (is (thrown? UnsupportedOperationException (.close p))))) + ; Arrays: [alength] aget aset [make-array to-array into-array to-array-2d aclone] ; [float-array, int-array, etc] ; amap, areduce From 7f79ac9ee85fe305e4d9cbb76badf3a8bad24ea0 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Wed, 1 Oct 2014 01:34:48 +0200 Subject: [PATCH 069/854] CLJ-130: preserve metadata for AOT compiled namespaces Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 4da25a409d..033f6e6a91 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5573,9 +5573,11 @@ (list* `gen-class :name (.replace (str name) \- \_) :impl-ns name :main true (next gen-class-clause))) references (remove #(= :gen-class (first %)) references) ;ns-effect (clojure.core/in-ns name) - ] + name-metadata (meta name)] `(do (clojure.core/in-ns '~name) + ~@(when name-metadata + `((.resetMeta (clojure.lang.Namespace/find '~name) ~name-metadata))) (with-loading-context ~@(when gen-class-call (list gen-class-call)) ~@(when (and (not= name 'clojure.core) (not-any? #(= :refer-clojure (first %)) references)) From 2be494793f5a8a9f130d6bf0c8f2965e07c6291a Mon Sep 17 00:00:00 2001 From: Ghadi Shayban Date: Fri, 21 Dec 2012 12:30:06 -0500 Subject: [PATCH 070/854] evaluate def symbol metadata only once Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index e480154bba..27ec3b9dfc 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -458,7 +458,7 @@ public Object eval() { { IPersistentMap metaMap = (IPersistentMap) meta.eval(); if (initProvided || true)//includesExplicitMetadata((MapExpr) meta)) - var.setMeta((IPersistentMap) meta.eval()); + var.setMeta(metaMap); } return var.setDynamic(isDynamic); } From 733e3d0689165d8090378ce4d516d35ae278e783 Mon Sep 17 00:00:00 2001 From: Jozef Wagner Date: Tue, 21 Jul 2015 23:03:35 +0200 Subject: [PATCH 071/854] CLJ-1785 Allow reader conditionals to have nil value as an expression Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LispReader.java | 7 ++++--- test/clojure/test_clojure/reader.cljc | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index 67876ad5ed..6441d45213 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -1314,6 +1314,7 @@ static boolean isPreserveReadCond(Object opts) { public static class ConditionalReader extends AFn { + final static private Object READ_STARTED = new Object(); final static public Keyword DEFAULT_FEATURE = Keyword.intern(null, "default"); final static public IPersistentSet RESERVED_FEATURES = RT.set(Keyword.intern(null, "else"), Keyword.intern(null, "none")); @@ -1330,7 +1331,7 @@ public static boolean hasFeature(Object feature, Object opts) { } public static Object readCondDelimited(PushbackReader r, boolean splicing, Object opts, Object pendingForms) { - Object result = null; + Object result = READ_STARTED; Object form; // The most recently ready form boolean toplevel = (pendingForms == null); pendingForms = ensurePending(pendingForms); @@ -1340,7 +1341,7 @@ public static Object readCondDelimited(PushbackReader r, boolean splicing, Objec ((LineNumberingPushbackReader) r).getLineNumber() : -1; for(; ;) { - if(result == null) { + if(result == READ_STARTED) { // Read the next feature form = read(r, false, READ_EOF, ')', READ_FINISHED, true, opts, pendingForms); @@ -1398,7 +1399,7 @@ public static Object readCondDelimited(PushbackReader r, boolean splicing, Objec } - if (result == null) // no features matched + if (result == READ_STARTED) // no features matched return r; if (splicing) { diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc index 63774a86dc..6d03590dcc 100644 --- a/test/clojure/test_clojure/reader.cljc +++ b/test/clojure/test_clojure/reader.cljc @@ -702,7 +702,12 @@ (is (= 1 (read-string opts "#?(:cljs (let [{{b :b} :a {d :d} :c} {}]) :clj 1)"))) (is (= '(def m {}) (read-string opts "(def m #?(:cljs ^{:a :b} {} :clj ^{:a :b} {}))"))) (is (= '(def m {}) (read-string opts "(def m #?(:cljs ^{:a :b} {} :clj ^{:a :b} {}))"))) - (is (= 1 (read-string opts "#?(:cljs {:a #_:b :c} :clj 1)")))))) + (is (= 1 (read-string opts "#?(:cljs {:a #_:b :c} :clj 1)"))))) + (testing "nil expressions" + (is (nil? #?(:default nil))) + (is (nil? #?(:foo :bar :clj nil))) + (is (nil? #?(:clj nil :foo :bar))) + (is (nil? #?(:foo :bar :default nil))))) (deftest eof-option (is (= 23 (read-string {:eof 23} ""))) From 2ea3c53689ffe781c1a6878dc85774b467d48f8d Mon Sep 17 00:00:00 2001 From: Steve Miner Date: Thu, 10 Apr 2014 17:35:36 -0400 Subject: [PATCH 072/854] CLJ-1390 pprint GregorianCalendar Signed-off-by: Stuart Halloway --- src/clj/clojure/pprint/pretty_writer.clj | 4 +++- test/clojure/test_clojure/pprint/test_pretty.clj | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/pprint/pretty_writer.clj b/src/clj/clojure/pprint/pretty_writer.clj index c31142c691..ac83fe4208 100644 --- a/src/clj/clojure/pprint/pretty_writer.clj +++ b/src/clj/clojure/pprint/pretty_writer.clj @@ -406,7 +406,9 @@ Integer (p-write-char this x) Long - (p-write-char this x)))) + (p-write-char this x))) + ([x off len] + (.write this (subs (str x) off (+ off len))))) (ppflush [] (if (= (getf :mode) :buffering) diff --git a/test/clojure/test_clojure/pprint/test_pretty.clj b/test/clojure/test_clojure/pprint/test_pretty.clj index aca03d8bcd..e5726df777 100644 --- a/test/clojure/test_clojure/pprint/test_pretty.clj +++ b/test/clojure/test_clojure/pprint/test_pretty.clj @@ -375,3 +375,10 @@ It is implemented with a number of custom enlive templates.\" (pprint (range 50))) (is (= @flush-count-atom 0) "pprint flushes on newline"))) +(deftest test-pprint-calendar + (let [calendar (doto (java.util.GregorianCalendar. 2014 3 29 14 0 0) + (.setTimeZone (java.util.TimeZone/getTimeZone "GMT"))) + calendar-str (with-out-str (pprint calendar))] + (is (= calendar-str "#inst \"2014-04-29T14:00:00.000+00:00\"\n") + "calendar object pretty prints"))) + From c450806297f13c949b432e95a18ecfd27bf98ec3 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Thu, 5 Sep 2013 22:38:23 -0700 Subject: [PATCH 073/854] CLJ-1225: Correct (quot (bigint Long/MIN_VALUE) -1N) answer Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/BigInt.java | 2 ++ test/clojure/test_clojure/numbers.clj | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/jvm/clojure/lang/BigInt.java b/src/jvm/clojure/lang/BigInt.java index fb4bc0080c..a3ca969410 100644 --- a/src/jvm/clojure/lang/BigInt.java +++ b/src/jvm/clojure/lang/BigInt.java @@ -160,6 +160,8 @@ public BigInt multiply(BigInt y) { public BigInt quotient(BigInt y) { if ((bipart == null) && (y.bipart == null)) { + if (lpart == Long.MIN_VALUE && y.lpart == -1) + return BigInt.fromBigInteger(this.toBigInteger().negate()); return BigInt.valueOf(lpart / y.lpart); } return BigInt.fromBigInteger(this.toBigInteger().divide(y.toBigInteger())); diff --git a/test/clojure/test_clojure/numbers.clj b/test/clojure/test_clojure/numbers.clj index 67a87e8290..f09dd4bf85 100644 --- a/test/clojure/test_clojure/numbers.clj +++ b/test/clojure/test_clojure/numbers.clj @@ -300,6 +300,14 @@ (is (thrown? ArithmeticException (/ 2 0))) (is (thrown? IllegalArgumentException (/))) ) +(deftest test-divide-bigint-at-edge + (are [x] (= x (-' Long/MIN_VALUE)) + (/ Long/MIN_VALUE -1N) + (/ (bigint Long/MIN_VALUE) -1) + (/ (bigint Long/MIN_VALUE) -1N) + (quot Long/MIN_VALUE -1N) + (quot (bigint Long/MIN_VALUE) -1) + (quot (bigint Long/MIN_VALUE) -1N))) ;; mod ;; http://en.wikipedia.org/wiki/Modulo_operation From 6dcb5e28980873a7c2fc12094c2b443155cac7d3 Mon Sep 17 00:00:00 2001 From: Tsutomu YANO Date: Thu, 25 Sep 2014 10:14:21 -0500 Subject: [PATCH 074/854] CLJ-1157 added a method for loading classes with the classloader of a specified class Signed-off-by: Stuart Halloway --- src/clj/clojure/genclass.clj | 7 +++---- src/jvm/clojure/lang/Util.java | 13 ++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/genclass.clj b/src/clj/clojure/genclass.clj index 11c12be982..819851243f 100644 --- a/src/clj/clojure/genclass.clj +++ b/src/clj/clojure/genclass.clj @@ -161,6 +161,7 @@ ifn-type (totype clojure.lang.IFn) iseq-type (totype clojure.lang.ISeq) ex-type (totype java.lang.UnsupportedOperationException) + util-type (totype clojure.lang.Util) all-sigs (distinct (concat (map #(let[[m p] (key %)] {m [p]}) (mapcat non-private-methods supers)) (map (fn [[m p]] {(str m) [p]}) methods))) sigs-by-name (apply merge-with concat {} all-sigs) @@ -287,11 +288,9 @@ (. gen putStatic ctype (var-name v) var-type)) (when load-impl-ns - (. gen push "clojure.core") - (. gen push "load") - (. gen (invokeStatic rt-type (. Method (getMethod "clojure.lang.Var var(String,String)")))) (. gen push (str "/" impl-cname)) - (. gen (invokeInterface ifn-type (new Method "invoke" obj-type (to-types [Object])))) + (. gen push ctype) + (. gen (invokeStatic util-type (. Method (getMethod "Object loadWithClass(String,Class)")))) ; (. gen push (str (.replace impl-pkg-name \- \_) "__init")) ; (. gen (invokeStatic class-type (. Method (getMethod "Class forName(String)")))) (. gen pop)) diff --git a/src/jvm/clojure/lang/Util.java b/src/jvm/clojure/lang/Util.java index cdac732c63..e3be3e8cd8 100644 --- a/src/jvm/clojure/lang/Util.java +++ b/src/jvm/clojure/lang/Util.java @@ -12,12 +12,12 @@ package clojure.lang; +import java.io.IOException; import java.lang.ref.Reference; import java.math.BigInteger; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.lang.ref.SoftReference; import java.lang.ref.ReferenceQueue; public class Util{ @@ -244,5 +244,16 @@ static private void sneakyThrow0(Throwable t) throws T { throw (T) t; } +static public Object loadWithClass(String scriptbase, Class loadFrom) throws IOException, ClassNotFoundException{ + Var.pushThreadBindings(RT.map(new Object[] { Compiler.LOADER, loadFrom.getClassLoader() })); + try { + return RT.var("clojure.core", "load").invoke(scriptbase); + } + finally + { + Var.popThreadBindings(); + } +} + } From 7754e94377042cc539182f48a66010a5e260e8ea Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Wed, 1 Jul 2015 17:40:38 +1200 Subject: [PATCH 075/854] CLJ-1772 Fix spelling in clojure.test/use-fixtures Signed-off-by: Stuart Halloway --- src/clj/clojure/test.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/test.clj b/src/clj/clojure/test.clj index c9b52d3ae8..7eb5a7dd6c 100644 --- a/src/clj/clojure/test.clj +++ b/src/clj/clojure/test.clj @@ -656,7 +656,7 @@ (defmulti use-fixtures "Wrap test runs in a fixture function to perform setup and teardown. Using a fixture-type of :each wraps every test - individually, while:once wraps the whole run in a single function." + individually, while :once wraps the whole run in a single function." {:added "1.1"} (fn [fixture-type & args] fixture-type)) From 9d70c752facbf6ed1171176af9f486a0d6dddc47 Mon Sep 17 00:00:00 2001 From: Mark Simpson Date: Sat, 27 Jun 2015 15:19:21 -0400 Subject: [PATCH 076/854] Small correction to *' and +' doc strings Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 033f6e6a91..56156dc061 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -938,7 +938,7 @@ (defn ^:private >0? [n] (clojure.lang.Numbers/gt n 0)) (defn +' - "Returns the sum of nums. (+) returns 0. Supports arbitrary precision. + "Returns the sum of nums. (+') returns 0. Supports arbitrary precision. See also: +" {:inline (nary-inline 'addP) :inline-arities >1? @@ -962,7 +962,7 @@ (reduce1 + (+ x y) more))) (defn *' - "Returns the product of nums. (*) returns 1. Supports arbitrary precision. + "Returns the product of nums. (*') returns 1. Supports arbitrary precision. See also: *" {:inline (nary-inline 'multiplyP) :inline-arities >1? From 1bfdebf90a570d808581c47323d6939e5b4fa9bb Mon Sep 17 00:00:00 2001 From: Blake West Date: Sun, 3 May 2015 19:12:23 -0700 Subject: [PATCH 077/854] Fixed typo in with-binding docs. ("then executes" vs "the executes") Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 56156dc061..d68721ea35 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1874,7 +1874,7 @@ (defmacro with-bindings "Takes a map of Var/value pairs. Installs for the given Vars the associated - values as thread-local bindings. The executes body. Pops the installed + values as thread-local bindings. Then executes body. Pops the installed bindings after body was evaluated. Returns the value of body." {:added "1.1"} [binding-map & body] From d9f3f83182e146525a78cf638f0613487d7e18c6 Mon Sep 17 00:00:00 2001 From: "Howard M. Lewis Ship" Date: Tue, 29 Jul 2014 14:55:53 -0700 Subject: [PATCH 078/854] Modify clojure.test.junit/with-junit-output to support multiple forms Fixes CLJ-1485 Signed-off-by: Stuart Halloway --- src/clj/clojure/test/junit.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/test/junit.clj b/src/clj/clojure/test/junit.clj index 33077033f9..3d129b186b 100644 --- a/src/clj/clojure/test/junit.clj +++ b/src/clj/clojure/test/junit.clj @@ -190,6 +190,6 @@ (t/with-test-out (println "") (println "")) - (let [result# ~@body] + (let [result# (do ~@body)] (t/with-test-out (println "")) result#))) From c4e02798ae487067e2cc11800e6f62bc1961bd02 Mon Sep 17 00:00:00 2001 From: Russ Olsen Date: Tue, 18 Feb 2014 14:35:02 -0500 Subject: [PATCH 079/854] Add test for and then fix pprint of code (ns foo). Signed-off-by: Stuart Halloway --- src/clj/clojure/pprint/dispatch.clj | 2 +- test/clojure/test_clojure/pprint/test_pretty.clj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/pprint/dispatch.clj b/src/clj/clojure/pprint/dispatch.clj index de4626c392..d8c952bab1 100644 --- a/src/clj/clojure/pprint/dispatch.clj +++ b/src/clj/clojure/pprint/dispatch.clj @@ -211,7 +211,7 @@ (when (next args) ((formatter-out "~:_")))))) (recur (next args)))))) - (write-out reference))) + (when reference (write-out reference)))) (defn- pprint-ns "The pretty print dispatch chunk for the ns macro" diff --git a/test/clojure/test_clojure/pprint/test_pretty.clj b/test/clojure/test_clojure/pprint/test_pretty.clj index e5726df777..b7552cf748 100644 --- a/test/clojure/test_clojure/pprint/test_pretty.clj +++ b/test/clojure/test_clojure/pprint/test_pretty.clj @@ -178,6 +178,7 @@ Usage: *hello* (pprint-simple-code-list writer alis)))") (code-block ns-macro-test + "(ns foobarbaz)" "(ns slam.hound.stitch (:use [slam.hound.prettify :only [prettify]]))" From 8a8e1f91ddc16194126e0ceda1db3a0f42b82273 Mon Sep 17 00:00:00 2001 From: Jean Niklas L'orange Date: Wed, 19 Dec 2012 04:14:04 +0100 Subject: [PATCH 080/854] Fix "~n@*"-directives in clojure.pprint/cl-format. Fixes #CLJ-1134. This solves two issues as specified by #CLJ-1134. Issue #1 is solved by doing a relative jump forward within `absolute-reposition` in cl_format.clj, line 114 by switching `(- (:pos navigator) position)` with `(- position (:pos navigator))`. Issue #2 is handled by changing the default `n`-parameter to `*` depending on whether the `@`-prefix is placed or not. If it is placed, then `n` defaults to 0, otherwise it defaults to 1. In addition, new tests have been appended to `test_cl_format.clj` to ensure the correctness of this patch. The tests have been tested on the Common Lisp implementation GNU CLISP 2.49, which presumably handle the `~n@*` correctly. This patch and GNU CLISP returns the same output for each format call, sans case for printed symbols; Common Lisp has case-insensitive symbols, whereas Clojure has not. Signed-off-by: Stuart Halloway --- src/clj/clojure/pprint/cl_format.clj | 17 +++++++++-------- .../test_clojure/pprint/test_cl_format.clj | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/clj/clojure/pprint/cl_format.clj b/src/clj/clojure/pprint/cl_format.clj index a8308c0424..30daa90af4 100644 --- a/src/clj/clojure/pprint/cl_format.clj +++ b/src/clj/clojure/pprint/cl_format.clj @@ -111,7 +111,7 @@ http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm (defn- absolute-reposition [navigator position] (if (>= position (:pos navigator)) - (relative-reposition navigator (- (:pos navigator) position)) + (relative-reposition navigator (- position (:pos navigator))) (struct arg-navigator (:seq navigator) (drop position (:seq navigator)) position))) (defn- relative-reposition [navigator position] @@ -1471,14 +1471,15 @@ not a pretty writer (which keeps track of columns), this function always outputs #(absolute-tabulation %1 %2 %3))) (\* - [ :n [1 Integer] ] + [ :n [nil Integer] ] #{ :colon :at } {} - (fn [params navigator offsets] - (let [n (:n params)] - (if (:at params) - (absolute-reposition navigator n) - (relative-reposition navigator (if (:colon params) (- n) n))) - ))) + (if (:at params) + (fn [params navigator offsets] + (let [n (or (:n params) 0)] ; ~@* has a default n = 0 + (absolute-reposition navigator n))) + (fn [params navigator offsets] + (let [n (or (:n params) 1)] ; whereas ~* and ~:* have a default n = 1 + (relative-reposition navigator (if (:colon params) (- n) n)))))) (\? [ ] diff --git a/test/clojure/test_clojure/pprint/test_cl_format.clj b/test/clojure/test_clojure/pprint/test_cl_format.clj index 53ae3f2093..a8cc134f79 100644 --- a/test/clojure/test_clojure/pprint/test_cl_format.clj +++ b/test/clojure/test_clojure/pprint/test_cl_format.clj @@ -823,3 +823,21 @@ but it was called with an argument of type short-float.\n") | -2/3 | | panda | dog | " ) + +(simple-tests *-at-tests + (format nil "~*~c defaults to ~D, so ~~@* goes ~A to the ~@*~A arg." + 'first \n 0 'back) + "n defaults to 0, so ~@* goes back to the first arg." + (format nil "~~n@* is an ~1@*~A ~0@*~A rather than a ~2@*~A ~0@*~A." + 'goto 'absolute 'relative) + "~n@* is an absolute goto rather than a relative goto." + (format nil "We will see no numbers: ~6@*~S" + 0 1 2 3 4 5 :see?) "We will see no numbers: :see?" + (format nil "~4@*~D ~3@*~D ~2@*~D ~1@*~D ~0@*~D" + 0 1 2 3 4) "4 3 2 1 0" + (format nil "~{~A a ~~{ ~3@*~A, the ~1@*~A is ~A to the ~4@*~A of ~A~}[...]" + '("Within" goto relative construct list arguments)) + "Within a ~{ construct, the goto is relative to the list of arguments[...]" + (format nil "~{~2@*~S ~1@*~S ~4@*~S ~3@*~S ~S~}" + '(:a :b :c :d :e)) ":c :b :e :d :e" + ) From 4bb1dbd596f032621c00a670b1609a94acfcfcab Mon Sep 17 00:00:00 2001 From: asheldo Date: Thu, 18 Jun 2015 08:41:47 -0400 Subject: [PATCH 081/854] Update list* docstring per CLJ-1060 Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index d68721ea35..1d13d76655 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -614,7 +614,7 @@ :else (cons (first arglist) (spread (next arglist))))) (defn list* - "Creates a new list containing the items prepended to the rest, the + "Creates a new seq containing the items prepended to the rest, the last of which will be treated as a sequence." {:added "1.0" :static true} From e58042cb78cdb83a8885f7c014a9d0659d4102e6 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Sat, 1 Aug 2015 09:20:21 -0500 Subject: [PATCH 082/854] 1.8.0-alpha4 changes Signed-off-by: Stuart Halloway --- changes.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/changes.md b/changes.md index dc1d754aa9..eed08ada17 100644 --- a/changes.md +++ b/changes.md @@ -1,5 +1,101 @@ +# Changes to Clojure in Version 1.8 + +## 1 New and Improved Features + +### 1.1 Direct Linking + +*This feature is a work in progress, subject to change.* + +Direct linking can be enabled with -Dclojure.compiler.direct-linking=true + +Direct linking allows functions compiled with direct linking on to make direct +static method calls to most other functions, instead of going through the var +and the Fn object. This can enable further optimization by the jit, at a cost +in dynamism. In particular, directly-linked calls will not see redefinitions. + +As of 1.8.0-alpha3, clojure.core is compiled with direct linking by default +and therefore other namespaces cannot redefine core fns and have those +redefinitions seen by core code. + +Functions declared as dynamic will never be direct linked. + +## 2 Enhancements + +### 2.1 Error messages + +### 2.2 Documentation strings + +* [CLJ-1060](http://dev.clojure.org/jira/browse/CLJ-1060) + 'list*' returns not a list +* [CLJ-1722](http://dev.clojure.org/jira/browse/CLJ-1722) + Typo in the docstring of 'with-bindings' +* [CLJ-1769](http://dev.clojure.org/jira/browse/CLJ-1769) + Docstrings for *' and +' refer to * and + + +### 2.3 Performance + +* [CLJ-703](http://dev.clojure.org/jira/browse/CLJ-703) + Improve writeClassFile performance + +### 2.4 Other enhancements + +* [CLJ-1208](http://dev.clojure.org/jira/browse/CLJ-1208) + Optionally require namespace on defrecord class init + +## 3 Bug Fixes + +* [CLJ-130](http://dev.clojure.org/jira/browse/CLJ-130) + Namespace metadata lost in AOT compile +* [CLJ-1134](http://dev.clojure.org/jira/browse/CLJ-1134) + star-directive in clojure.pprint/cl-format with at-prefix ("~n@*") does + not obey its specification +* [CLJ-1137](http://dev.clojure.org/jira/browse/CLJ-1137) + Metadata on a def gets evaluated twice +* [CLJ-1157](http://dev.clojure.org/jira/browse/CLJ-1157) + Classes generated by gen-class aren't loadable from remote codebase +* [CLJ-1225](http://dev.clojure.org/jira/browse/CLJ-1225) + quot overflow issues around Long/MIN_VALUE for BigInt +* [CLJ-1250](http://dev.clojure.org/jira/browse/CLJ-1250) + Reducer (and folder) instances hold onto the head of seqs +* [CLJ-1313](http://dev.clojure.org/jira/browse/CLJ-1313) + Correct a few unit tests +* [CLJ-1319](http://dev.clojure.org/jira/browse/CLJ-1319) + array-map fails lazily if passed an odd number of arguments +* [CLJ-1361](http://dev.clojure.org/jira/browse/CLJ-1361) + pprint with code-dispatch incorrectly prints a simple ns macro call +* [CLJ-1390](http://dev.clojure.org/jira/browse/CLJ-1390) + pprint a GregorianCalendar results in Arity exception +* [CLJ-1399](http://dev.clojure.org/jira/browse/CLJ-1399) + field name unmunged when recreating deftypes serialized into bytecode +* [CLJ-1485](http://dev.clojure.org/jira/browse/CLJ-1485) + clojure.test.junit/with-junit-output doesn't handle multiple expressions +* [CLJ-1528](http://dev.clojure.org/jira/browse/CLJ-1528) + clojure.test/inc-report-counter is not thread-safe +* [CLJ-1533](http://dev.clojure.org/jira/browse/CLJ-1533) + invokePrim path does not take into account var or form meta +* [CLJ-1562](http://dev.clojure.org/jira/browse/CLJ-1562) + some->,some->>,cond->,cond->> and as-> doesn't work with (recur) +* [CLJ-1565](http://dev.clojure.org/jira/browse/CLJ-1565) + pprint produces infinite output for a protocol +* [CLJ-1588](http://dev.clojure.org/jira/browse/CLJ-1588) + StackOverflow in clojure.test macroexpand with `are` and anon `fn` +* [CLJ-1644](http://dev.clojure.org/jira/browse/CLJ-1644) + into-array fails for sequences starting with nil +* [CLJ-1645](http://dev.clojure.org/jira/browse/CLJ-1645) + protocol class does not set the source file +* [CLJ-1657](http://dev.clojure.org/jira/browse/CLJ-1657) + proxy bytecode calls super methods of abstract classes +* [CLJ-1659](http://dev.clojure.org/jira/browse/CLJ-1659) + compile leaks files +* [CLJ-1761](http://dev.clojure.org/jira/browse/CLJ-1761) + clojure.core/run! does not always return nil per docstring +* [CLJ-1782](http://dev.clojure.org/jira/browse/CLJ-1782) + Spelling mistake in clojure.test/use-fixtures +* [CLJ-1785](http://dev.clojure.org/jira/browse/CLJ-1785) + Reader conditionals throw when returning nil + # Changes to Clojure in Version 1.7 ## 1 Compatibility Notes From 16f1df68cabee2e107a00f8b0436962d4628cc35 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 3 Aug 2015 06:13:09 -0500 Subject: [PATCH 083/854] [maven-release-plugin] prepare release clojure-1.8.0-alpha4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80692a98b8..c64c7aae01 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-alpha4 http://clojure.org/ Clojure core environment and runtime library. From d534894c3006474068e46b67e4b838bf3727722d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 3 Aug 2015 06:13:09 -0500 Subject: [PATCH 084/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c64c7aae01..80692a98b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-alpha4 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From b38c23e66e01da3b573f3ad891f4107c1a7076e7 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 5 Aug 2015 13:58:43 -0400 Subject: [PATCH 085/854] revert 9b6c3a5 --- src/jvm/clojure/lang/Compiler.java | 102 +++++++++------------- test/clojure/test_clojure/compilation.clj | 18 ---- test/clojure/test_clojure/reducers.clj | 4 - 3 files changed, 43 insertions(+), 81 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 27ec3b9dfc..ba97fe6a36 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -223,7 +223,6 @@ public class Compiler implements Opcodes{ static final public Var METHOD = Var.create(null).setDynamic(); //null or not -static final public Var IN_TRY_BLOCK = Var.create(null).setDynamic(); static final public Var IN_CATCH_FINALLY = Var.create(null).setDynamic(); static final public Var NO_RECUR = Var.create(null).setDynamic(); @@ -373,10 +372,6 @@ static boolean isSpecial(Object sym){ return specials.containsKey(sym); } -static boolean inTailCall(C context) { - return (context == C.RETURN) && (IN_TRY_BLOCK.deref() == null); -} - static Symbol resolveSymbol(Symbol sym){ //already qualified or classname? if(sym.name.indexOf('.') > 0) @@ -1009,13 +1004,12 @@ else if(instance != null && instance.hasJavaClass() && instance.getJavaClass() ! Symbol sym = (Symbol) RT.first(call); Symbol tag = tagOf(form); PersistentVector args = PersistentVector.EMPTY; - boolean tailPosition = inTailCall(context); for(ISeq s = RT.next(call); s != null; s = s.next()) args = args.cons(analyze(context == C.EVAL ? context : C.EXPRESSION, s.first())); if(c != null) - return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args, tailPosition); + return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args); else - return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args, tailPosition); + return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args); } } } @@ -1452,15 +1446,13 @@ static class InstanceMethodExpr extends MethodExpr{ public final int line; public final int column; public final Symbol tag; - public final boolean tailPosition; public final java.lang.reflect.Method method; final static Method invokeInstanceMethodMethod = Method.getMethod("Object invokeInstanceMethod(Object,String,Object[])"); - public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, - String methodName, IPersistentVector args, boolean tailPosition) + public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, String methodName, IPersistentVector args) { this.source = source; this.line = line; @@ -1469,7 +1461,6 @@ public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr this.methodName = methodName; this.target = target; this.tag = tag; - this.tailPosition = tailPosition; if(target.hasJavaClass() && target.getJavaClass() != null) { List methods = Reflector.getMethods(target.getJavaClass(), args.count(), methodName, false); @@ -1563,10 +1554,10 @@ public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){ gen.checkCast(type); MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args); gen.visitLineNumber(line, gen.mark()); - if(tailPosition) + if(context == C.RETURN) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearThis(gen); + method.emitClearLocals(gen); } Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method)); if(method.getDeclaringClass().isInterface()) @@ -1637,14 +1628,12 @@ static class StaticMethodExpr extends MethodExpr{ public final int column; public final java.lang.reflect.Method method; public final Symbol tag; - public final boolean tailPosition; final static Method forNameMethod = Method.getMethod("Class classForName(String)"); final static Method invokeStaticMethodMethod = Method.getMethod("Object invokeStaticMethod(Class,String,Object[])"); final static Keyword warnOnBoxedKeyword = Keyword.intern("warn-on-boxed"); - public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, - String methodName, IPersistentVector args, boolean tailPosition) + public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, String methodName, IPersistentVector args) { this.c = c; this.methodName = methodName; @@ -1653,7 +1642,6 @@ public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c this.line = line; this.column = column; this.tag = tag; - this.tailPosition = tailPosition; List methods = Reflector.getMethods(c, args.count(), methodName, true); if(methods.isEmpty()) @@ -1792,10 +1780,10 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args); gen.visitLineNumber(line, gen.mark()); //Type type = Type.getObjectType(className.replace('.', '/')); - if(tailPosition) + if(context == C.RETURN) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearThis(gen); + method.emitClearLocals(gen); } Type type = Type.getType(c); Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method)); @@ -2279,14 +2267,13 @@ public Expr parse(C context, Object frm) { } else { - if(bodyExpr == null) - try { - Var.pushThreadBindings(RT.map(NO_RECUR, true, IN_TRY_BLOCK, RT.T)); - bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); - } finally { - Var.popThreadBindings(); - } - + if(bodyExpr == null) + try { + Var.pushThreadBindings(RT.map(NO_RECUR, true)); + bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); + } finally { + Var.popThreadBindings(); + } if(Util.equals(op, CATCH)) { Class c = HostExpr.maybeClass(RT.second(f), false); @@ -2334,21 +2321,17 @@ public Expr parse(C context, Object frm) { } } } - if(bodyExpr == null) - { - // this codepath is hit when there is neither catch or finally, e.g. (try (expr)) - // return a body expr directly - try - { - Var.pushThreadBindings(RT.map(NO_RECUR, true)); - bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); - } - finally - { - Var.popThreadBindings(); - } - return bodyExpr; - } + if(bodyExpr == null) { + try + { + Var.pushThreadBindings(RT.map(NO_RECUR, true)); + bodyExpr = (new BodyExpr.Parser()).parse(C.EXPRESSION, RT.seq(body)); + } + finally + { + Var.popThreadBindings(); + } + } return new TryExpr(bodyExpr, catches, finallyExpr, retLocal, finallyLocal); @@ -2596,6 +2579,11 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ gen.newInstance(type); gen.dup(); MethodExpr.emitTypedArgs(objx, gen, ctor.getParameterTypes(), args); + if(context == C.RETURN) + { + ObjMethod method = (ObjMethod) METHOD.deref(); + method.emitClearLocals(gen); + } gen.invokeConstructor(type, new Method("", Type.getConstructorDescriptor(ctor))); } else @@ -2603,6 +2591,11 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ gen.push(destubClassName(c.getName())); gen.invokeStatic(RT_TYPE, forNameMethod); MethodExpr.emitArgsAsArray(args, objx, gen); + if(context == C.RETURN) + { + ObjMethod method = (ObjMethod) METHOD.deref(); + method.emitClearLocals(gen); + } gen.invokeStatic(REFLECTOR_TYPE, invokeConstructorMethod); } if(context == C.STATEMENT) @@ -3570,7 +3563,6 @@ static class InvokeExpr implements Expr{ public final IPersistentVector args; public final int line; public final int column; - public final boolean tailPosition; public final String source; public boolean isProtocol = false; public boolean isDirect = false; @@ -3593,14 +3585,12 @@ static Object sigTag(int argcount, Var v){ return null; } - public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args, boolean tailPosition) { + public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args) { this.source = source; this.fexpr = fexpr; this.args = args; this.line = line; this.column = column; - this.tailPosition = tailPosition; - if(fexpr instanceof VarExpr) { Var fvar = ((VarExpr)fexpr).var; @@ -3744,10 +3734,10 @@ void emitArgsAndCall(int firstArgToEmit, C context, ObjExpr objx, GeneratorAdapt } gen.visitLineNumber(line, gen.mark()); - if(tailPosition) + if(context == C.RETURN) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearThis(gen); + method.emitClearLocals(gen); } gen.invokeInterface(IFN_TYPE, new Method("invoke", OBJECT_TYPE, ARG_TYPES[Math.min(MAX_POSITIONAL_ARITY + 1, @@ -3763,7 +3753,6 @@ public Class getJavaClass() { } static public Expr parse(C context, ISeq form) { - boolean tailPosition = inTailCall(context); if(context != C.EVAL) context = C.EXPRESSION; Expr fexpr = analyze(context, form.first()); @@ -3840,7 +3829,7 @@ static public Expr parse(C context, ISeq form) { // throw new IllegalArgumentException( // String.format("No more than %d args supported", MAX_POSITIONAL_ARITY)); - return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args, tailPosition); + return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args); } } @@ -5865,11 +5854,6 @@ void emitClearLocalsOld(GeneratorAdapter gen){ } } } - - void emitClearThis(GeneratorAdapter gen) { - gen.visitInsn(Opcodes.ACONST_NULL); - gen.visitVarInsn(Opcodes.ASTORE, 0); - } } public static class LocalBinding{ @@ -6295,14 +6279,14 @@ public Expr parse(C context, Object frm) { { if(recurMismatches != null && RT.booleanCast(recurMismatches.nth(i/2))) { - init = new StaticMethodExpr("", 0, 0, null, RT.class, "box", RT.vector(init), false); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "box", RT.vector(init)); if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref())) RT.errPrintWriter().println("Auto-boxing loop arg: " + sym); } else if(maybePrimitiveType(init) == int.class) - init = new StaticMethodExpr("", 0, 0, null, RT.class, "longCast", RT.vector(init), false); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "longCast", RT.vector(init)); else if(maybePrimitiveType(init) == float.class) - init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init), false); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init)); } //sequential enhancement of env (like Lisp let*) try diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index 593c38f42a..56ce5fd6a9 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -342,21 +342,3 @@ (deftest clj-1399 ;; throws an exception on failure (is (eval `(fn [] ~(CLJ1399. 1))))) - -(deftest CLJ-1250-this-clearing - (let [closed-over-in-catch (let [x :foo] - (fn [] - (try - (throw (Exception. "boom")) - (catch Exception e - x)))) ;; x should remain accessible to the fn - - a (atom nil) - closed-over-in-finally (fn [] - (try - :ret - (finally - (reset! a :run))))] - (is (= :foo (closed-over-in-catch))) - (is (= :ret (closed-over-in-finally))) - (is (= :run @a)))) diff --git a/test/clojure/test_clojure/reducers.clj b/test/clojure/test_clojure/reducers.clj index a884c85179..c2852ccb2d 100644 --- a/test/clojure/test_clojure/reducers.clj +++ b/test/clojure/test_clojure/reducers.clj @@ -89,7 +89,3 @@ ([ret k v] (when (= k k-fail) (throw (IndexOutOfBoundsException.))))) (zipmap (range test-map-count) (repeat :dummy))))))) - -(deftest test-closed-over-clearing - ;; this will throw OutOfMemory without proper reference clearing - (is (number? (reduce + 0 (r/map identity (range 1e8)))))) From 2344de2b2aadd5b0e47f1594a6f9e4eb2fdbdf5c Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 5 Aug 2015 16:12:17 -0400 Subject: [PATCH 086/854] ignore rettags other than long and double --- clojure.iml | 9 ++++++--- src/jvm/clojure/lang/Compiler.java | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/clojure.iml b/clojure.iml index e29b996c12..e4d101a477 100644 --- a/clojure.iml +++ b/clojure.iml @@ -1,6 +1,10 @@ - + + + + + @@ -21,5 +25,4 @@ - - + \ No newline at end of file diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index ba97fe6a36..22aec2cb21 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -5295,6 +5295,12 @@ static FnMethod parse(ObjExpr objx, ISeq form, Object rettag) { rettag = Symbol.intern(null, (String) rettag); if(!(rettag instanceof Symbol)) rettag = null; + if(rettag != null) + { + String retstr = ((Symbol)rettag).getName(); + if(!retstr.equals("long") || !retstr.equals("double")) + rettag = null; + } method.retClass = tagClass(tagOf(parms)!=null?tagOf(parms):rettag); if(method.retClass.isPrimitive()){ if(!(method.retClass == double.class || method.retClass == long.class)) From 1d5237f9d7db0bc5f6e929330108d016ac7bf76c Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 5 Aug 2015 16:23:40 -0400 Subject: [PATCH 087/854] exempt records and deftypes from consideration as empty collection constants, as they are currently exempt as non-empty map constants --- src/jvm/clojure/lang/Compiler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 22aec2cb21..16976a5c84 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6666,7 +6666,10 @@ else if(fclass == String.class) return new StringExpr(((String) form).intern()); // else if(fclass == Character.class) // return new CharExpr((Character) form); - else if(form instanceof IPersistentCollection && ((IPersistentCollection) form).count() == 0) + else if(form instanceof IPersistentCollection + && !(form instanceof IRecord) + && !(form instanceof IType) + && ((IPersistentCollection) form).count() == 0) { Expr ret = new EmptyExpr(form); if(RT.meta(form) != null) From bd6e906ba70cc19c06e551a55e1928877e8d3d81 Mon Sep 17 00:00:00 2001 From: Andrew Rosa Date: Tue, 18 Aug 2015 12:24:24 -0500 Subject: [PATCH 088/854] CLJ-1766: Serializing+deserializing lists breaks their hash Lists declare their `hash` and `hasheq` fields as transients, which are deserialized with default value `0`, breaking the expectation of non-computed values. Transients are meant to be non-serialized by default, so changing `0` to be non-computed on this case is not only more natural but also fixes the serialization roundtrip. Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/ASeq.java | 8 ++++---- test/clojure/test_clojure/serialization.clj | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/jvm/clojure/lang/ASeq.java b/src/jvm/clojure/lang/ASeq.java index 4f5c1a8010..325aa27c24 100644 --- a/src/jvm/clojure/lang/ASeq.java +++ b/src/jvm/clojure/lang/ASeq.java @@ -14,8 +14,8 @@ import java.util.*; public abstract class ASeq extends Obj implements ISeq, Sequential, List, Serializable, IHashEq { -transient int _hash = -1; -transient int _hasheq = -1; +transient int _hash; +transient int _hasheq; public String toString(){ return RT.printString(this); @@ -62,7 +62,7 @@ public boolean equals(Object obj){ } public int hashCode(){ - if(_hash == -1) + if(_hash == 0) { int hash = 1; for(ISeq s = seq(); s != null; s = s.next()) @@ -75,7 +75,7 @@ public int hashCode(){ } public int hasheq(){ - if(_hasheq == -1) + if(_hasheq == 0) { // int hash = 1; // for(ISeq s = seq(); s != null; s = s.next()) diff --git a/test/clojure/test_clojure/serialization.clj b/test/clojure/test_clojure/serialization.clj index 7a4c075bb5..60cd65c9fd 100644 --- a/test/clojure/test_clojure/serialization.clj +++ b/test/clojure/test_clojure/serialization.clj @@ -43,7 +43,9 @@ rt-seq (-> v seq serialize deserialize)] (and (= v rt) (= (seq v) (seq rt)) - (= (seq v) rt-seq)))) + (= (seq v) rt-seq) + (= (hash v) (hash rt)) + (= (.hashCode v) (.hashCode rt))))) (deftest sequable-serialization (are [val] (roundtrip val) From c5f0521cb80bb8966a45647a01a615e3c94be30c Mon Sep 17 00:00:00 2001 From: Jeremy Heiler Date: Mon, 24 Aug 2015 12:08:31 -0500 Subject: [PATCH 089/854] CLJ-1609 Fix an edge case in the Reflector's search for a public method declaration Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Reflector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Reflector.java b/src/jvm/clojure/lang/Reflector.java index 117a56bef2..dd534e62a4 100644 --- a/src/jvm/clojure/lang/Reflector.java +++ b/src/jvm/clojure/lang/Reflector.java @@ -83,7 +83,7 @@ else if(methods.size() == 1) { //public method of non-public class, try to find it in hierarchy Method oldm = m; - m = getAsMethodOfPublicBase(m.getDeclaringClass(), m); + m = getAsMethodOfPublicBase(target.getClass(), m); if(m == null) throw new IllegalArgumentException("Can't call public method of non-public class: " + oldm.toString()); From 26881631ef6b15b5401145be62527072d4b88aef Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 18 Aug 2015 16:50:18 -0500 Subject: [PATCH 090/854] CLJ-1609 reflector test Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/reflect.clj | 7 ++++++- test/java/reflector/IBar.java | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 test/java/reflector/IBar.java diff --git a/test/clojure/test_clojure/reflect.clj b/test/clojure/test_clojure/reflect.clj index 4ade5baa57..23f5ac1238 100644 --- a/test/clojure/test_clojure/reflect.clj +++ b/test/clojure/test_clojure/reflect.clj @@ -1,6 +1,7 @@ (ns clojure.test-clojure.reflect (:use clojure.data [clojure.reflect :as reflect] clojure.test clojure.pprint) - (:import [clojure.reflect AsmReflector JavaReflector])) + (:import [clojure.reflect AsmReflector JavaReflector] + [reflector IBar$Factory])) (defn nodiff [x y] @@ -32,3 +33,7 @@ (deftest internal-name->class-symbol-test (are [s n] (= s (@#'reflect/internal-name->class-symbol n)) 'java.lang.Exception "java/lang/Exception")) + +(def inst (IBar$Factory/get)) +(deftest invoking-nonpublic-super + (is (= "stuff" (.stuff inst)))) \ No newline at end of file diff --git a/test/java/reflector/IBar.java b/test/java/reflector/IBar.java new file mode 100644 index 0000000000..41a952a520 --- /dev/null +++ b/test/java/reflector/IBar.java @@ -0,0 +1,20 @@ +package reflector; + +public interface IBar { + String stuff(); + + class Factory { + public static IBar get() { + return new SubBar(); + } + } +} + +class Bar { + public String stuff() { + return "stuff"; + } +} + +class SubBar extends Bar implements IBar { +} From 5fa11fd689298d17bf634c646dab454d44d5088c Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Tue, 11 Aug 2015 10:54:08 -0500 Subject: [PATCH 091/854] CLJ-1586: Compiler doesn't preserve metadata for lazyseq literals Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 2 ++ test/clojure/test_clojure/compilation.clj | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 16976a5c84..e11672bda1 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6645,9 +6645,11 @@ private static Expr analyze(C context, Object form, String name) { { if(form instanceof LazySeq) { + Object mform = form; form = RT.seq(form); if(form == null) form = PersistentList.EMPTY; + form = ((IObj)form).withMeta(RT.meta(mform)); } if(form == null) return NIL_EXPR; diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index 56ce5fd6a9..7e76b6d5ea 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -342,3 +342,6 @@ (deftest clj-1399 ;; throws an exception on failure (is (eval `(fn [] ~(CLJ1399. 1))))) + +(deftest CLJ-1586-lazyseq-literals-preserve-metadata + (should-not-reflect (eval (list '.substring (with-meta (concat '(identity) '("foo")) {:tag 'String}) 0)))) \ No newline at end of file From 722e023ea00b27a47b11afc29fa7fe0a282696f4 Mon Sep 17 00:00:00 2001 From: Nola Stowe Date: Sun, 26 Jul 2015 09:31:35 -0500 Subject: [PATCH 092/854] CLJ-1449 Add new clojure.string functions Signed-off-by: Stuart Halloway --- src/clj/clojure/string.clj | 62 ++++++++++++++++++++++++++++ test/clojure/test_clojure/string.clj | 44 ++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src/clj/clojure/string.clj b/src/clj/clojure/string.clj index bc3b4e4429..910403ee58 100644 --- a/src/clj/clojure/string.clj +++ b/src/clj/clojure/string.clj @@ -43,6 +43,8 @@ Design notes for clojure.string: (:import (java.util.regex Pattern Matcher) clojure.lang.LazilyPersistentVector)) +(set! *warn-on-reflection* true) + (defn ^String reverse "Returns s with its characters reversed." {:added "1.2"} @@ -312,3 +314,63 @@ Design notes for clojure.string: (.append buffer replacement) (.append buffer ch)) (recur (inc index) buffer))))) + +(defn index-of + "Return index of value (string or char) in s, optionally searching + forward from from-index or nil if not found." + {:added "1.8"} + ([^CharSequence s value] + (let [result ^long + (if (instance? Character value) + (.indexOf (.toString s) ^int (.charValue ^Character value)) + (.indexOf (.toString s) ^String value))] + (if (= result -1) + nil + result))) + ([^CharSequence s value ^long from-index] + (let [result ^long + (if (instance? Character value) + (.indexOf (.toString s) ^int (.charValue ^Character value) (unchecked-int from-index)) + (.indexOf (.toString s) ^String value (unchecked-int from-index)))] + (if (= result -1) + nil + result)))) + +(defn last-index-of + "Return last index of value (string or char) in s, optionally + searching backward from from-index or nil if not found." + {:added "1.8"} + ([^CharSequence s value] + (let [result ^long + (if (instance? Character value) + (.lastIndexOf (.toString s) ^int (.charValue ^Character value)) + (.lastIndexOf (.toString s) ^String value))] + (if (= result -1) + nil + result))) + ([^CharSequence s value ^long from-index] + (let [result ^long + (if (instance? Character value) + (.lastIndexOf (.toString s) ^int (.charValue ^Character value) (unchecked-int from-index)) + (.lastIndexOf (.toString s) ^String value (unchecked-int from-index)))] + (if (= result -1) + nil + result)))) + +(defn starts-with? + "True if s starts with substr." + {:added "1.8"} + [^CharSequence s ^String substr] + (.startsWith (.toString s) substr)) + +(defn ends-with? + "True if s ends with substr." + {:added "1.8"} + [^CharSequence s ^String substr] + (.endsWith (.toString s) substr)) + +(defn includes? + "True if s includes substr." + {:added "1.8"} + [^CharSequence s ^CharSequence substr] + (.contains (.toString s) substr)) diff --git a/test/clojure/test_clojure/string.clj b/test/clojure/test_clojure/string.clj index 525364243d..c6dfe92438 100644 --- a/test/clojure/test_clojure/string.clj +++ b/test/clojure/test_clojure/string.clj @@ -2,6 +2,8 @@ (:require [clojure.string :as s]) (:use clojure.test)) +(set! *warn-on-reflection* true) + (deftest t-split (is (= ["a" "b"] (s/split "a-b" #"-"))) (is (= ["a" "b-c"] (s/split "a-b-c" #"-" 2))) @@ -145,3 +147,45 @@ (is (vector? result))) (is (= (list "foo") (s/split-lines "foo")))) +(deftest t-index-of + (let [sb (StringBuffer. "tacos")] + (is (= 2 (s/index-of sb "c"))) + (is (= 2 (s/index-of sb \c))) + (is (= 1 (s/index-of sb "ac"))) + (is (= 3 (s/index-of sb "o" 2))) + (is (= 3 (s/index-of sb \o 2))) + (is (= 3 (s/index-of sb "o" -100))) + (is (= nil (s/index-of sb "z"))) + (is (= nil (s/index-of sb \z))) + (is (= nil (s/index-of sb "z" 2))) + (is (= nil (s/index-of sb \z 2))) + (is (= nil (s/index-of sb "z" 100)) + (is (= nil (s/index-of sb "z" -10)))))) + +(deftest t-last-index-of + (let [sb (StringBuffer. "banana")] + (is (= 4 (s/last-index-of sb "n"))) + (is (= 4 (s/last-index-of sb \n))) + (is (= 3 (s/last-index-of sb "an"))) + (is (= 4 (s/last-index-of sb "n" ))) + (is (= 4 (s/last-index-of sb "n" 5))) + (is (= 4 (s/last-index-of sb \n 5))) + (is (= 4 (s/last-index-of sb "n" 500))) + (is (= nil (s/last-index-of sb "z"))) + (is (= nil (s/last-index-of sb "z" 1))) + (is (= nil (s/last-index-of sb \z 1))) + (is (= nil (s/last-index-of sb "z" 100)) + (is (= nil (s/last-index-of sb "z" -10)))))) + +(deftest t-starts-with? + (is (s/starts-with? (StringBuffer. "clojure west") "clojure")) + (is (not (s/starts-with? (StringBuffer. "conj") "clojure")))) + +(deftest t-ends-with? + (is (s/ends-with? (StringBuffer. "Clojure West") "West") + (is (not (s/ends-with? (StringBuffer. "Conj") "West"))))) + +(deftest t-includes? + (let [sb (StringBuffer. "Clojure Applied Book")] + (is (s/includes? sb "Applied")) + (is (not (s/includes? sb "Living"))))) From 87a7d7892a0cbf80b639cd33f81009667c2dded6 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Tue, 4 Aug 2015 15:27:25 +0200 Subject: [PATCH 093/854] CLJ-1232: auto-qualify :arglists class names Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 18 +++++-- src/jvm/clojure/lang/Compiler.java | 63 ++++++++++------------- test/clojure/test_clojure/compilation.clj | 5 ++ 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 1d13d76655..dc50204f09 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -237,13 +237,25 @@ (if (next body) (with-meta arglist (conj (if (meta arglist) (meta arglist) {}) (first body))) arglist) - arglist)))] + arglist))) + resolve-tag (fn [argvec] + (let [m (meta argvec) + ^clojure.lang.Symbol tag (:tag m)] + (if (instance? clojure.lang.Symbol tag) + (if (clojure.lang.Util/equiv (.indexOf (.getName tag) ".") -1) + (if (clojure.lang.Util/equals nil (clojure.lang.Compiler$HostExpr/maybeSpecialTag tag)) + (let [t (.getName (clojure.lang.Compiler$HostExpr/maybeClass tag false)) + resolvedtag (clojure.lang.Symbol/intern t)] + (with-meta argvec (assoc m :tag resolvedtag))) + argvec) + argvec) + argvec)))] (if (seq? (first fdecl)) (loop [ret [] fdecls fdecl] (if fdecls - (recur (conj ret (asig (first fdecls))) (next fdecls)) + (recur (conj ret (resolve-tag (asig (first fdecls)))) (next fdecls)) (seq ret))) - (list (asig fdecl)))))) + (list (resolve-tag (asig fdecl))))))) (def diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index e11672bda1..a2f04de8f4 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -1014,7 +1014,7 @@ else if(instance != null && instance.hasJavaClass() && instance.getJavaClass() ! } } - private static Class maybeClass(Object form, boolean stringOk) { + public static Class maybeClass(Object form, boolean stringOk) { if(form instanceof Class) return (Class) form; Class c = null; @@ -1074,6 +1074,32 @@ else if(stringOk && form instanceof String) return className; } */ + public static Class maybeSpecialTag(Symbol sym) { + Class c = primClass(sym); + if (c != null) + return c; + else if(sym.name.equals("objects")) + c = Object[].class; + else if(sym.name.equals("ints")) + c = int[].class; + else if(sym.name.equals("longs")) + c = long[].class; + else if(sym.name.equals("floats")) + c = float[].class; + else if(sym.name.equals("doubles")) + c = double[].class; + else if(sym.name.equals("chars")) + c = char[].class; + else if(sym.name.equals("shorts")) + c = short[].class; + else if(sym.name.equals("bytes")) + c = byte[].class; + else if(sym.name.equals("booleans")) + c = boolean[].class; + return c; + } + + static Class tagToClass(Object tag) { Class c = null; if(tag instanceof Symbol) @@ -1081,40 +1107,7 @@ static Class tagToClass(Object tag) { Symbol sym = (Symbol) tag; if(sym.ns == null) //if ns-qualified can't be classname { - if(sym.name.equals("objects")) - c = Object[].class; - else if(sym.name.equals("ints")) - c = int[].class; - else if(sym.name.equals("longs")) - c = long[].class; - else if(sym.name.equals("floats")) - c = float[].class; - else if(sym.name.equals("doubles")) - c = double[].class; - else if(sym.name.equals("chars")) - c = char[].class; - else if(sym.name.equals("shorts")) - c = short[].class; - else if(sym.name.equals("bytes")) - c = byte[].class; - else if(sym.name.equals("booleans")) - c = boolean[].class; - else if(sym.name.equals("int")) - c = Integer.TYPE; - else if(sym.name.equals("long")) - c = Long.TYPE; - else if(sym.name.equals("float")) - c = Float.TYPE; - else if(sym.name.equals("double")) - c = Double.TYPE; - else if(sym.name.equals("char")) - c = Character.TYPE; - else if(sym.name.equals("short")) - c = Short.TYPE; - else if(sym.name.equals("byte")) - c = Byte.TYPE; - else if(sym.name.equals("boolean")) - c = Boolean.TYPE; + c = maybeSpecialTag(sym); } } if(c == null) diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index 7e76b6d5ea..76360ad70a 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -140,6 +140,11 @@ (should-not-reflect #(.floatValue (clojure.test-clojure.compilation/hinted "arg"))) (should-not-reflect #(.size (clojure.test-clojure.compilation/hinted :many :rest :args :here)))) +(deftest CLJ-1232-qualify-hints + (let [arglists (-> #'clojure.test-clojure.compilation/hinted meta :arglists)] + (is (= 'java.lang.String (-> arglists first meta :tag))) + (is (= 'java.lang.Integer (-> arglists second meta :tag))))) + (defn ^String hinting-conflict ^Integer []) (deftest calls-use-arg-vector-hint From b9330ec9c9451a2dd1d0bc54310524ec910f4230 Mon Sep 17 00:00:00 2001 From: OHTA Shogo Date: Fri, 15 May 2015 10:45:45 +0900 Subject: [PATCH 094/854] CLJ-1319 Throw on odd arguments to PersistentArrayMap.createAsIfByAssoc Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/PersistentArrayMap.java | 2 ++ test/clojure/test_clojure/data_structures.clj | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java index d563fb496e..f825720752 100644 --- a/src/jvm/clojure/lang/PersistentArrayMap.java +++ b/src/jvm/clojure/lang/PersistentArrayMap.java @@ -74,6 +74,8 @@ static public PersistentArrayMap createWithCheck(Object[] init){ } static public PersistentArrayMap createAsIfByAssoc(Object[] init){ + if ((init.length & 1) == 1) + throw new IllegalArgumentException(String.format("No value supplied for key: %s", init[init.length-1])); // If this looks like it is doing busy-work, it is because it // is achieving these goals: O(n^2) run time like // createWithCheck(), never modify init arg, and only diff --git a/test/clojure/test_clojure/data_structures.clj b/test/clojure/test_clojure/data_structures.clj index 27e7705237..68c8100811 100644 --- a/test/clojure/test_clojure/data_structures.clj +++ b/test/clojure/test_clojure/data_structures.clj @@ -1062,6 +1062,9 @@ {x1 v4a, w5a v4c, v4a z3b, y2 2} [x1 v4a, w5a v4a, w5b v4b, v4a z3a, y2 2, v4b z3b, w5c v4c]))) +(deftest test-array-map-arity + (is (thrown? IllegalArgumentException + (array-map 1 2 3)))) (deftest test-assoc (are [x y] (= x y) From 91af4c5d070b64c28e02dc8de6dccfb5fec53505 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 4 Sep 2015 14:09:51 -0500 Subject: [PATCH 095/854] Changelog update for 1.8.0-alpha5 Signed-off-by: Stuart Halloway --- changes.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/changes.md b/changes.md index eed08ada17..b95478e3a7 100644 --- a/changes.md +++ b/changes.md @@ -21,6 +21,19 @@ redefinitions seen by core code. Functions declared as dynamic will never be direct linked. +### 1.2 String Functions + +Several new string functions were added to clojure.string to increase +portability and reduce the need for Java interop calls: + +* index-of - search for the index of a char or string in a string +* last-index-of - search for the index of a char or string backwards in a string +* starts-with? - true if string starts with a substring +* ends-with? - true if string ends with a substring +* includes? - true if string includes a substring + +[CLJ-1449](http://dev.clojure.org/jira/browse/CLJ-1449) + ## 2 Enhancements ### 2.1 Error messages @@ -57,8 +70,6 @@ Functions declared as dynamic will never be direct linked. Classes generated by gen-class aren't loadable from remote codebase * [CLJ-1225](http://dev.clojure.org/jira/browse/CLJ-1225) quot overflow issues around Long/MIN_VALUE for BigInt -* [CLJ-1250](http://dev.clojure.org/jira/browse/CLJ-1250) - Reducer (and folder) instances hold onto the head of seqs * [CLJ-1313](http://dev.clojure.org/jira/browse/CLJ-1313) Correct a few unit tests * [CLJ-1319](http://dev.clojure.org/jira/browse/CLJ-1319) @@ -95,6 +106,16 @@ Functions declared as dynamic will never be direct linked. Spelling mistake in clojure.test/use-fixtures * [CLJ-1785](http://dev.clojure.org/jira/browse/CLJ-1785) Reader conditionals throw when returning nil +* [CLJ-1766](http://dev.clojure.org/jira/browse/CLJ-1766) + Serializing+deserializing lists breaks their hash +* [CLJ-1609](http://dev.clojure.org/jira/browse/CLJ-1609) + Edge case in Reflector's search for a public method declaration +* [CLJ-1586](http://dev.clojure.org/jira/browse/CLJ-1586) + Compiler doesn't preserve metadata for LazySeq literals +* [CLJ-1232](http://dev.clojure.org/jira/browse/CLJ-1232) + Functions with non-qualified return type hints will now work without + import from other namespace +* Records and types without fields eval to empty map # Changes to Clojure in Version 1.7 From 8e9ec1e9dd7cd2a0979c5937033d564cd86a09ae Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Sun, 6 Sep 2015 14:18:16 -0700 Subject: [PATCH 096/854] CLJ-1812: Fix example test to pass on Windows Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/pprint/test_pretty.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/clojure/test_clojure/pprint/test_pretty.clj b/test/clojure/test_clojure/pprint/test_pretty.clj index b7552cf748..c9b321fdff 100644 --- a/test/clojure/test_clojure/pprint/test_pretty.clj +++ b/test/clojure/test_clojure/pprint/test_pretty.clj @@ -380,6 +380,7 @@ It is implemented with a number of custom enlive templates.\" (let [calendar (doto (java.util.GregorianCalendar. 2014 3 29 14 0 0) (.setTimeZone (java.util.TimeZone/getTimeZone "GMT"))) calendar-str (with-out-str (pprint calendar))] - (is (= calendar-str "#inst \"2014-04-29T14:00:00.000+00:00\"\n") + (is (= (str/split-lines calendar-str) + ["#inst \"2014-04-29T14:00:00.000+00:00\""]) "calendar object pretty prints"))) From b007c2d184a70e40b290bc3a48cdff983a5850d5 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 9 Sep 2015 09:24:16 -0500 Subject: [PATCH 097/854] CLJ-1671 server socket repl Signed-off-by: Stuart Halloway --- build.xml | 1 + src/clj/clojure/core/server.clj | 172 +++++++++++++++++++++++++++ src/jvm/clojure/lang/RT.java | 7 ++ test/clojure/test_clojure/server.clj | 29 +++++ 4 files changed, 209 insertions(+) create mode 100644 src/clj/clojure/core/server.clj create mode 100644 test/clojure/test_clojure/server.clj diff --git a/build.xml b/build.xml index c2880ac15f..962b463ace 100644 --- a/build.xml +++ b/build.xml @@ -56,6 +56,7 @@ + diff --git a/src/clj/clojure/core/server.clj b/src/clj/clojure/core/server.clj new file mode 100644 index 0000000000..929c83eb09 --- /dev/null +++ b/src/clj/clojure/core/server.clj @@ -0,0 +1,172 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns ^{:doc "Socket server support" + :author "Alex Miller"} + clojure.core.server + (:require [clojure.string :as str] + [clojure.edn :as edn] + [clojure.main :as m]) + (:import [java.net InetAddress Socket ServerSocket SocketException])) + +(set! *warn-on-reflection* true) + +(def ^:dynamic *session* nil) + +;; lock protects servers +(defonce ^:private lock (Object.)) +(defonce ^:private servers {}) + +(defmacro ^:private thread + [^String name daemon & body] + `(doto (Thread. (fn [] ~@body) ~name) + (.setDaemon ~daemon) + (.start))) + +(defn- required + "Throw if opts does not contain prop." + [opts prop] + (when (nil? (get opts prop)) + (throw (ex-info (str "Missing required socket server property " prop) opts)))) + +(defn- validate-opts + "Validate server config options" + [{:keys [name port accept] :as opts}] + (doseq [prop [:name :port :accept]] (required opts prop)) + (when (or (not (integer? port)) (not (< -1 port 65535))) + (throw (ex-info (str "Invalid socket server port: " port) opts)))) + +(defn- accept-connection + "Start accept function, to be invoked on a client thread, given: + conn - client socket + name - server name + client-id - client identifier + in - in stream + out - out stream + err - err stream + accept - accept fn symbol to invoke + args - to pass to accept-fn" + [^Socket conn name client-id in out err accept args] + (try + (binding [*in* in + *out* out + *err* err + *session* {:server name :client client-id}] + (locking lock + (alter-var-root #'servers assoc-in [name :sessions client-id] {})) + (let [accept-fn (resolve accept)] + (require (symbol (namespace accept))) + (apply accept-fn args))) + (catch SocketException _disconnect) + (finally + (locking lock + (alter-var-root #'servers update-in [name :sessions] dissoc client-id)) + (.close conn)))) + +(defn start-server + "Start a socket server given the specified opts: + :address Host or address, string, defaults to loopback address + :port Port, integer, required + :name Name, required + :accept Namespaced symbol of the accept function to invoke, required + :args Vector of args to pass to accept function + :bind-err Bind *err* to socket out stream?, defaults to true + :server-daemon Is server thread a daemon?, defaults to true + :client-daemon Are client threads daemons?, defaults to true + Returns server socket." + [opts] + (validate-opts opts) + (let [{:keys [address port name accept args bind-err server-daemon client-daemon] + :or {bind-err true + server-daemon true + client-daemon true}} opts + address (InetAddress/getByName address) ;; nil returns loopback + socket (ServerSocket. port 0 address)] + (locking lock + (alter-var-root #'servers assoc name {:name name, :socket socket, :sessions {}})) + (thread + (str "Clojure Server " name) server-daemon + (try + (loop [client-counter 1] + (when (not (.isClosed socket)) + (try + (let [conn (.accept socket) + in (clojure.lang.LineNumberingPushbackReader. (java.io.InputStreamReader. (.getInputStream conn))) + out (java.io.BufferedWriter. (java.io.OutputStreamWriter. (.getOutputStream conn))) + client-id (str client-counter)] + (thread + (str "Clojure Connection " name " " client-id) client-daemon + (accept-connection conn name client-id in out (if bind-err out *err*) accept args))) + (catch SocketException _disconnect)) + (recur (inc client-counter)))) + (finally + (locking lock + (alter-var-root #'servers dissoc name))))) + socket)) + +(defn stop-server + "Stop server with name or use the server-name from *session* if none supplied. + Returns true if server stopped successfully, nil if not found, or throws if + there is an error closing the socket." + ([] + (stop-server (:server *session*))) + ([name] + (locking lock + (let [server-socket ^ServerSocket (get-in servers [name :socket])] + (when server-socket + (alter-var-root #'servers dissoc name) + (.close server-socket) + true))))) + +(defn stop-servers + "Stop all servers ignores all errors, and returns nil." + [] + (locking lock + (doseq [name (keys servers)] + (future (stop-server name))))) + +(defn- parse-props + "Parse clojure.server.* from properties to produce a map of server configs." + [props] + (reduce + (fn [acc [^String k ^String v]] + (let [[k1 k2 k3] (str/split k #"\.")] + (if (and (= k1 "clojure") (= k2 "server")) + (conj acc (merge {:name k3} (edn/read-string v))) + acc))) + [] props)) + +(defn start-servers + "Start all servers specified in the system properties." + [system-props] + (doseq [server (parse-props system-props)] + (start-server server))) + +(defn repl-init + "Initialize repl in user namespace and make standard repl requires." + [] + (in-ns 'user) + (apply require clojure.main/repl-requires)) + +(defn repl-read + "Enhanced :read hook for repl supporting :repl/quit." + [request-prompt request-exit] + (or ({:line-start request-prompt :stream-end request-exit} + (m/skip-whitespace *in*)) + (let [input (read {:read-cond :allow} *in*)] + (m/skip-if-eol *in*) + (case input + :repl/quit request-exit + input)))) + +(defn repl + "REPL with predefined hooks for attachable socket server." + [] + (m/repl + :init repl-init + :read repl-read)) \ No newline at end of file diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 3678e886c7..07e82d5f11 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -473,6 +473,13 @@ static void doInit() throws ClassNotFoundException, IOException{ in_ns.invoke(USER); refer.invoke(CLOJURE); maybeLoadResourceScript("user.clj"); + + // start socket servers + Var require = var("clojure.core", "require"); + Symbol SERVER = Symbol.intern("clojure.core.server"); + require.invoke(SERVER); + Var start_servers = var("clojure.core.server", "start-servers"); + start_servers.invoke(System.getProperties()); } finally { Var.popThreadBindings(); diff --git a/test/clojure/test_clojure/server.clj b/test/clojure/test_clojure/server.clj new file mode 100644 index 0000000000..1d24d0b2dd --- /dev/null +++ b/test/clojure/test_clojure/server.clj @@ -0,0 +1,29 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +; Author: Alex Miller + +(ns clojure.test-clojure.server + (:require [clojure.test :refer :all]) + (:require [clojure.core.server :as s])) + +(defn check-invalid-opts + [opts msg] + (try + (#'clojure.core.server/validate-opts opts) + (is nil) + (catch Exception e + (is (= (ex-data e) opts)) + (is (= msg (.getMessage e)))))) + +(deftest test-validate-opts + (check-invalid-opts {} "Missing required socket server property :name") + (check-invalid-opts {:name "a" :accept 'clojure.core/+} "Missing required socket server property :port") + (doseq [port [-1 "5" 999999]] + (check-invalid-opts {:name "a" :port port :accept 'clojure.core/+} (str "Invalid socket server port: " port))) + (check-invalid-opts {:name "a" :port 5555} "Missing required socket server property :accept")) From df85e2e41d7fa336cb2dbb724ae6ed3a9e5472f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ragnar=20Dahl=C3=A9n?= Date: Thu, 16 Jul 2015 17:24:28 +0100 Subject: [PATCH 098/854] Fix regressions with symbol/keyword destructuring. Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 24 ++++++++++++------------ test/clojure/test_clojure/special.clj | 16 +++++++++++++--- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index dc50204f09..ee67278b9d 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -4294,24 +4294,24 @@ (if (seq bes) (let [bb (key (first bes)) bk (val (first bes)) - has-default (contains? defaults bb)] - (recur (pb ret bb (if has-default - (list `get gmap bk (defaults bb)) - (list `get gmap bk))) + bv (if (contains? defaults bb) + (list `get gmap bk (defaults bb)) + (list `get gmap bk))] + (recur (cond + (symbol? bb) (-> ret (conj (if (namespace bb) (symbol (name bb)) bb)) (conj bv)) + (keyword? bb) (-> ret (conj (symbol (name bb)) bv)) + :else (pb ret bb bv)) (next bes))) ret))))] (cond - (symbol? b) (-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v)) - (keyword? b) (-> bvec (conj (symbol (name b))) (conj v)) - (vector? b) (pvec bvec b v) - (map? b) (pmap bvec b v) - :else (throw (new Exception (str "Unsupported binding form: " b)))))) + (symbol? b) (-> bvec (conj b) (conj v)) + (vector? b) (pvec bvec b v) + (map? b) (pmap bvec b v) + :else (throw (new Exception (str "Unsupported binding form: " b)))))) process-entry (fn [bvec b] (pb bvec (first b) (second b)))] (if (every? symbol? (map first bents)) bindings - (if-let [kwbs (seq (filter #(keyword? (first %)) bents))] - (throw (new Exception (str "Unsupported binding key: " (ffirst kwbs)))) - (reduce1 process-entry [] bents))))) + (reduce1 process-entry [] bents)))) (defmacro let "binding => binding-form init-expr diff --git a/test/clojure/test_clojure/special.clj b/test/clojure/test_clojure/special.clj index 58a9dd9a46..39ec495a84 100644 --- a/test/clojure/test_clojure/special.clj +++ b/test/clojure/test_clojure/special.clj @@ -53,10 +53,20 @@ (is (= 2 d)))) (deftest keywords-not-allowed-in-let-bindings - (is (thrown-with-msg? Exception #"Unsupported binding key: :a" + (is (thrown-with-msg? Exception #"Unsupported binding form: :a" (eval '(let [:a 1] a)))) - (is (thrown-with-msg? Exception #"Unsupported binding key: :a/b" - (eval '(let [:a/b 1] b))))) + (is (thrown-with-msg? Exception #"Unsupported binding form: :a/b" + (eval '(let [:a/b 1] b)))) + (is (thrown-with-msg? Exception #"Unsupported binding form: :a" + (eval '(let [[:a] [1]] a)))) + (is (thrown-with-msg? Exception #"Unsupported binding form: :a/b" + (eval '(let [[:a/b] [1]] b))))) + +(deftest namespaced-syms-only-allowed-in-map-destructuring + (is (thrown-with-msg? Exception #"Can't let qualified name: a/x" + (eval '(let [a/x 1, [y] [1]] x)))) + (is (thrown-with-msg? Exception #"Can't let qualified name: a/x" + (eval '(let [[a/x] [1]] x))))) (require '[clojure.string :as s]) (deftest resolve-keyword-ns-alias-in-destructuring From c0364204dd35007d66fe0207b7100512a56dbc19 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 10 Sep 2015 09:30:12 -0500 Subject: [PATCH 099/854] more changelog updates for 1.8.0-alpha5 Signed-off-by: Stuart Halloway --- changes.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/changes.md b/changes.md index b95478e3a7..43e826a73e 100644 --- a/changes.md +++ b/changes.md @@ -34,10 +34,58 @@ portability and reduce the need for Java interop calls: [CLJ-1449](http://dev.clojure.org/jira/browse/CLJ-1449) +### 1.3 Socket Server and REPL + +The Clojure runtime now has the ability to start a socket server at initialization +based on system properties. One expected use for this is serving a socket-based +REPL, but it also has many other potential uses for dynamically adding server +capability to existing programs without code changes. + +A socket server will be started for each JVM system property like +"clojure.server.". The value for this property is an edn map +representing the configuration of the socket server with the following properties: + +* address - host or address, defaults to loopback +* port - positive integer, required +* accept - namespaced symbol of function to invoke on socket accept, required +* args - sequential collection of args to pass to accept +* bind-err - defaults to true, binds `*err*` to socket out stream +* server-daemon - defaults to true, socket server thread doesn't block exit +* client-daemon - defaults to true, socket client thread doesn't block exit + +Additionally, there is a repl function provided that is slightly customized for +use with the socket server in `clojure.core.server/repl`. + +Following is an example of starting a socket server with a repl listener. +This can be added to any existing Clojure program to allow it to accept +external REPL clients. + +``` +-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}" +``` + +An example client you can use to connect to this socket repl is telnet: + +``` +$ telnet 127.0.0.1 5555 +Trying 127.0.0.1... +Connected to localhost. +Escape character is '^]'. +user=> (println "hello") +hello +``` + +See: +* [CLJ-1671](http://dev.clojure.org/jira/browse/CLJ-1671) +* [Socket REPL design page](http://dev.clojure.org/display/design/Socket+Server+REPL) + ## 2 Enhancements ### 2.1 Error messages +* [CLJ-1778](http://dev.clojure.org/jira/browse/CLJ-1778) + let-bound namespace-qualified bindings should throw (if not map destructuring) + ### 2.2 Documentation strings * [CLJ-1060](http://dev.clojure.org/jira/browse/CLJ-1060) @@ -115,6 +163,8 @@ portability and reduce the need for Java interop calls: * [CLJ-1232](http://dev.clojure.org/jira/browse/CLJ-1232) Functions with non-qualified return type hints will now work without import from other namespace +* [CLJ-1812](http://dev.clojure.org/jira/browse/CLJ-1812) + Fix test failure on windows due to line endings * Records and types without fields eval to empty map # Changes to Clojure in Version 1.7 From 75a2affc2e94407ff7087c70e1c7f6522f2fff4d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 11 Sep 2015 08:17:39 -0500 Subject: [PATCH 100/854] [maven-release-plugin] prepare release clojure-1.8.0-alpha5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80692a98b8..f83fd168ef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-alpha5 http://clojure.org/ Clojure core environment and runtime library. From bc186508ab98514780efbbddb002bf6fd2938aee Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 11 Sep 2015 08:17:39 -0500 Subject: [PATCH 101/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f83fd168ef..80692a98b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-alpha5 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 19d868083d56b50112f43e35cec113499c7171a1 Mon Sep 17 00:00:00 2001 From: Michael Blume Date: Mon, 31 Aug 2015 15:02:47 -0700 Subject: [PATCH 102/854] CLJ-1810 mark ATransientMap public Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/ATransientMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/ATransientMap.java b/src/jvm/clojure/lang/ATransientMap.java index 6b21f6f548..59199a8764 100644 --- a/src/jvm/clojure/lang/ATransientMap.java +++ b/src/jvm/clojure/lang/ATransientMap.java @@ -14,7 +14,7 @@ import clojure.lang.PersistentHashMap.INode; -abstract class ATransientMap extends AFn implements ITransientMap { +public abstract class ATransientMap extends AFn implements ITransientMap { abstract void ensureEditable(); abstract ITransientMap doAssoc(Object key, Object val); abstract ITransientMap doWithout(Object key); From 3a11e27822f10b9d4594e3d4be24670e4d8f68ae Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 9 Oct 2015 08:25:18 -0500 Subject: [PATCH 103/854] CLJ-1765 areduce speed optimization Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index ee67278b9d..21148f4d7a 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5055,10 +5055,10 @@ evaluation of expr at each step, returning ret." {:added "1.0"} [a idx ret init expr] - `(let [a# ~a] + `(let [a# ~a l# (alength a#)] (loop [~idx 0 ~ret ~init] - (if (< ~idx (alength a#)) - (recur (unchecked-inc ~idx) ~expr) + (if (< ~idx l#) + (recur (unchecked-inc-int ~idx) ~expr) ~ret)))) (defn float-array From 206d94c9cfb01f981a157142929c9456c547d6ea Mon Sep 17 00:00:00 2001 From: Jozef Wagner Date: Mon, 4 May 2015 19:45:28 +0200 Subject: [PATCH 104/854] CLJ-1724 - Reuse call to seq() in LazySeq/hashcode for else case Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LazySeq.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/LazySeq.java b/src/jvm/clojure/lang/LazySeq.java index de59d96ba1..78cca8e31c 100644 --- a/src/jvm/clojure/lang/LazySeq.java +++ b/src/jvm/clojure/lang/LazySeq.java @@ -108,7 +108,7 @@ public int hashCode(){ ISeq s = seq(); if(s == null) return 1; - return Util.hash(seq()); + return Util.hash(s); } public int hasheq(){ From 828d82fb5bae9a985f32279b819e22ae436022da Mon Sep 17 00:00:00 2001 From: Steve Miner Date: Fri, 9 Oct 2015 09:12:45 -0500 Subject: [PATCH 105/854] CLJ-1653 - toString() for EmptyList Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/PersistentList.java | 4 ++++ test/clojure/test_clojure/string.clj | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/jvm/clojure/lang/PersistentList.java b/src/jvm/clojure/lang/PersistentList.java index 60b83fc676..618d52fca0 100644 --- a/src/jvm/clojure/lang/PersistentList.java +++ b/src/jvm/clojure/lang/PersistentList.java @@ -159,6 +159,10 @@ public int hasheq(){ return hasheq; } + public String toString() { + return "()"; + } + public boolean equals(Object o) { return (o instanceof Sequential || o instanceof List) && RT.seq(o) == null; } diff --git a/test/clojure/test_clojure/string.clj b/test/clojure/test_clojure/string.clj index c6dfe92438..c929190293 100644 --- a/test/clojure/test_clojure/string.clj +++ b/test/clojure/test_clojure/string.clj @@ -189,3 +189,8 @@ (let [sb (StringBuffer. "Clojure Applied Book")] (is (s/includes? sb "Applied")) (is (not (s/includes? sb "Living"))))) + +(deftest empty-collections + (is (= "()" (str ()))) + (is (= "{}" (str {}))) + (is (= "[]" (str [])))) From cb52f02640da6214003b0b8f3d4b5d067d177342 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 9 Oct 2015 09:18:41 -0500 Subject: [PATCH 106/854] CLJ-1567 Remove unused local in clojure.core/condp Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 21148f4d7a..0804454555 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6136,8 +6136,7 @@ ~(emit pred expr more)) :else `(if-let [p# (~pred ~a ~expr)] (~c p#) - ~(emit pred expr more))))) - gres (gensym "res__")] + ~(emit pred expr more)))))] `(let [~gpred ~pred ~gexpr ~expr] ~(emit gpred gexpr clauses)))) From fd50af1680f40d199bac373f4638e2dde02bfa5a Mon Sep 17 00:00:00 2001 From: Alf Kristian Stoyle Date: Fri, 9 Oct 2015 08:32:35 -0500 Subject: [PATCH 107/854] CLJ-1456 throw error message if too few or too many args to throw Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 4 ++++ test/clojure/test_clojure/compilation.clj | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index a2f04de8f4..f8978e09d4 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -2411,6 +2411,10 @@ static class Parser implements IParser{ public Expr parse(C context, Object form) { if(context == C.EVAL) return analyze(context, RT.list(RT.list(FNONCE, PersistentVector.EMPTY, form))); + else if(RT.count(form) == 1) + throw Util.runtimeException("Too few arguments to throw, throw expects a single Throwable instance"); + else if(RT.count(form) > 2) + throw Util.runtimeException("Too many arguments to throw, throw expects a single Throwable instance"); return new ThrowExpr(analyze(C.EXPRESSION, RT.second(form))); } } diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index 76360ad70a..e050c630a3 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -349,4 +349,10 @@ (is (eval `(fn [] ~(CLJ1399. 1))))) (deftest CLJ-1586-lazyseq-literals-preserve-metadata - (should-not-reflect (eval (list '.substring (with-meta (concat '(identity) '("foo")) {:tag 'String}) 0)))) \ No newline at end of file + (should-not-reflect (eval (list '.substring (with-meta (concat '(identity) '("foo")) {:tag 'String}) 0)))) + +(deftest CLJ-1456-compiler-error-on-incorrect-number-of-parameters-to-throw + (is (thrown? RuntimeException (eval '(defn foo [] (throw))))) + (is (thrown? RuntimeException (eval '(defn foo [] (throw RuntimeException any-symbol))))) + (is (thrown? RuntimeException (eval '(defn foo [] (throw (RuntimeException.) any-symbol))))) + (is (var? (eval '(defn foo [] (throw (IllegalArgumentException.))))))) From 3961f830a5c8a4082bd520ea8746165c4ec88e81 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Sat, 30 Aug 2014 12:59:09 -0700 Subject: [PATCH 108/854] CLJ-1414: sort and sort-by promise stable sort in their doc strings Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 0804454555..b951104592 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -2986,8 +2986,9 @@ (defn sort "Returns a sorted sequence of the items in coll. If no comparator is supplied, uses compare. comparator must implement - java.util.Comparator. If coll is a Java array, it will be modified. - To avoid this, sort a copy of the array." + java.util.Comparator. Guaranteed to be stable: equal elements will + not be reordered. If coll is a Java array, it will be modified. To + avoid this, sort a copy of the array." {:added "1.0" :static true} ([coll] @@ -3003,8 +3004,9 @@ "Returns a sorted sequence of the items in coll, where the sort order is determined by comparing (keyfn item). If no comparator is supplied, uses compare. comparator must implement - java.util.Comparator. If coll is a Java array, it will be modified. - To avoid this, sort a copy of the array." + java.util.Comparator. Guaranteed to be stable: equal elements will + not be reordered. If coll is a Java array, it will be modified. To + avoid this, sort a copy of the array." {:added "1.0" :static true} ([keyfn coll] From a56dacffb7d1ba2c91aff337bbf6060a7854e7ae Mon Sep 17 00:00:00 2001 From: Gordon Syme Date: Mon, 27 Oct 2014 09:52:47 +0000 Subject: [PATCH 109/854] CLJ-1380 - Ensure ex-info data argument is a map The intention from the code and docs is that exceptions created by ex-info should carry a map of additional data. The two-arg constructor ensures that the data argument is a map but it was possible for data to be nil if the three-arg constructor was used. Apply the same argument checks from the two-arg ExceptionInfo constructor to the three-arg constructor. Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/ExceptionInfo.java | 14 +++++++------- test/clojure/test_clojure/errors.clj | 11 +++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/jvm/clojure/lang/ExceptionInfo.java b/src/jvm/clojure/lang/ExceptionInfo.java index 7ccaa0bc9f..92040d21e0 100644 --- a/src/jvm/clojure/lang/ExceptionInfo.java +++ b/src/jvm/clojure/lang/ExceptionInfo.java @@ -19,17 +19,17 @@ public class ExceptionInfo extends RuntimeException implements IExceptionInfo { public final IPersistentMap data; public ExceptionInfo(String s, IPersistentMap data) { - super(s); - if (data instanceof IPersistentMap) { - this.data = data; - } else { - throw new IllegalArgumentException("Additional data must be a persistent map: " + data); - } + this(s, data, null); } public ExceptionInfo(String s, IPersistentMap data, Throwable throwable) { + // null cause is equivalent to not passing a cause super(s, throwable); - this.data = data; + if (data != null) { + this.data = data; + } else { + throw new IllegalArgumentException("Additional data must be non-nil."); + } } public IPersistentMap getData() { diff --git a/test/clojure/test_clojure/errors.clj b/test/clojure/test_clojure/errors.clj index ce457a79eb..9131f6a581 100644 --- a/test/clojure/test_clojure/errors.clj +++ b/test/clojure/test_clojure/errors.clj @@ -80,3 +80,14 @@ (Throwable->map (ex-info "ex-info" {:some "data"}))] (is (= data data-top-level {:some "data"}))))) + +(deftest ex-info-disallows-nil-data + (is (thrown? IllegalArgumentException (ex-info "message" nil))) + (is (thrown? IllegalArgumentException (ex-info "message" nil (Throwable. "cause"))))) + +(deftest ex-info-arities-construct-equivalent-exceptions + (let [ex1 (ex-info "message" {:foo "bar"}) + ex2 (ex-info "message" {:foo "bar"} nil)] + (is (= (.getMessage ex1) (.getMessage ex2))) + (is (= (.getData ex1) (.getData ex2))) + (is (= (.getCause ex1) (.getCause ex2))))) From 47a6d64038052688c4ad2c56d4d2b5078470be0b Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Fri, 9 Oct 2015 08:38:26 -0500 Subject: [PATCH 110/854] CLJ-1351 remove unused swapThunk method generation Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 35 ------------------------------ 1 file changed, 35 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index f8978e09d4..e2fb617f5c 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -4561,41 +4561,6 @@ void compile(String superName, String[] interfaceNames, boolean oneTimeUse) thro emitStatics(cv); emitMethods(cv); - if(keywordCallsites.count() > 0) - { - Method meth = Method.getMethod("void swapThunk(int,clojure.lang.ILookupThunk)"); - - GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC, - meth, - null, - null, - cv); - gen.visitCode(); - Label endLabel = gen.newLabel(); - - Label[] labels = new Label[keywordCallsites.count()]; - for(int i = 0; i < keywordCallsites.count();i++) - { - labels[i] = gen.newLabel(); - } - gen.loadArg(0); - gen.visitTableSwitchInsn(0,keywordCallsites.count()-1,endLabel,labels); - - for(int i = 0; i < keywordCallsites.count();i++) - { - gen.mark(labels[i]); -// gen.loadThis(); - gen.loadArg(1); - gen.putStatic(objtype, thunkNameStatic(i),ILOOKUP_THUNK_TYPE); - gen.goTo(endLabel); - } - - gen.mark(endLabel); - - gen.returnValue(); - gen.endMethod(); - } - //end of class cv.visitEnd(); From 25221dfa32dc099a4896ae522db78fa199c9fe62 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 29 Apr 2015 14:26:36 -0500 Subject: [PATCH 111/854] CLJ-1329 - remove unused local in PersistentVector.cons() Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/PersistentVector.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java index adc39c8492..3aa26dd10f 100644 --- a/src/jvm/clojure/lang/PersistentVector.java +++ b/src/jvm/clojure/lang/PersistentVector.java @@ -215,7 +215,6 @@ public IPersistentMap meta(){ public PersistentVector cons(Object val){ - int i = cnt; //room in tail? // if(tail.length < 32) if(cnt - tailoff() < 32) From fe1772a0079cedeb9bf0c533889906729eee5afa Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 9 Oct 2015 08:43:55 -0500 Subject: [PATCH 112/854] CLJ-1295: Speed up dissoc on array-maps Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/PersistentArrayMap.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java index f825720752..e03de11939 100644 --- a/src/jvm/clojure/lang/PersistentArrayMap.java +++ b/src/jvm/clojure/lang/PersistentArrayMap.java @@ -218,15 +218,8 @@ public IPersistentMap without(Object key){ if(newlen == 0) return empty(); Object[] newArray = new Object[newlen]; - for(int s = 0, d = 0; s < array.length; s += 2) - { - if(!equalKey(array[s], key)) //skip removal key - { - newArray[d] = array[s]; - newArray[d + 1] = array[s + 1]; - d += 2; - } - } + System.arraycopy(array, 0, newArray, 0, i); + System.arraycopy(array, i+2, newArray, i, newlen - i); return create(newArray); } //don't have key, no op From 587655a0d97b141945239590f0f906dbdd278142 Mon Sep 17 00:00:00 2001 From: Gary Fredericks Date: Mon, 9 Dec 2013 09:28:41 -0600 Subject: [PATCH 113/854] CLJ-1282: Throw exception for bad quote arity Decided to throw on (quote) as well, which previously returned nil. Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 10 ++++++++++ test/clojure/test_clojure/special.clj | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index e2fb617f5c..e6d674b138 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -1959,7 +1959,17 @@ else if (v instanceof APersistentVector) } static class Parser implements IParser{ + static Keyword formKey = Keyword.intern("form"); + public Expr parse(C context, Object form){ + int argCount = RT.count(form) - 1; + if(argCount != 1){ + IPersistentMap exData = new PersistentArrayMap(new Object[]{formKey, form}); + throw new ExceptionInfo("Wrong number of args (" + + argCount + + ") passed to quote", + exData); + } Object v = RT.second(form); if(v == null) diff --git a/test/clojure/test_clojure/special.clj b/test/clojure/test_clojure/special.clj index 39ec495a84..9432c45683 100644 --- a/test/clojure/test_clojure/special.clj +++ b/test/clojure/test_clojure/special.clj @@ -73,3 +73,12 @@ (let [{:keys [::s/x ::s/y]} {:clojure.string/x 1 :clojure.string/y 2}] (is (= x 1)) (is (= y 2)))) + +(deftest quote-with-multiple-args + (let [ex (is (thrown? clojure.lang.Compiler$CompilerException + (eval '(quote 1 2 3))))] + (is (= '(quote 1 2 3) + (-> ex + (.getCause) + (ex-data) + (:form)))))) From edce1cc06be054614e403d48ecce8cb8345a179e Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Wed, 9 Oct 2013 22:04:26 -0700 Subject: [PATCH 114/854] CLJ-1277: Speed up instant printing by eliminating reflection Signed-off-by: Stuart Halloway --- src/clj/clojure/instant.clj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/instant.clj b/src/clj/clojure/instant.clj index c5274a79e3..ddece22025 100644 --- a/src/clj/clojure/instant.clj +++ b/src/clj/clojure/instant.clj @@ -11,6 +11,8 @@ [java.sql Timestamp])) +(set! *warn-on-reflection* true) + ;;; ------------------------------------------------------------------------ ;;; convenience macros @@ -157,7 +159,7 @@ with invalid arguments." ;;; ------------------------------------------------------------------------ ;;; print integration -(def ^:private thread-local-utc-date-format +(def ^:private ^ThreadLocal thread-local-utc-date-format ;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access. ;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335 (proxy [ThreadLocal] [] @@ -169,7 +171,7 @@ with invalid arguments." (defn- print-date "Print a java.util.Date as RFC3339 timestamp, always in UTC." [^java.util.Date d, ^java.io.Writer w] - (let [utc-format (.get thread-local-utc-date-format)] + (let [^java.text.DateFormat utc-format (.get thread-local-utc-date-format)] (.write w "#inst \"") (.write w (.format utc-format d)) (.write w "\""))) @@ -203,7 +205,7 @@ with invalid arguments." (print-calendar c w)) -(def ^:private thread-local-utc-timestamp-format +(def ^:private ^ThreadLocal thread-local-utc-timestamp-format ;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access. ;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335 (proxy [ThreadLocal] [] @@ -214,7 +216,7 @@ with invalid arguments." (defn- print-timestamp "Print a java.sql.Timestamp as RFC3339 timestamp, always in UTC." [^java.sql.Timestamp ts, ^java.io.Writer w] - (let [utc-format (.get thread-local-utc-timestamp-format)] + (let [^java.text.DateFormat utc-format (.get thread-local-utc-timestamp-format)] (.write w "#inst \"") (.write w (.format utc-format ts)) ;; add on nanos and offset From 0a6810ab3484b5be0afe4f505cd724eb5c974a09 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 9 Oct 2015 08:57:36 -0500 Subject: [PATCH 115/854] CLJ-1259 Add type hints for pprint and cl-format Signed-off-by: Stuart Halloway --- src/clj/clojure/pprint.clj | 1 + src/clj/clojure/pprint/cl_format.clj | 6 +-- src/clj/clojure/pprint/column_writer.clj | 2 +- src/clj/clojure/pprint/dispatch.clj | 4 +- src/clj/clojure/pprint/pprint_base.clj | 2 +- src/clj/clojure/pprint/pretty_writer.clj | 55 ++++++++++++++---------- 6 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/clj/clojure/pprint.clj b/src/clj/clojure/pprint.clj index ba90aa6588..f09f4da0fa 100644 --- a/src/clj/clojure/pprint.clj +++ b/src/clj/clojure/pprint.clj @@ -38,6 +38,7 @@ complete documentation on the the clojure web site on github.", (:refer-clojure :exclude (deftype)) (:use [clojure.walk :only [walk]])) +(set! *warn-on-reflection* true) (load "pprint/utilities") (load "pprint/column_writer") diff --git a/src/clj/clojure/pprint/cl_format.clj b/src/clj/clojure/pprint/cl_format.clj index 30daa90af4..56fc0984a2 100644 --- a/src/clj/clojure/pprint/cl_format.clj +++ b/src/clj/clojure/pprint/cl_format.clj @@ -174,9 +174,9 @@ http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm (opt-base-str *print-base* n))) (ratio? n) (str (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r"))) - (opt-base-str *print-base* (.numerator n)) + (opt-base-str *print-base* (.numerator ^clojure.lang.Ratio n)) "/" - (opt-base-str *print-base* (.denominator n))) + (opt-base-str *print-base* (.denominator ^clojure.lang.Ratio n))) :else nil)) (defn- format-ascii [print-func params arg-navigator offsets] @@ -681,7 +681,7 @@ string, or one character longer." append-zero (and (not d) (<= (dec (count mantissa)) scaled-exp)) [rounded-mantissa scaled-exp expanded] (round-str mantissa scaled-exp d (if w (- w (if add-sign 1 0)))) - fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d) + ^String fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d) fixed-repr (if (and w d (>= d 1) (= (.charAt fixed-repr 0) \0) diff --git a/src/clj/clojure/pprint/column_writer.clj b/src/clj/clojure/pprint/column_writer.clj index 7e754455d0..704fc0c363 100644 --- a/src/clj/clojure/pprint/column_writer.clj +++ b/src/clj/clojure/pprint/column_writer.clj @@ -54,7 +54,7 @@ (defn- column-writer ([writer] (column-writer writer *default-page-width*)) - ([writer max-columns] + ([^Writer writer max-columns] (let [fields (ref {:max max-columns, :cur 0, :line 0 :base writer})] (proxy [Writer IDeref] [] (deref [] fields) diff --git a/src/clj/clojure/pprint/dispatch.clj b/src/clj/clojure/pprint/dispatch.clj index d8c952bab1..323348eb59 100644 --- a/src/clj/clojure/pprint/dispatch.clj +++ b/src/clj/clojure/pprint/dispatch.clj @@ -19,7 +19,7 @@ (defn- use-method "Installs a function as a new method of multimethod associated with dispatch-value. " - [multifn dispatch-val func] + [^clojure.lang.MultiFn multifn dispatch-val func] (. multifn addMethod dispatch-val func)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -132,7 +132,7 @@ (pprint-newline :linear) (write-out (cond (and (future? o) (not (future-done? o))) :pending - (and (instance? clojure.lang.IPending o) (not (.isRealized o))) :not-delivered + (and (instance? clojure.lang.IPending o) (not (.isRealized ^clojure.lang.IPending o))) :not-delivered :else @o))))) (def ^{:private true} pprint-pqueue (formatter-out "~<<-(~;~@{~w~^ ~_~}~;)-<~:>")) diff --git a/src/clj/clojure/pprint/pprint_base.clj b/src/clj/clojure/pprint/pprint_base.clj index 2d450d8ed7..d95bd2613d 100644 --- a/src/clj/clojure/pprint/pprint_base.clj +++ b/src/clj/clojure/pprint/pprint_base.clj @@ -164,7 +164,7 @@ radix specifier is in the form #XXr where XX is the decimal value of *print-base (make-pretty-writer base-writer# *print-right-margin* *print-miser-width*) base-writer#)] ~@body - (.ppflush *out*)))) + (.ppflush ^PrettyFlush *out*)))) ;;;TODO: if pretty print is not set, don't use pr but rather something that respects *print-base*, etc. diff --git a/src/clj/clojure/pprint/pretty_writer.clj b/src/clj/clojure/pprint/pretty_writer.clj index ac83fe4208..d088a1a02c 100644 --- a/src/clj/clojure/pprint/pretty_writer.clj +++ b/src/clj/clojure/pprint/pretty_writer.clj @@ -36,7 +36,7 @@ (defmacro ^{:private true} getf - "Get the value of the field a named by the argument (which should be a keyword)." + "Get the value of the field named by the argument (which should be a keyword)." [sym] `(~sym @@~'this)) @@ -55,6 +55,14 @@ [& vals#] (apply struct ~type-name ~(keyword name-str) vals#)) (defn- ~(symbol (str name-str "?")) [x#] (= (:type-tag x#) ~(keyword name-str)))))) +(defmacro ^{:private true} + write-to-base + "Call .write on Writer (getf :base) with proper type-hinting to + avoid reflection." + [& args] + `(let [^Writer w# (getf :base)] + (.write w# ~@args))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; The data structures used by pretty-writer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -106,7 +114,7 @@ (let [lb (:logical-block token)] (dosync (when-let [^String prefix (:prefix lb)] - (.write (getf :base) prefix)) + (write-to-base prefix)) (let [col (get-column (getf :base))] (ref-set (:start-col lb) col) (ref-set (:indent lb) col))))) @@ -114,7 +122,7 @@ (defmethod write-token :end-block-t [^Writer this token] (when-let [cb (getf :logical-block-callback)] (cb :end)) (when-let [^String suffix (:suffix (:logical-block token))] - (.write (getf :base) suffix))) + (write-to-base suffix))) (defmethod write-token :indent-t [^Writer this token] (let [lb (:logical-block token)] @@ -125,7 +133,7 @@ :current (get-column (getf :base))))))) (defmethod write-token :buffer-blob [^Writer this token] - (.write (getf :base) ^String (:data token))) + (write-to-base ^String (:data token))) (defmethod write-token :nl-t [^Writer this token] ; (prlabel wt @(:done-nl (:logical-block token))) @@ -135,19 +143,19 @@ @(:done-nl (:logical-block token)))) (emit-nl this token) (if-let [^String tws (getf :trailing-white-space)] - (.write (getf :base) tws))) + (write-to-base tws))) (dosync (setf :trailing-white-space nil))) (defn- write-tokens [^Writer this tokens force-trailing-whitespace] (doseq [token tokens] (if-not (= (:type-tag token) :nl-t) (if-let [^String tws (getf :trailing-white-space)] - (.write (getf :base) tws))) + (write-to-base tws))) (write-token this token) (setf :trailing-white-space (:trailing-white-space token))) (let [^String tws (getf :trailing-white-space)] (when (and force-trailing-whitespace tws) - (.write (getf :base) tws) + (write-to-base tws) (setf :trailing-white-space nil)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -225,15 +233,15 @@ (recur (:parent lb))))))) (defn- emit-nl [^Writer this nl] - (.write (getf :base) (pp-newline)) + (write-to-base ^String (pp-newline)) (dosync (setf :trailing-white-space nil)) (let [lb (:logical-block nl) ^String prefix (:per-line-prefix lb)] (if prefix - (.write (getf :base) prefix)) + (write-to-base prefix)) (let [^String istr (apply str (repeat (- @(:indent lb) (count prefix)) \space))] - (.write (getf :base) istr)) + (write-to-base istr)) (update-nl-state lb))) (defn- split-at-newline [tokens] @@ -315,7 +323,7 @@ (defn- write-white-space [^Writer this] (when-let [^String tws (getf :trailing-white-space)] ; (prlabel wws (str "*" tws "*")) - (.write (getf :base) tws) + (write-to-base tws) (dosync (setf :trailing-white-space nil)))) @@ -337,13 +345,13 @@ (write-buffered-output this)) (do (write-white-space this) - (.write (getf :base) l))) - (.write (getf :base) (int \newline)) + (write-to-base l))) + (write-to-base (int \newline)) (doseq [^String l (next (butlast lines))] - (.write (getf :base) l) - (.write (getf :base) (pp-newline)) + (write-to-base l) + (write-to-base ^String (pp-newline)) (if prefix - (.write (getf :base) prefix))) + (write-to-base prefix))) (setf :buffering :writing) (last lines)))))) @@ -352,7 +360,7 @@ (if (= (getf :mode) :writing) (do (write-white-space this) - (.write (getf :base) c)) + (write-to-base c)) (if (= c \newline) (write-initial-lines this "\n") (let [oldpos (getf :pos) @@ -396,7 +404,7 @@ (if (= mode :writing) (do (write-white-space this) - (.write (getf :base) s) + (write-to-base s) (setf :trailing-white-space white-space)) (let [oldpos (getf :pos) newpos (+ oldpos (count s0))] @@ -418,11 +426,12 @@ (write-white-space this))) (flush [] - (.ppflush this) - (.flush (getf :base))) + (.ppflush ^PrettyFlush this) + (let [^Writer w (getf :base)] + (.flush w))) (close [] - (.flush this))))) + (.flush ^Writer this))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -442,7 +451,7 @@ (write-white-space this) (when-let [cb (getf :logical-block-callback)] (cb :start)) (if prefix - (.write (getf :base) prefix)) + (write-to-base prefix)) (let [col (get-column (getf :base))] (ref-set (:start-col lb) col) (ref-set (:indent lb) col))) @@ -459,7 +468,7 @@ (do (write-white-space this) (if suffix - (.write (getf :base) suffix)) + (write-to-base suffix)) (when-let [cb (getf :logical-block-callback)] (cb :end))) (let [oldpos (getf :pos) newpos (+ oldpos (if suffix (count suffix) 0))] From 4ed7e42738f7a77fe18b7fc1ae5c6ee7f419310c Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Sun, 31 Aug 2014 17:34:44 +0200 Subject: [PATCH 116/854] CLJ-1226: fix set! of instance field expression that refers to a deftype's `this` Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 4 ++-- test/clojure/test_clojure/java_interop.clj | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index e6d674b138..bc3df9b97c 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -1224,12 +1224,12 @@ public void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen, if(targetClass != null && field != null) { target.emit(C.EXPRESSION, objx, gen); - gen.checkCast(Type.getType(targetClass)); + gen.checkCast(getType(targetClass)); val.emit(C.EXPRESSION, objx, gen); gen.visitLineNumber(line, gen.mark()); gen.dupX1(); HostExpr.emitUnboxArg(objx, gen, field.getType()); - gen.putField(Type.getType(targetClass), fieldName, Type.getType(field.getType())); + gen.putField(getType(targetClass), fieldName, Type.getType(field.getType())); } else { diff --git a/test/clojure/test_clojure/java_interop.clj b/test/clojure/test_clojure/java_interop.clj index f8f2ebcc8d..86ba5ca63e 100644 --- a/test/clojure/test_clojure/java_interop.clj +++ b/test/clojure/test_clojure/java_interop.clj @@ -120,6 +120,12 @@ ; set! +(defprotocol p (f [_])) +(deftype t [^:unsynchronized-mutable x] p (f [_] (set! (.x _) 1))) + +(deftest test-set! + (is (= 1 (f (t. 1))))) + ; memfn From 2c9ff46454ebf34b14ab4612f691b3c93031b362 Mon Sep 17 00:00:00 2001 From: Alex Redington and Russ Olsen Date: Fri, 9 Oct 2015 09:01:59 -0500 Subject: [PATCH 117/854] CLJ-1210 Extend IOFactory to nil to provide better error msgs Signed-off-by: Stuart Halloway --- src/clj/clojure/java/io.clj | 10 ++++++++++ test/clojure/test_clojure/java/io.clj | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/src/clj/clojure/java/io.clj b/src/clj/clojure/java/io.clj index e5006c2963..948561a0c3 100644 --- a/src/clj/clojure/java/io.clj +++ b/src/clj/clojure/java/io.clj @@ -282,6 +282,16 @@ IOFactory default-streams-impl) +(extend nil + IOFactory + (assoc default-streams-impl + :make-reader (fn [x opts] + (throw (IllegalArgumentException. + (str "Cannot open <" (pr-str x) "> as a Reader.")))) + :make-writer (fn [x opts] + (throw (IllegalArgumentException. + (str "Cannot open <" (pr-str x) "> as a Writer.")))))) + (defmulti ^{:doc "Internal helper for copy" :private true diff --git a/test/clojure/test_clojure/java/io.clj b/test/clojure/test_clojure/java/io.clj index f06641b527..b83c63d311 100644 --- a/test/clojure/test_clojure/java/io.clj +++ b/test/clojure/test_clojure/java/io.clj @@ -60,6 +60,12 @@ (finally (.delete f))))) +(deftest test-streams-nil + (is (thrown-with-msg? IllegalArgumentException #"Cannot open.*nil" (reader nil))) + (is (thrown-with-msg? IllegalArgumentException #"Cannot open.*nil" (writer nil))) + (is (thrown-with-msg? IllegalArgumentException #"Cannot open.*nil" (input-stream nil))) + (is (thrown-with-msg? IllegalArgumentException #"Cannot open.*nil" (output-stream nil)))) + (defn bytes-should-equal [byte-array-1 byte-array-2 msg] (is (= @#'clojure.java.io/byte-array-type (class byte-array-1) (class byte-array-2)) msg) (is (= (into [] byte-array-1) (into [] byte-array-2)) msg)) From c3be0027c368a39372df48649773b5beb58ce9c6 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Fri, 9 Oct 2015 08:20:36 -0500 Subject: [PATCH 118/854] CLJ-668 improve performance of slurp via jio/copy Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index b951104592..dcb14a22a4 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6652,14 +6652,10 @@ {:added "1.0"} ([f & opts] (let [opts (normalize-slurp-opts opts) - sb (StringBuilder.)] + sw (java.io.StringWriter.)] (with-open [^java.io.Reader r (apply jio/reader f opts)] - (loop [c (.read r)] - (if (neg? c) - (str sb) - (do - (.append sb (char c)) - (recur (.read r))))))))) + (jio/copy r sw) + (.toString sw))))) (defn spit "Opposite of slurp. Opens f with writer, writes content, then From 3748127f440a39d6003e94733da70c2704e385f2 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 12 Oct 2015 20:16:23 -0500 Subject: [PATCH 119/854] Changelog updates for 1.8.0-beta1 Signed-off-by: Stuart Halloway --- changes.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/changes.md b/changes.md index 43e826a73e..fbbc78e6d0 100644 --- a/changes.md +++ b/changes.md @@ -81,10 +81,16 @@ See: ## 2 Enhancements -### 2.1 Error messages +### 2.1 Error handling * [CLJ-1778](http://dev.clojure.org/jira/browse/CLJ-1778) let-bound namespace-qualified bindings should throw (if not map destructuring) +* [CLJ-1456](http://dev.clojure.org/jira/browse/CLJ-1456) + Compiler now errors if too few or too many arguments to throw +* [CLJ-1282](http://dev.clojure.org/jira/browse/CLJ-1282) + quote now throws if passed more or less than one arg +* [CLJ-1210](http://dev.clojure.org/jira/browse/CLJ-1210) + Improved error message for (clojure.java.io/reader nil) ### 2.2 Documentation strings @@ -94,16 +100,40 @@ See: Typo in the docstring of 'with-bindings' * [CLJ-1769](http://dev.clojure.org/jira/browse/CLJ-1769) Docstrings for *' and +' refer to * and + +* [CLJ-1414](http://dev.clojure.org/jira/browse/CLJ-1414) + sort and sort-by now indicate sort is stable in docstring ### 2.3 Performance * [CLJ-703](http://dev.clojure.org/jira/browse/CLJ-703) Improve writeClassFile performance +* [CLJ-1765](http://dev.clojure.org/jira/browse/CLJ-1765) + areduce performance improvements +* [CLJ-1724](http://dev.clojure.org/jira/browse/CLJ-1724) + Remove unnecessary call to seq() in LazySeq.hashCode() +* [CLJ-1295](http://dev.clojure.org/jira/browse/CLJ-1295) + Improved array-map dissoc performance +* [CLJ-1277](http://dev.clojure.org/jira/browse/CLJ-1277) + Speed up printing of time instants with type hints +* [CLJ-1259](http://dev.clojure.org/jira/browse/CLJ-1259) + Speed up pprint and cl-format with type hints +* [CLJ-668](http://dev.clojure.org/jira/browse/CLJ-668) + Improve slurp performance by using StringWriter and jio/copy ### 2.4 Other enhancements * [CLJ-1208](http://dev.clojure.org/jira/browse/CLJ-1208) Optionally require namespace on defrecord class init +* [CLJ-1810](http://dev.clojure.org/jira/browse/CLJ-1810) + ATransientMap now marked public +* [CLJ-1653](http://dev.clojure.org/jira/browse/CLJ-1653) + str of an empty list should be "()" +* [CLJ-1567](http://dev.clojure.org/jira/browse/CLJ-1567) + Removed unused local in condp implementation +* [CLJ-1351](http://dev.clojure.org/jira/browse/CLJ-1351) + Unused swapThunk method was being emitted for fns with keyword callsites +* [CLJ-1329](http://dev.clojure.org/jira/browse/CLJ-1329) + Removed unused local in PersistentVector.cons() ## 3 Bug Fixes @@ -165,6 +195,10 @@ See: import from other namespace * [CLJ-1812](http://dev.clojure.org/jira/browse/CLJ-1812) Fix test failure on windows due to line endings +* [CLJ-1380](http://dev.clojure.org/jira/browse/CLJ-1380) + 3-arg ExceptionInfo constructor permitted nil data +* [CLJ-1226](http://dev.clojure.org/jira/browse/CLJ-1226) + set! of a deftype field using field-access syntax caused ClassCastException * Records and types without fields eval to empty map # Changes to Clojure in Version 1.7 From e883e9c1d6cebef38e1d467b5652534fcd0766cf Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 13 Oct 2015 08:50:12 -0500 Subject: [PATCH 120/854] [maven-release-plugin] prepare release clojure-1.8.0-beta1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80692a98b8..8174973d7e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-beta1 http://clojure.org/ Clojure core environment and runtime library. From f76b343d917532d0b2e1397587e3fdefdd5bd166 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 13 Oct 2015 08:50:12 -0500 Subject: [PATCH 121/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8174973d7e..80692a98b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-beta1 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From d988cefa9838ef1d2635d14ae314ebb416692b24 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Thu, 15 Oct 2015 08:20:06 -0700 Subject: [PATCH 122/854] CLJ-1827: Eliminate reflection warning in pretty_writer.clj Signed-off-by: Stuart Halloway --- src/clj/clojure/pprint/pretty_writer.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/pprint/pretty_writer.clj b/src/clj/clojure/pprint/pretty_writer.clj index d088a1a02c..e3a6e3382a 100644 --- a/src/clj/clojure/pprint/pretty_writer.clj +++ b/src/clj/clojure/pprint/pretty_writer.clj @@ -416,7 +416,7 @@ Long (p-write-char this x))) ([x off len] - (.write this (subs (str x) off (+ off len))))) + (.write ^Writer this (subs (str x) off (+ off len))))) (ppflush [] (if (= (getf :mode) :buffering) From f0de6eed69f4722e9df55d7b4ce7cfec38bad57e Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Mon, 12 Oct 2015 10:20:39 -0500 Subject: [PATCH 123/854] CLJ-1823: document :load-ns option to deftype/defrecord, see CLJ-1208 Signed-off-by: Stuart Halloway --- src/clj/clojure/core_deftype.clj | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index 23fb0c4a9f..03aaba8a78 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -296,8 +296,13 @@ (defmacro defrecord "(defrecord name [fields*] options* specs*) - - Currently there are no options. + + Options are expressed as sequential keywords and arguments (in any order). + + Supported options: + :load-ns - if true, importing the record class will cause the + namespace in which the record was defined to be loaded. + Defaults to false. Each spec consists of a protocol or interface name followed by zero or more method bodies: @@ -401,8 +406,13 @@ (defmacro deftype "(deftype name [fields*] options* specs*) - - Currently there are no options. + + Options are expressed as sequential keywords and arguments (in any order). + + Supported options: + :load-ns - if true, importing the record class will cause the + namespace in which the record was defined to be loaded. + Defaults to false. Each spec consists of a protocol or interface name followed by zero or more method bodies: From 1551fb51583fee1ea881664459a171748be8a0d3 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 27 Oct 2015 10:43:46 -0500 Subject: [PATCH 124/854] CLJ-1834 Build support for testing with and without direct linking Signed-off-by: Stuart Halloway --- build.xml | 6 ++++++ pom.xml | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/build.xml b/build.xml index 962b463ace..cae30b21fb 100644 --- a/build.xml +++ b/build.xml @@ -28,6 +28,8 @@ + + @@ -88,6 +90,7 @@ + Direct linking = ${directlinking} + @@ -109,6 +113,7 @@ + @@ -125,6 +130,7 @@ depends="compile-tests" unless="maven.test.skip"> + diff --git a/pom.xml b/pom.xml index 80692a98b8..9d01d13452 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,10 @@ git@github.com:clojure/clojure.git + + true + + org.codehaus.jsr166-mirror @@ -221,6 +225,19 @@ + + + test-direct + + true + + + + test-no-direct + + false + + distribution From 60e4ff521a03e68831ba858eb3203c08833cadba Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 29 Sep 2015 11:52:08 -0500 Subject: [PATCH 125/854] CLJ-1805 fix boolean logic in rettag Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index bc3df9b97c..d2eb21da98 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -5270,7 +5270,7 @@ static FnMethod parse(ObjExpr objx, ISeq form, Object rettag) { if(rettag != null) { String retstr = ((Symbol)rettag).getName(); - if(!retstr.equals("long") || !retstr.equals("double")) + if(!(retstr.equals("long") || retstr.equals("double"))) rettag = null; } method.retClass = tagClass(tagOf(parms)!=null?tagOf(parms):rettag); From c716a5a8802cc40a45fca8bbddb6c574b42e2a17 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 1 Oct 2015 08:39:46 -0500 Subject: [PATCH 126/854] CLJ-1453 Fix iterator implementations to throw NSEE when exhausted Signed-off-by: Stuart Halloway --- src/clj/clojure/gvec.clj | 15 ++++++++++++--- src/jvm/clojure/lang/APersistentVector.java | 19 +++++++++++++++---- src/jvm/clojure/lang/ArrayIter.java | 20 ++++++++++---------- src/jvm/clojure/lang/PersistentArrayMap.java | 9 +++++++-- src/jvm/clojure/lang/PersistentTreeMap.java | 10 +++++++--- src/jvm/clojure/lang/PersistentVector.java | 13 +++++++++---- 6 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/clj/clojure/gvec.clj b/src/clj/clojure/gvec.clj index c6ee1b4887..7b6e47c4e1 100644 --- a/src/clj/clojure/gvec.clj +++ b/src/clj/clojure/gvec.clj @@ -385,7 +385,10 @@ (let [i (java.util.concurrent.atomic.AtomicInteger. 0)] (reify java.util.Iterator (hasNext [_] (< (.get i) cnt)) - (next [_] (.nth this (dec (.incrementAndGet i)))) + (next [_] (try + (.nth this (dec (.incrementAndGet i))) + (catch IndexOutOfBoundsException _ + (throw (java.util.NoSuchElementException.))))) (remove [_] (throw (UnsupportedOperationException.)))))) java.util.Collection @@ -428,9 +431,15 @@ (reify java.util.ListIterator (hasNext [_] (< (.get i) cnt)) (hasPrevious [_] (pos? i)) - (next [_] (.nth this (dec (.incrementAndGet i)))) + (next [_] (try + (.nth this (dec (.incrementAndGet i))) + (catch IndexOutOfBoundsException _ + (throw (java.util.NoSuchElementException.))))) (nextIndex [_] (.get i)) - (previous [_] (.nth this (.decrementAndGet i))) + (previous [_] (try + (.nth this (.decrementAndGet i)) + (catch IndexOutOfBoundsException _ + (throw (java.util.NoSuchElementException.))))) (previousIndex [_] (dec (.get i))) (add [_ e] (throw (UnsupportedOperationException.))) (remove [_] (throw (UnsupportedOperationException.))) diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java index 7034f18bf5..cec6f35fc8 100644 --- a/src/jvm/clojure/lang/APersistentVector.java +++ b/src/jvm/clojure/lang/APersistentVector.java @@ -223,7 +223,10 @@ public boolean hasNext(){ } public Object next(){ - return nth(nexti++); + if(nexti < count()) + return nth(nexti++); + else + throw new NoSuchElementException(); } public boolean hasPrevious(){ @@ -231,7 +234,10 @@ public boolean hasPrevious(){ } public Object previous(){ - return nth(--nexti); + if(nexti > 0) + return nth(--nexti); + else + throw new NoSuchElementException(); } public int nextIndex(){ @@ -265,7 +271,10 @@ public boolean hasNext(){ } public Object next(){ - return nth(i++); + if(i < end) + return nth(i++); + else + throw new NoSuchElementException(); } public void remove(){ @@ -308,7 +317,9 @@ public boolean hasNext(){ } public Object next(){ - return nth(i++); + if(i < count()) + return nth(i++); + else throw new NoSuchElementException(); } public void remove(){ diff --git a/src/jvm/clojure/lang/ArrayIter.java b/src/jvm/clojure/lang/ArrayIter.java index 0dcdc6f27e..294a145d81 100644 --- a/src/jvm/clojure/lang/ArrayIter.java +++ b/src/jvm/clojure/lang/ArrayIter.java @@ -19,7 +19,7 @@ public class ArrayIter implements Iterator { static public Iterator EMPTY_ITERATOR = new Iterator() { public boolean hasNext() { return false; } - public Object next() { return null; } + public Object next() { throw new java.util.NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException("remove() not supported"); } }; @@ -68,7 +68,7 @@ public boolean hasNext() { public Object next() { if(array != null && i < array.length) return array[i++]; - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -93,7 +93,7 @@ public boolean hasNext() { public Long next() { if(array != null && i < array.length) return Long.valueOf(array[i++]); - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -117,7 +117,7 @@ public boolean hasNext() { public Double next() { if(array != null && i < array.length) return Double.valueOf(array[i++]); - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -141,7 +141,7 @@ public boolean hasNext() { public Double next() { if(array != null && i < array.length) return array[i++]; - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -166,7 +166,7 @@ public boolean hasNext() { public Long next() { if(array != null && i < array.length) return Long.valueOf(array[i++]); - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -191,7 +191,7 @@ public boolean hasNext() { public Byte next() { if(array != null && i < array.length) return array[i++]; - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -216,7 +216,7 @@ public boolean hasNext() { public Character next() { if(array != null && i < array.length) return array[i++]; - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -241,7 +241,7 @@ public boolean hasNext() { public Long next() { if(array != null && i < array.length) return Long.valueOf(array[i++]); - return null; + throw new java.util.NoSuchElementException(); } public void remove() { @@ -266,7 +266,7 @@ public boolean hasNext() { public Boolean next() { if(array != null && i < array.length) return Boolean.valueOf(array[i++]); - return null; + throw new java.util.NoSuchElementException(); } public void remove() { diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java index e03de11939..8f32ad09f4 100644 --- a/src/jvm/clojure/lang/PersistentArrayMap.java +++ b/src/jvm/clojure/lang/PersistentArrayMap.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.Map; +import java.util.NoSuchElementException; /** * Simple implementation of persistent map on an array @@ -353,8 +354,12 @@ public boolean hasNext(){ } public Object next(){ - i += 2; - return f.invoke(array[i],array[i+1]); + try { + i += 2; + return f.invoke(array[i], array[i+1]); + } catch(IndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } } public void remove(){ diff --git a/src/jvm/clojure/lang/PersistentTreeMap.java b/src/jvm/clojure/lang/PersistentTreeMap.java index 4309977518..adbbb9734b 100644 --- a/src/jvm/clojure/lang/PersistentTreeMap.java +++ b/src/jvm/clojure/lang/PersistentTreeMap.java @@ -855,9 +855,13 @@ public boolean hasNext(){ } public Object next(){ - Node t = (Node) stack.pop(); - push(asc ? t.right() : t.left()); - return t; + try { + Node t = (Node) stack.pop(); + push(asc ? t.right() : t.left()); + return t; + } catch(EmptyStackException e) { + throw new NoSuchElementException(); + } } public void remove(){ diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java index 3aa26dd10f..3f6de59ab8 100644 --- a/src/jvm/clojure/lang/PersistentVector.java +++ b/src/jvm/clojure/lang/PersistentVector.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicReference; public class PersistentVector extends APersistentVector implements IObj, IEditableCollection, IReduce, IKVReduce{ @@ -294,12 +295,16 @@ public boolean hasNext(){ } public Object next(){ - if(i-base == 32){ - array = arrayFor(i); - base += 32; + if(i < end) { + if(i-base == 32){ + array = arrayFor(i); + base += 32; } - return array[i++ & 0x01f]; + return array[i++ & 0x01f]; + } else { + throw new NoSuchElementException(); } + } public void remove(){ throw new UnsupportedOperationException(); From 15932d875c7b433935a5bd8f4ce2cef3849203c9 Mon Sep 17 00:00:00 2001 From: Andrew Rosa Date: Sat, 18 Jul 2015 18:19:11 -0300 Subject: [PATCH 127/854] CLJ-1453: Test Iterator contract from various sources Test via test.check, asserting about the correct behavior of differt Iterator implementations, comming from different data sources - both data structures or helper functions. Used a bunch of custom made generators to simplify the spec declarations. The structure adopted by specs that test multiple sources uses a tuple containing the symbol related to source (eg `vector`, `hash-map`), mostly for debugging porpouses on failure cases. This symbol is ignored on the actual test assertion. Signed-off-by: Stuart Halloway --- .../test_clojure/data_structures_interop.clj | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 test/clojure/test_clojure/data_structures_interop.clj diff --git a/test/clojure/test_clojure/data_structures_interop.clj b/test/clojure/test_clojure/data_structures_interop.clj new file mode 100644 index 0000000000..190530de05 --- /dev/null +++ b/test/clojure/test_clojure/data_structures_interop.clj @@ -0,0 +1,131 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.test-clojure.data-structures-interop + (:require [clojure.test :refer :all] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop] + [clojure.test.check.clojure-test :refer (defspec)])) + +(defn gen-range [min max] + (gen/bind (gen/choose min max) (fn [n] (gen/tuple (gen/return n) + (gen/choose n max))))) + +(defn gen-subvec [generator] + (gen/bind (gen/not-empty generator) + (fn [v] (gen/bind (gen-range 0 (dec (count v))) + (fn [[n m]] (gen/return (subvec v n m))))))) + +(defn gen-gvec + ([] + (gen/bind (gen/elements {:int gen/int + :short (gen/fmap short gen/byte) + :long (gen/fmap long gen/int) + :float (gen/fmap float gen/int) + :double (gen/fmap double gen/int) + :byte gen/byte + :char gen/char + :boolean gen/boolean}) + #(apply gen-gvec %))) + ([type generator] + (gen/bind (gen/list generator) #(gen/return (apply vector-of type %))))) + +(defn gen-hash-set [generator] + (gen/fmap (partial apply hash-set) (gen/list generator))) + +(defn gen-sorted-set [generator] + (gen/fmap (partial apply sorted-set) (gen/list generator))) + +(defn gen-array-map [key-gen val-gen] + (gen/fmap (partial into (array-map)) (gen/map key-gen val-gen))) + +(defn gen-sorted-map [key-gen val-gen] + (gen/fmap (partial into (sorted-map)) (gen/map key-gen val-gen))) + +(defn gen-array + ([] + (gen/bind (gen/elements {int-array gen/int + short-array gen/int + long-array (gen/fmap long gen/int) + float-array (gen/fmap float gen/int) + double-array (gen/fmap double gen/int) + byte-array gen/byte + char-array gen/char + boolean-array gen/boolean + object-array gen/string}) + #(apply gen-array %))) + ([array-fn generator] + (gen/fmap array-fn (gen/list generator)))) + +(defn exaust-iterator-forward [^java.util.Iterator iter] + (loop [_ iter] (when (.hasNext iter) (recur (.next iter)))) + (try (.next iter) nil (catch Throwable t t))) + +(defn exaust-iterator-backward [^java.util.ListIterator iter] + (loop [_ iter] (when (.hasPrevious iter) (recur (.previous iter)))) + (try (.previous iter) nil (catch Throwable t t))) + +(defspec iterator-throws-exception-on-exaustion 100 + (prop/for-all [[_ x] (gen/bind (gen/elements [['list (gen/list gen/int)] + ['vector (gen/vector gen/int)] + ['vector-of (gen-gvec)] + ['subvec (gen-subvec (gen/vector gen/int))] + ['hash-set (gen-hash-set gen/int)] + ['sorted-set (gen-sorted-set gen/int)] + ['hash-map (gen/hash-map gen/symbol gen/int)] + ['array-map (gen-array-map gen/symbol gen/int)] + ['sorted-map (gen-sorted-map gen/symbol gen/int)]]) + (fn [[s g]] (gen/tuple (gen/return s) g)))] + (instance? java.util.NoSuchElementException (exaust-iterator-forward (.iterator x))))) + +(defspec array-iterator-throws-exception-on-exaustion 100 + (prop/for-all [arr (gen-array)] + (let [iter (clojure.lang.ArrayIter/createFromObject arr)] + (instance? java.util.NoSuchElementException (exaust-iterator-forward iter))))) + +(defspec list-iterator-throws-exception-on-forward-exaustion 50 + (prop/for-all [[_ x] (gen/bind (gen/elements [['vector (gen/vector gen/int)] + ['subvec (gen-subvec (gen/vector gen/int))] + ['vector-of (gen-gvec)]]) + (fn [[s g]] (gen/tuple (gen/return s) g)))] + (instance? java.util.NoSuchElementException (exaust-iterator-forward (.listIterator x))))) + +(defspec list-iterator-throws-exception-on-backward-exaustion 50 + (prop/for-all [[_ x] (gen/bind (gen/elements [['vector (gen/vector gen/int)] + ['subvec (gen-subvec (gen/vector gen/int))] + ['vector-of (gen-gvec)]]) + (fn [[s g]] (gen/tuple (gen/return s) g)))] + (instance? java.util.NoSuchElementException (exaust-iterator-backward (.listIterator x))))) + +(defspec map-keyset-iterator-throws-exception-on-exaustion 50 + (prop/for-all [[_ m] (gen/bind (gen/elements [['hash-map (gen/hash-map gen/symbol gen/int) + 'array-map (gen-array-map gen/symbol gen/int) + 'sorted-map (gen-sorted-map gen/symbol gen/int)]]) + (fn [[s g]] (gen/tuple (gen/return s) g)))] + (let [iter (.iterator (.keySet m))] + (instance? java.util.NoSuchElementException (exaust-iterator-forward iter))))) + +(defspec map-values-iterator-throws-exception-on-exaustion 50 + (prop/for-all [[_ m] (gen/bind (gen/elements [['hash-map (gen/hash-map gen/symbol gen/int) + 'array-map (gen-array-map gen/symbol gen/int) + 'sorted-map (gen-sorted-map gen/symbol gen/int)]]) + (fn [[s g]] (gen/tuple (gen/return s) g)))] + (let [iter (.iterator (.values m))] + (instance? java.util.NoSuchElementException (exaust-iterator-forward iter))))) + +(defspec map-keys-iterator-throws-exception-on-exaustion 50 + (prop/for-all [m (gen-sorted-map gen/symbol gen/int)] + (instance? java.util.NoSuchElementException (exaust-iterator-forward (.keys m))))) + +(defspec map-vals-iterator-throws-exception-on-exaustion 50 + (prop/for-all [m (gen-sorted-map gen/symbol gen/int)] + (instance? java.util.NoSuchElementException (exaust-iterator-forward (.vals m))))) + +(defspec map-reverse-iterator-throws-exception-on-exaustion 50 + (prop/for-all [m (gen-sorted-map gen/symbol gen/int)] + (instance? java.util.NoSuchElementException (exaust-iterator-forward (.reverseIterator m))))) From 5da21b38470d175b3cecbf93b9cd145ca36940c1 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 19 Oct 2015 20:19:26 -0500 Subject: [PATCH 128/854] CLJ-1831 Add map-entry? predicate Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 9 +++++++++ src/clj/clojure/core_deftype.clj | 2 +- src/clj/clojure/inspector.clj | 2 +- test/clojure/test_clojure/data_structures.clj | 10 ++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index dcb14a22a4..52daccf9d6 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1425,6 +1425,15 @@ ;;map stuff +(defn map-entry? + "Return true if x is a map entry" + {:added "1.8"} + [x] + (and (instance? java.util.Map$Entry x) + (if (instance? clojure.lang.IPersistentVector x) + (= 2 (count x)) + true))) + (defn contains? "Returns true if key is present in the given collection, otherwise returns false. Note that for numerically indexed collections like diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index 03aaba8a78..5bea63b5fa 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -132,7 +132,7 @@ (defn- imap-cons [^IPersistentMap this o] (cond - (instance? java.util.Map$Entry o) + (map-entry? o) (let [^java.util.Map$Entry pair o] (.assoc this (.getKey pair) (.getValue pair))) (instance? clojure.lang.IPersistentVector o) diff --git a/src/clj/clojure/inspector.clj b/src/clj/clojure/inspector.clj index 85e79c0bfd..bd84d9a8cc 100644 --- a/src/clj/clojure/inspector.clj +++ b/src/clj/clojure/inspector.clj @@ -21,7 +21,7 @@ (defn collection-tag [x] (cond - (instance? java.util.Map$Entry x) :entry + (map-entry? x) :entry (instance? java.util.Map x) :seqable (instance? java.util.Set x) :seqable (sequential? x) :seq diff --git a/test/clojure/test_clojure/data_structures.clj b/test/clojure/test_clojure/data_structures.clj index 68c8100811..885743b5e8 100644 --- a/test/clojure/test_clojure/data_structures.clj +++ b/test/clojure/test_clojure/data_structures.clj @@ -704,6 +704,16 @@ ai3 ao3 ai4 ao4))) +(deftest test-map-entry? + (testing "map-entry? = false" + (are [entry] + (false? (map-entry? entry)) + nil 5 #{1 2} '(1 2) {:a 1} [] [0] [1 2 3])) + (testing "map-entry? = true" + (are [entry] + (true? (map-entry? entry)) + [1 2] (first (doto (java.util.HashMap.) (.put "x" 1)))))) + ;; *** Sets *** (deftest test-hash-set From 20f3094afd75c404c44bf55b1670f19768fd50cf Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Mon, 21 Sep 2015 10:41:27 -0500 Subject: [PATCH 129/854] CLJ-1809 fix local indexing error in direct linking Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 4 ++-- test/clojure/test_clojure/compilation.clj | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index d2eb21da98..6b964cffff 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -4012,9 +4012,9 @@ else if(methodArray[f.reqParms.count()] == null) { for(LocalBinding lb : (Collection)RT.keys(fm.locals)) { - lb.idx -= 1; + if(lb.isArg) + lb.idx -= 1; } - fm.maxLocal -= 1; } } } diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index e050c630a3..b5f616e7fe 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -356,3 +356,9 @@ (is (thrown? RuntimeException (eval '(defn foo [] (throw RuntimeException any-symbol))))) (is (thrown? RuntimeException (eval '(defn foo [] (throw (RuntimeException.) any-symbol))))) (is (var? (eval '(defn foo [] (throw (IllegalArgumentException.))))))) + +(deftest clj-1809 + (is (eval `(fn [y#] + (try + (finally + (let [z# y#]))))))) From 38f00f0d5cda395da3f9ed404cf8ca6e5e761558 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 27 Oct 2015 12:37:38 -0500 Subject: [PATCH 130/854] Changelog updates for 1.8.0-beta2 Signed-off-by: Stuart Halloway --- changes.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/changes.md b/changes.md index fbbc78e6d0..0d136c6b55 100644 --- a/changes.md +++ b/changes.md @@ -21,6 +21,9 @@ redefinitions seen by core code. Functions declared as dynamic will never be direct linked. +[CLJ-1809](http://dev.clojure.org/jira/browse/CLJ-1809) +[CLJ-1805](http://dev.clojure.org/jira/browse/CLJ-1805) + ### 1.2 String Functions Several new string functions were added to clojure.string to increase @@ -124,6 +127,8 @@ See: * [CLJ-1208](http://dev.clojure.org/jira/browse/CLJ-1208) Optionally require namespace on defrecord class init +* [CLJ-1823](http://dev.clojure.org/jira/browse/CLJ-1823) + Document new :load-ns option to defrecord/deftype * [CLJ-1810](http://dev.clojure.org/jira/browse/CLJ-1810) ATransientMap now marked public * [CLJ-1653](http://dev.clojure.org/jira/browse/CLJ-1653) @@ -134,6 +139,9 @@ See: Unused swapThunk method was being emitted for fns with keyword callsites * [CLJ-1329](http://dev.clojure.org/jira/browse/CLJ-1329) Removed unused local in PersistentVector.cons() +* [CLJ-1831](http://dev.clojure.org/jira/browse/CLJ-1831) + Add clojure.core/map-entry? predicate + ## 3 Bug Fixes @@ -200,6 +208,11 @@ See: * [CLJ-1226](http://dev.clojure.org/jira/browse/CLJ-1226) set! of a deftype field using field-access syntax caused ClassCastException * Records and types without fields eval to empty map +* [CLJ-1827](http://dev.clojure.org/jira/browse/CLJ-1827) + Fix reflection warning introduced in CLJ-1259 +* [CLJ-1453](http://dev.clojure.org/jira/browse/CLJ-1453) + Ensure that all Iterator implementations throw NoSuchElementException + on next() when exhausted # Changes to Clojure in Version 1.7 From e89b2f22cf7f9b687442748372b53f9c105ce8c2 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 27 Oct 2015 15:16:15 -0500 Subject: [PATCH 131/854] [maven-release-plugin] prepare release clojure-1.8.0-beta2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d01d13452..0875dcf77a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-beta2 http://clojure.org/ Clojure core environment and runtime library. From eb26aac312b9f5a754bd639193fbd225e1ff4342 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 27 Oct 2015 15:16:16 -0500 Subject: [PATCH 132/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0875dcf77a..9d01d13452 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-beta2 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 10a74b3fcd1df375b0d078429de293aabc4028d9 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 10 Nov 2015 09:41:08 -0600 Subject: [PATCH 133/854] CLJ-1845 Mark load as dynamic to allow redefinition Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 52daccf9d6..18dad4b224 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5879,7 +5879,8 @@ "Loads Clojure code from resources in classpath. A path is interpreted as classpath-relative if it begins with a slash or relative to the root directory for the current namespace otherwise." - {:added "1.0"} + {:dynamic true + :added "1.0"} [& paths] (doseq [^String path paths] (let [^String path (if (.startsWith path "/") From 5189fdae8c4a010482de4c30e7926adadaaa43ee Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 10 Nov 2015 10:31:06 -0600 Subject: [PATCH 134/854] change log updates for 1.8.0-RC1 Signed-off-by: Stuart Halloway --- changes.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/changes.md b/changes.md index 0d136c6b55..c45dfca94c 100644 --- a/changes.md +++ b/changes.md @@ -6,8 +6,6 @@ ### 1.1 Direct Linking -*This feature is a work in progress, subject to change.* - Direct linking can be enabled with -Dclojure.compiler.direct-linking=true Direct linking allows functions compiled with direct linking on to make direct @@ -15,14 +13,14 @@ static method calls to most other functions, instead of going through the var and the Fn object. This can enable further optimization by the jit, at a cost in dynamism. In particular, directly-linked calls will not see redefinitions. -As of 1.8.0-alpha3, clojure.core is compiled with direct linking by default +With this change, clojure.core itself is compiled with direct linking and therefore other namespaces cannot redefine core fns and have those redefinitions seen by core code. Functions declared as dynamic will never be direct linked. -[CLJ-1809](http://dev.clojure.org/jira/browse/CLJ-1809) -[CLJ-1805](http://dev.clojure.org/jira/browse/CLJ-1805) +* [CLJ-1809](http://dev.clojure.org/jira/browse/CLJ-1809) +* [CLJ-1805](http://dev.clojure.org/jira/browse/CLJ-1805) ### 1.2 String Functions @@ -35,7 +33,7 @@ portability and reduce the need for Java interop calls: * ends-with? - true if string ends with a substring * includes? - true if string includes a substring -[CLJ-1449](http://dev.clojure.org/jira/browse/CLJ-1449) +* [CLJ-1449](http://dev.clojure.org/jira/browse/CLJ-1449) ### 1.3 Socket Server and REPL @@ -141,7 +139,8 @@ See: Removed unused local in PersistentVector.cons() * [CLJ-1831](http://dev.clojure.org/jira/browse/CLJ-1831) Add clojure.core/map-entry? predicate - +* [CLJ-1845](http://dev.clojure.org/jira/browse/CLJ-1845) + Make clojure.core/load dynamic so it can be redef'ed even with direct linking ## 3 Bug Fixes From eb27efb6e4d2fa46eaf9d40d20594a291f8b3810 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 10 Nov 2015 11:16:51 -0600 Subject: [PATCH 135/854] [maven-release-plugin] prepare release clojure-1.8.0-RC1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d01d13452..30530d6ca9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-RC1 http://clojure.org/ Clojure core environment and runtime library. From 69b89b5ace167e4f39abcd6a3f8036d495f07130 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 10 Nov 2015 11:16:51 -0600 Subject: [PATCH 136/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 30530d6ca9..9d01d13452 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-RC1 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 9448d627e091bc010e68e05a5669c134cd715a98 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 11 Nov 2015 14:55:14 -0500 Subject: [PATCH 137/854] report error on lying type hints rather than emit unverifiable code. see CLJ-1846 --- src/jvm/clojure/lang/Compiler.java | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 6b964cffff..ef14676463 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -1424,7 +1424,7 @@ else if(primc == double.class && parameterTypes[i] == float.class) } catch(Exception e1) { - e1.printStackTrace(RT.errPrintWriter()); + throw Util.sneakyThrow(e1); } } @@ -1606,7 +1606,7 @@ public boolean hasJavaClass(){ } public Class getJavaClass() { - return tag != null ? HostExpr.tagToClass(tag) : method.getReturnType(); + return retType((tag!=null)?HostExpr.tagToClass(tag):null, (method!=null)?method.getReturnType():null); } } @@ -1819,7 +1819,7 @@ public boolean hasJavaClass(){ } public Class getJavaClass() { - return tag != null ? HostExpr.tagToClass(tag) : method.getReturnType(); + return retType((tag!=null)?HostExpr.tagToClass(tag):null, (method!=null)?method.getReturnType():null); } } @@ -3465,7 +3465,7 @@ public boolean hasJavaClass() { } public Class getJavaClass() { - return tag != null ? HostExpr.tagToClass(tag) : retClass; + return retType((tag!=null)?HostExpr.tagToClass(tag):null, retClass); } public boolean canEmitPrimitive(){ @@ -8319,6 +8319,27 @@ public void emit(ObjExpr obj, ClassVisitor cv){ } } + static boolean inty(Class c){ + return c == int.class + || c == short.class + || c == byte.class + || c == char.class; + } + + static Class retType(Class tc, Class ret){ + if(tc == null) + return ret; + if(ret == null) + return tc; + if(ret.isPrimitive() && tc.isPrimitive()){ + if((inty(ret) && inty(tc)) || (ret == tc)) + return tc; + throw new UnsupportedOperationException("Cannot coerce " + ret + + " to " + tc + ", use a cast instead"); + } + return tc; + } + static Class primClass(Symbol sym){ if(sym == null) return null; From 7faeb3a5e1fb183539a8638b72d299a3433fe990 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 12 Nov 2015 15:59:19 -0500 Subject: [PATCH 138/854] track use of this fn by nested closures - CLJ-1825 --- src/jvm/clojure/lang/Compiler.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index ef14676463..a520afabc7 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -7251,15 +7251,20 @@ static Namespace currentNS(){ static void closeOver(LocalBinding b, ObjMethod method){ if(b != null && method != null) { - if(RT.get(method.locals, b) == null) + LocalBinding lb = (LocalBinding) RT.get(method.locals, b); + if(lb == null) { method.objx.closes = (IPersistentMap) RT.assoc(method.objx.closes, b, b); closeOver(b, method.parent); } - else if(IN_CATCH_FINALLY.deref() != null) - { - method.localsUsedInCatchFinally = (PersistentHashSet) method.localsUsedInCatchFinally.cons(b.idx); - } + else { + if(lb.idx == 0) + method.usesThis = true; + if(IN_CATCH_FINALLY.deref() != null) + { + method.localsUsedInCatchFinally = (PersistentHashSet) method.localsUsedInCatchFinally.cons(b.idx); + } + } } } From bbea352ed47a9b81c6de17a165736330b5f32a06 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 13 Nov 2015 09:51:53 -0600 Subject: [PATCH 139/854] CLJ-1849 Add tests for CLJ-1846 and CLJ-1825 Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/compilation.clj | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index b5f616e7fe..c003596871 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -362,3 +362,17 @@ (try (finally (let [z# y#]))))))) + +;; See CLJ-1846 +(deftest incorrect-primitive-type-hint-throws + ;; invalid primitive type hint + (is (thrown-with-msg? Compiler$CompilerException #"Cannot coerce long to int" + (load-string "(defn returns-long ^long [] 1) (Integer/bitCount ^int (returns-long))"))) + ;; correct casting instead + (is (= 1 (load-string "(defn returns-long ^long [] 1) (Integer/bitCount (int (returns-long)))")))) + +;; See CLJ-1825 +(def zf (fn rf [x] (lazy-seq (cons x (rf x))))) +(deftest test-anon-recursive-fn + (is (= [0 0] (take 2 ((fn rf [x] (lazy-seq (cons x (rf x)))) 0)))) + (is (= [0 0] (take 2 (zf 0))))) From da4d54d71d76c9a4104d4882cbb564bb8a066ffe Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 16 Nov 2015 08:10:15 -0600 Subject: [PATCH 140/854] [maven-release-plugin] prepare release clojure-1.8.0-RC2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d01d13452..43e7360a1d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-RC2 http://clojure.org/ Clojure core environment and runtime library. From 7a1977d304622fb8344d023218cf72bdb5e1bb40 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 16 Nov 2015 08:10:15 -0600 Subject: [PATCH 141/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 43e7360a1d..9d01d13452 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-RC2 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 7be225fde436bc26caee6f8d8400d4137522f413 Mon Sep 17 00:00:00 2001 From: OHTA Shogo Date: Wed, 25 Nov 2015 00:54:05 +0900 Subject: [PATCH 142/854] CLJ-1853: Require the ns before resolving the accept-fn Signed-off-by: Stuart Halloway --- src/clj/clojure/core/server.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/core/server.clj b/src/clj/clojure/core/server.clj index 929c83eb09..32ce6ab42a 100644 --- a/src/clj/clojure/core/server.clj +++ b/src/clj/clojure/core/server.clj @@ -59,8 +59,8 @@ *session* {:server name :client client-id}] (locking lock (alter-var-root #'servers assoc-in [name :sessions client-id] {})) + (require (symbol (namespace accept))) (let [accept-fn (resolve accept)] - (require (symbol (namespace accept))) (apply accept-fn args))) (catch SocketException _disconnect) (finally From 786fc5aad515df85bbc2c37fe8d4242ae58f53f8 Mon Sep 17 00:00:00 2001 From: Ghadi Shayban Date: Tue, 24 Nov 2015 13:52:07 -0500 Subject: [PATCH 143/854] CLJ-1854 make line number available on synthetic method Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index a520afabc7..88b3760628 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -5451,6 +5451,8 @@ public void doEmitStatic(ObjExpr fn, ClassVisitor cv){ gen.storeArg(i); } } + Label callLabel = gen.mark(); + gen.visitLineNumber(line, callLabel); gen.invokeStatic(objx.objtype, ms); gen.box(returnType); From 2e0c0a9a89ede8221504edeb90e8c4ee6cce7e16 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 25 Nov 2015 14:57:02 -0600 Subject: [PATCH 144/854] CLJ-1856 Switch from reporting test failures based on stack depth to removing leading unwanted stack frames based on class name. Deprecated but did not remove old function. Signed-off-by: Stuart Halloway --- src/clj/clojure/test.clj | 21 +++++++++++++++++---- test/clojure/test_clojure/test.clj | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/test.clj b/src/clj/clojure/test.clj index 7eb5a7dd6c..10208993b5 100644 --- a/src/clj/clojure/test.clj +++ b/src/clj/clojure/test.clj @@ -233,7 +233,8 @@ "} clojure.test (:require [clojure.template :as temp] - [clojure.stacktrace :as stack])) + [clojure.stacktrace :as stack] + [clojure.string :as str])) ;; Nothing is marked "private" here, so you can rebind things to plug ;; in your own testing or reporting frameworks. @@ -331,7 +332,8 @@ :added "1.1"} report :type) -(defn- file-and-line +(defn- file-and-line + {:deprecated "1.8"} [^Throwable exception depth] (let [stacktrace (.getStackTrace exception)] (if (< depth (count stacktrace)) @@ -339,6 +341,13 @@ {:file (.getFileName s) :line (.getLineNumber s)}) {:file nil :line nil}))) +(defn- stacktrace-file-and-line + [stacktrace] + (if (seq stacktrace) + (let [^StackTraceElement s (first stacktrace)] + {:file (.getFileName s) :line (.getLineNumber s)}) + {:file nil :line nil})) + (defn do-report "Add file and line information to a test result and call report. If you are writing a custom assert-expr method, call this function @@ -348,8 +357,12 @@ (report (case (:type m) - :fail (merge (file-and-line (new java.lang.Throwable) 1) m) - :error (merge (file-and-line (:actual m) 0) m) + :fail (merge (stacktrace-file-and-line (drop-while + #(let [cl-name (.getClassName ^StackTraceElement %)] + (or (str/starts-with? cl-name "java.lang.") + (str/starts-with? cl-name "clojure.test$"))) + (.getStackTrace (Thread/currentThread)))) m) + :error (merge (stacktrace-file-and-line (.getStackTrace ^Throwable (:actual m))) m) m))) (defmethod report :default [m] diff --git a/test/clojure/test_clojure/test.clj b/test/clojure/test_clojure/test.clj index 08a4726de5..498ddff998 100644 --- a/test/clojure/test_clojure/test.clj +++ b/test/clojure/test_clojure/test.clj @@ -78,6 +78,7 @@ []) t (doto (Exception.) (.setStackTrace empty-stack))] (is (map? (#'clojure.test/file-and-line t 0)) "Should pass") + (is (map? (#'clojure.test/stacktrace-file-and-line empty-stack)) "Should pass") (is (string? (with-out-str (stack/print-stack-trace t))) "Should pass"))) (deftest #^{:has-meta true} can-add-metadata-to-tests From e1017ddcf2c45952668342f05cec2db643e03882 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 20 Nov 2015 11:04:59 -0600 Subject: [PATCH 145/854] CLJ-1851 Add new :redef meta that prevents direct invocation but does not make :dynamic Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 4 ++-- src/jvm/clojure/lang/Compiler.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 18dad4b224..b63ae17908 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5689,7 +5689,7 @@ (let [d (root-resource lib)] (subs d 0 (.lastIndexOf d "/")))) -(declare load) +(def ^:declared ^:redef load) (defn- load-one "Loads a lib given its name. If need-ns, ensures that the associated @@ -5879,7 +5879,7 @@ "Loads Clojure code from resources in classpath. A path is interpreted as classpath-relative if it begins with a slash or relative to the root directory for the current namespace otherwise." - {:dynamic true + {:redef true :added "1.0"} [& paths] (doseq [^String path paths] diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 88b3760628..1b54f9d4e2 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -92,6 +92,7 @@ public class Compiler implements Opcodes{ static final Keyword protocolKey = Keyword.intern(null, "protocol"); static final Keyword onKey = Keyword.intern(null, "on"); static Keyword dynamicKey = Keyword.intern("dynamic"); +static final Keyword redefKey = Keyword.intern(null, "redef"); static final Symbol NS = Symbol.intern("ns"); static final Symbol IN_NS = Symbol.intern("in-ns"); @@ -3781,7 +3782,7 @@ static public Expr parse(C context, ISeq form) { && context != C.EVAL) { Var v = ((VarExpr)fexpr).var; - if(!v.isDynamic()) + if(!v.isDynamic() && !RT.booleanCast(RT.get(v.meta(), redefKey, false))) { Symbol formtag = tagOf(form); Object arglists = RT.get(RT.meta(v), arglistsKey); From 38b30ab7b3876deb513a5279b704e9799f0ce3fb Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 1 Dec 2015 15:07:50 -0600 Subject: [PATCH 146/854] Changelog updates for 1.8.0-RC3 Signed-off-by: Stuart Halloway --- changes.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/changes.md b/changes.md index c45dfca94c..6f8b6cc25f 100644 --- a/changes.md +++ b/changes.md @@ -17,10 +17,12 @@ With this change, clojure.core itself is compiled with direct linking and therefore other namespaces cannot redefine core fns and have those redefinitions seen by core code. -Functions declared as dynamic will never be direct linked. +A new metadata key ^:redef is provided. A function declared with this key can be redefined and will never be direct linked. Also, functions declared as ^:dynamic will never be direct linked. * [CLJ-1809](http://dev.clojure.org/jira/browse/CLJ-1809) * [CLJ-1805](http://dev.clojure.org/jira/browse/CLJ-1805) +* [CLJ-1854](http://dev.clojure.org/jira/browse/CLJ-1854) +* [CLJ-1856](http://dev.clojure.org/jira/browse/CLJ-1856) ### 1.2 String Functions @@ -78,6 +80,7 @@ hello See: * [CLJ-1671](http://dev.clojure.org/jira/browse/CLJ-1671) +* [CLJ-1853](http://dev.clojure.org/jira/browse/CLJ-1853) * [Socket REPL design page](http://dev.clojure.org/display/design/Socket+Server+REPL) ## 2 Enhancements From a1eb4062e3ade2478d4b57aff06c29604d9c7a46 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 20 Nov 2015 10:57:23 -0600 Subject: [PATCH 147/854] CLJ-1845: test for direct linking Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/compilation.clj | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index c003596871..c7d9f90807 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -376,3 +376,16 @@ (deftest test-anon-recursive-fn (is (= [0 0] (take 2 ((fn rf [x] (lazy-seq (cons x (rf x)))) 0)))) (is (= [0 0] (take 2 (zf 0))))) + + +;; See CLJ-1845 +(deftest direct-linking-for-load + (let [called? (atom nil) + logger (fn [& args] + (reset! called? true) + nil)] + (with-redefs [load logger] + ;; doesn't actually load clojure.repl, but should + ;; eventually call `load` and reset called?. + (require 'clojure.repl :reload)) + (is @called?))) From 3960a09404720ba46b9e1bc4442d116322e80c80 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 2 Dec 2015 09:43:13 -0600 Subject: [PATCH 148/854] [maven-release-plugin] prepare release clojure-1.8.0-RC3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d01d13452..733793387b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-RC3 http://clojure.org/ Clojure core environment and runtime library. From 5cfe5111ccb5afec4f9c73b46bba29ecab6a5899 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 2 Dec 2015 09:43:14 -0600 Subject: [PATCH 149/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 733793387b..9d01d13452 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-RC3 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From bfe14aec1c223abc3253358bac34b503284467d9 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 10 Dec 2015 12:05:16 -0500 Subject: [PATCH 150/854] elide unused constants --- clojure.iml | 7 +- src/jvm/clojure/lang/Compiler.java | 187 ++++++++++++++++------------- 2 files changed, 102 insertions(+), 92 deletions(-) diff --git a/clojure.iml b/clojure.iml index e4d101a477..c1c8a6a84a 100644 --- a/clojure.iml +++ b/clojure.iml @@ -1,10 +1,5 @@ - - - - - @@ -12,8 +7,8 @@ - + diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 1b54f9d4e2..60668258ff 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -4125,6 +4125,8 @@ static public class ObjExpr implements Expr{ int line; int column; PersistentVector constants; + IPersistentSet usedConstants = PersistentHashSet.EMPTY; + int constantsID; int altCtorDrops = 0; @@ -4277,23 +4279,7 @@ void compile(String superName, String[] interfaceNames, boolean oneTimeUse) thro cv.visitSource(source, smap); } addAnnotation(cv, classMeta); - //static fields for constants - for(int i = 0; i < constants.count(); i++) - { - cv.visitField(ACC_PUBLIC + ACC_FINAL - + ACC_STATIC, constantName(i), constantType(i).getDescriptor(), - null, null); - } - //static fields for lookup sites - for(int i = 0; i < keywordCallsites.count(); i++) - { - cv.visitField(ACC_FINAL - + ACC_STATIC, siteNameStatic(i), KEYWORD_LOOKUPSITE_TYPE.getDescriptor(), - null, null); - cv.visitField(ACC_STATIC, thunkNameStatic(i), ILOOKUP_THUNK_TYPE.getDescriptor(), - null, null); - } // for(int i=0;i ()"), - null, - null, - cv); - clinitgen.visitCode(); - clinitgen.visitLineNumber(line, clinitgen.mark()); - - if(constants.count() > 0) - { - emitConstants(clinitgen); - } - - if(keywordCallsites.count() > 0) - emitKeywordCallsites(clinitgen); - - /* - for(int i=0;i ()"), + null, + null, + cv); + clinitgen.visitCode(); + clinitgen.visitLineNumber(line, clinitgen.mark()); + + if(constants.count() > 0) + { + emitConstants(clinitgen); + } + + if(keywordCallsites.count() > 0) + emitKeywordCallsites(clinitgen); + + /* + for(int i=0;i Date: Sun, 13 Dec 2015 10:02:50 -0500 Subject: [PATCH 151/854] wait on 2-tuples as MapEntries until reassessment of tuples under direct linking --- src/clj/clojure/core.clj | 5 +-- src/clj/clojure/core_deftype.clj | 4 +-- src/clj/clojure/core_proxy.clj | 4 +-- src/clj/clojure/gvec.clj | 2 +- src/jvm/clojure/lang/APersistentMap.java | 2 +- src/jvm/clojure/lang/APersistentVector.java | 33 ++----------------- src/jvm/clojure/lang/MapEntry.java | 4 +++ src/jvm/clojure/lang/PersistentArrayMap.java | 8 ++--- src/jvm/clojure/lang/PersistentHashMap.java | 10 +++--- src/jvm/clojure/lang/PersistentStructMap.java | 4 +-- src/jvm/clojure/lang/RT.java | 2 +- src/jvm/clojure/lang/RecordIterator.java | 2 +- test/clojure/test_clojure/data_structures.clj | 2 +- 13 files changed, 27 insertions(+), 55 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index b63ae17908..a715928d99 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1429,10 +1429,7 @@ "Return true if x is a map entry" {:added "1.8"} [x] - (and (instance? java.util.Map$Entry x) - (if (instance? clojure.lang.IPersistentVector x) - (= 2 (count x)) - true))) + (instance? java.util.Map$Entry x)) (defn contains? "Returns true if key is present in the given collection, otherwise diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index 5bea63b5fa..723ed24c41 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -212,8 +212,8 @@ `(containsKey [this# k#] (not (identical? this# (.valAt this# k# this#)))) `(entryAt [this# k#] (let [v# (.valAt this# k# this#)] (when-not (identical? this# v#) - (clojure.lang.Tuple/create k# v#)))) - `(seq [this#] (seq (concat [~@(map #(list `clojure.lang.Tuple/create (keyword %) %) base-fields)] + (clojure.lang.MapEntry/create k# v#)))) + `(seq [this#] (seq (concat [~@(map #(list `clojure.lang.MapEntry/create (keyword %) %) base-fields)] ~'__extmap))) `(iterator [~gs] (clojure.lang.RecordIterator. ~gs [~@(map keyword base-fields)] (RT/iter ~'__extmap))) diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj index fc46aea9d5..813c8bbe83 100644 --- a/src/clj/clojure/core_proxy.clj +++ b/src/clj/clojure/core_proxy.clj @@ -400,7 +400,7 @@ [] (iterator [] (.iterator ^Iterable pmap)) (containsKey [k] (contains? pmap k)) - (entryAt [k] (when (contains? pmap k) (clojure.lang.Tuple/create k (v k)))) + (entryAt [k] (when (contains? pmap k) (clojure.lang.MapEntry/create k (v k)))) (valAt ([k] (when (contains? pmap k) (v k))) ([k default] (if (contains? pmap k) (v k) default))) (cons [m] (conj (snapshot) m)) @@ -410,7 +410,7 @@ (seq [] ((fn thisfn [plseq] (lazy-seq (when-let [pseq (seq plseq)] - (cons (clojure.lang.Tuple/create (first pseq) (v (first pseq))) + (cons (clojure.lang.MapEntry/create (first pseq) (v (first pseq))) (thisfn (rest pseq)))))) (keys pmap)))))) diff --git a/src/clj/clojure/gvec.clj b/src/clj/clojure/gvec.clj index 7b6e47c4e1..dbfe9282e7 100644 --- a/src/clj/clojure/gvec.clj +++ b/src/clj/clojure/gvec.clj @@ -267,7 +267,7 @@ (< (int k) cnt))) (entryAt [this k] (if (.containsKey this k) - (clojure.lang.Tuple/create k (.nth this (int k))) + (clojure.lang.MapEntry/create k (.nth this (int k))) nil)) clojure.lang.ILookup diff --git a/src/jvm/clojure/lang/APersistentMap.java b/src/jvm/clojure/lang/APersistentMap.java index f1d7b3b03e..6e01ddc2b5 100644 --- a/src/jvm/clojure/lang/APersistentMap.java +++ b/src/jvm/clojure/lang/APersistentMap.java @@ -266,7 +266,7 @@ public void remove() { static final IFn MAKE_ENTRY = new AFn() { public Object invoke(Object key, Object val) { - return Tuple.create(key, val); + return MapEntry.create(key, val); } }; diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java index cec6f35fc8..b55ead8451 100644 --- a/src/jvm/clojure/lang/APersistentVector.java +++ b/src/jvm/clojure/lang/APersistentVector.java @@ -16,7 +16,7 @@ import java.util.*; public abstract class APersistentVector extends AFn implements IPersistentVector, Iterable, - List, IMapEntry, + List, RandomAccess, Comparable, Serializable, IHashEq { int _hash = -1; @@ -126,21 +126,6 @@ else if(obj instanceof List) } -@Override -public Object getKey(){ - return key(); -} - -@Override -public Object getValue(){ - return val(); -} - -@Override -public Object setValue(Object value){ - throw new UnsupportedOperationException(); -} - public boolean equals(Object obj){ if(obj == this) return true; @@ -346,7 +331,7 @@ public IMapEntry entryAt(Object key){ { int i = ((Number) key).intValue(); if(i >= 0 && i < count()) - return (IMapEntry) Tuple.create(key, nth(i)); + return (IMapEntry) MapEntry.create(key, nth(i)); } return null; } @@ -456,20 +441,6 @@ else if(count() > v.count()) return 0; } -@Override -public Object key(){ - if(count() == 2) - return nth(0); - throw new UnsupportedOperationException(); -} - -@Override -public Object val(){ - if(count() == 2) - return nth(1); - throw new UnsupportedOperationException(); -} - static class Seq extends ASeq implements IndexedSeq, IReduce{ //todo - something more efficient final IPersistentVector v; diff --git a/src/jvm/clojure/lang/MapEntry.java b/src/jvm/clojure/lang/MapEntry.java index 8537a3edae..310199c1db 100644 --- a/src/jvm/clojure/lang/MapEntry.java +++ b/src/jvm/clojure/lang/MapEntry.java @@ -16,6 +16,10 @@ public class MapEntry extends AMapEntry{ final Object _key; final Object _val; +static public MapEntry create(Object key, Object val){ + return new MapEntry(key, val); +} + public MapEntry(Object key, Object val){ this._key = key; this._val = val; diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java index 8f32ad09f4..6700cf46e0 100644 --- a/src/jvm/clojure/lang/PersistentArrayMap.java +++ b/src/jvm/clojure/lang/PersistentArrayMap.java @@ -39,8 +39,8 @@ static public IPersistentMap create(Map other){ ITransientMap ret = EMPTY.asTransient(); for(Object o : other.entrySet()) { - Map.Entry e = (Entry) o; - ret = ret.assoc(e.getKey(), e.getValue()); + Map.Entry e = (Entry) o; + ret = ret.assoc(e.getKey(), e.getValue()); } return ret.persistent(); } @@ -164,7 +164,7 @@ public boolean containsKey(Object key){ public IMapEntry entryAt(Object key){ int i = indexOf(key); if(i >= 0) - return (IMapEntry) Tuple.create(array[i],array[i+1]); + return (IMapEntry) MapEntry.create(array[i],array[i+1]); return null; } @@ -314,7 +314,7 @@ public Seq(IPersistentMap meta, Object[] array, int i){ } public Object first(){ - return Tuple.create(array[i],array[i+1]); + return MapEntry.create(array[i],array[i+1]); } public ISeq next(){ diff --git a/src/jvm/clojure/lang/PersistentHashMap.java b/src/jvm/clojure/lang/PersistentHashMap.java index 5c4ad37c7f..2fe1e8e915 100644 --- a/src/jvm/clojure/lang/PersistentHashMap.java +++ b/src/jvm/clojure/lang/PersistentHashMap.java @@ -128,7 +128,7 @@ public boolean containsKey(Object key){ public IMapEntry entryAt(Object key){ if(key == null) - return hasNull ? (IMapEntry) Tuple.create(null, nullValue) : null; + return hasNull ? (IMapEntry) MapEntry.create(null, nullValue) : null; return (root != null) ? root.find(0, hash(key), key) : null; } @@ -264,7 +264,7 @@ public int count(){ public ISeq seq(){ ISeq s = root != null ? root.nodeSeq() : null; - return hasNull ? new Cons(Tuple.create(null, nullValue), s) : s; + return hasNull ? new Cons(MapEntry.create(null, nullValue), s) : s; } public IPersistentCollection empty(){ @@ -766,7 +766,7 @@ public IMapEntry find(int shift, int hash, Object key){ if(keyOrNull == null) return ((INode) valOrNode).find(shift + 5, hash, key); if(Util.equiv(key, keyOrNull)) - return (IMapEntry) Tuple.create(keyOrNull, valOrNode); + return (IMapEntry) MapEntry.create(keyOrNull, valOrNode); return null; } @@ -967,7 +967,7 @@ public IMapEntry find(int shift, int hash, Object key){ if(idx < 0) return null; if(Util.equiv(key, array[idx])) - return (IMapEntry) Tuple.create(array[idx], array[idx+1]); + return (IMapEntry) MapEntry.create(array[idx], array[idx+1]); return null; } @@ -1343,7 +1343,7 @@ public Obj withMeta(IPersistentMap meta) { public Object first() { if(s != null) return s.first(); - return Tuple.create(array[i], array[i+1]); + return MapEntry.create(array[i], array[i+1]); } public ISeq next() { diff --git a/src/jvm/clojure/lang/PersistentStructMap.java b/src/jvm/clojure/lang/PersistentStructMap.java index 5ca3f2ce48..734428c06c 100644 --- a/src/jvm/clojure/lang/PersistentStructMap.java +++ b/src/jvm/clojure/lang/PersistentStructMap.java @@ -132,7 +132,7 @@ public IMapEntry entryAt(Object key){ Map.Entry e = def.keyslots.entryAt(key); if(e != null) { - return (IMapEntry) Tuple.create(e.getKey(), vals[(Integer) e.getValue()]); + return (IMapEntry) MapEntry.create(e.getKey(), vals[(Integer) e.getValue()]); } return ext.entryAt(key); } @@ -245,7 +245,7 @@ public Obj withMeta(IPersistentMap meta){ } public Object first(){ - return Tuple.create(keys.first(), vals[i]); + return MapEntry.create(keys.first(), vals[i]); } public ISeq next(){ diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 07e82d5f11..324616e643 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -822,7 +822,7 @@ else if(coll instanceof Associative) else { Map m = (Map) coll; if(m.containsKey(key)) - return Tuple.create(key, m.get(key)); + return MapEntry.create(key, m.get(key)); return null; } } diff --git a/src/jvm/clojure/lang/RecordIterator.java b/src/jvm/clojure/lang/RecordIterator.java index 2c7a6c1578..0c9ba36033 100644 --- a/src/jvm/clojure/lang/RecordIterator.java +++ b/src/jvm/clojure/lang/RecordIterator.java @@ -41,7 +41,7 @@ public Object next() { if (i < basecnt) { Object k = basefields.nth(i); i++; - return Tuple.create(k, rec.valAt(k)); + return MapEntry.create(k, rec.valAt(k)); } else { return extmap.next(); } diff --git a/test/clojure/test_clojure/data_structures.clj b/test/clojure/test_clojure/data_structures.clj index 885743b5e8..9151ceb9db 100644 --- a/test/clojure/test_clojure/data_structures.clj +++ b/test/clojure/test_clojure/data_structures.clj @@ -712,7 +712,7 @@ (testing "map-entry? = true" (are [entry] (true? (map-entry? entry)) - [1 2] (first (doto (java.util.HashMap.) (.put "x" 1)))))) + (first (doto (java.util.HashMap.) (.put "x" 1)))))) ;; *** Sets *** From 9020b38577c69c348eb49c88c1e60d8ac7e95d1c Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 14 Dec 2015 10:04:18 -0600 Subject: [PATCH 152/854] CLJ-1161 Omit version.properties file in -sources jar for release builds Signed-off-by: Stuart Halloway --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d01d13452..1b41c16264 100644 --- a/pom.xml +++ b/pom.xml @@ -187,7 +187,7 @@ 2.1.2 - sources-jar + attach-sources package jar From 865e478a808365f87a027843f5da2a3b82bfd659 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 16 Dec 2015 15:21:24 -0600 Subject: [PATCH 153/854] [maven-release-plugin] prepare release clojure-1.8.0-RC4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b41c16264..70bddbf6aa 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-RC4 http://clojure.org/ Clojure core environment and runtime library. From 77819a58b66dd7dc26bb5798d2e14d3b7b2ea481 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 16 Dec 2015 15:21:24 -0600 Subject: [PATCH 154/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 70bddbf6aa..1b41c16264 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-RC4 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 010864f8ed828f8d261807b7345f1a539c5b20df Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 17 Dec 2015 13:06:33 -0600 Subject: [PATCH 155/854] CLJ-1868 Avoid compiler NPE when checking class return type Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 7 ++++--- test/clojure/test_clojure/compilation.clj | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index a715928d99..32d04d8e96 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -244,9 +244,10 @@ (if (instance? clojure.lang.Symbol tag) (if (clojure.lang.Util/equiv (.indexOf (.getName tag) ".") -1) (if (clojure.lang.Util/equals nil (clojure.lang.Compiler$HostExpr/maybeSpecialTag tag)) - (let [t (.getName (clojure.lang.Compiler$HostExpr/maybeClass tag false)) - resolvedtag (clojure.lang.Symbol/intern t)] - (with-meta argvec (assoc m :tag resolvedtag))) + (let [c (clojure.lang.Compiler$HostExpr/maybeClass tag false)] + (if c + (with-meta argvec (assoc m :tag (clojure.lang.Symbol/intern (.getName c)))) + argvec)) argvec) argvec) argvec)))] diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index c7d9f90807..a730b89c0a 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -145,6 +145,12 @@ (is (= 'java.lang.String (-> arglists first meta :tag))) (is (= 'java.lang.Integer (-> arglists second meta :tag))))) +(deftest CLJ-1232-return-type-not-imported + (is (thrown-with-msg? Compiler$CompilerException #"Unable to resolve classname: Closeable" + (eval '(defn a ^Closeable [])))) + (is (thrown-with-msg? Compiler$CompilerException #"Unable to resolve classname: Closeable" + (eval '(defn a (^Closeable [])))))) + (defn ^String hinting-conflict ^Integer []) (deftest calls-use-arg-vector-hint From 2932d562e3a809f88130e5434c32aa18e34a67eb Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 8 Jan 2016 12:38:25 -0600 Subject: [PATCH 156/854] CLJ-1829 Use ReentrantLock instead of locking in socket server Signed-off-by: Stuart Halloway --- src/clj/clojure/core/server.clj | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/clj/clojure/core/server.clj b/src/clj/clojure/core/server.clj index 32ce6ab42a..8bcf3627c0 100644 --- a/src/clj/clojure/core/server.clj +++ b/src/clj/clojure/core/server.clj @@ -12,16 +12,26 @@ (:require [clojure.string :as str] [clojure.edn :as edn] [clojure.main :as m]) - (:import [java.net InetAddress Socket ServerSocket SocketException])) + (:import [java.net InetAddress Socket ServerSocket SocketException] + [java.util.concurrent.locks ReentrantLock])) (set! *warn-on-reflection* true) (def ^:dynamic *session* nil) ;; lock protects servers -(defonce ^:private lock (Object.)) +(defonce ^:private lock (ReentrantLock.)) (defonce ^:private servers {}) +(defmacro ^:private with-lock + [lock-expr & body] + `(let [lockee# ~(with-meta lock-expr {:tag 'java.util.concurrent.locks.ReentrantLock})] + (.lock lockee#) + (try + ~@body + (finally + (.unlock lockee#))))) + (defmacro ^:private thread [^String name daemon & body] `(doto (Thread. (fn [] ~@body) ~name) @@ -57,14 +67,14 @@ *out* out *err* err *session* {:server name :client client-id}] - (locking lock + (with-lock lock (alter-var-root #'servers assoc-in [name :sessions client-id] {})) (require (symbol (namespace accept))) (let [accept-fn (resolve accept)] (apply accept-fn args))) (catch SocketException _disconnect) (finally - (locking lock + (with-lock lock (alter-var-root #'servers update-in [name :sessions] dissoc client-id)) (.close conn)))) @@ -87,7 +97,7 @@ client-daemon true}} opts address (InetAddress/getByName address) ;; nil returns loopback socket (ServerSocket. port 0 address)] - (locking lock + (with-lock lock (alter-var-root #'servers assoc name {:name name, :socket socket, :sessions {}})) (thread (str "Clojure Server " name) server-daemon @@ -105,7 +115,7 @@ (catch SocketException _disconnect)) (recur (inc client-counter)))) (finally - (locking lock + (with-lock lock (alter-var-root #'servers dissoc name))))) socket)) @@ -116,7 +126,7 @@ ([] (stop-server (:server *session*))) ([name] - (locking lock + (with-lock lock (let [server-socket ^ServerSocket (get-in servers [name :socket])] (when server-socket (alter-var-root #'servers dissoc name) @@ -126,7 +136,7 @@ (defn stop-servers "Stop all servers ignores all errors, and returns nil." [] - (locking lock + (with-lock lock (doseq [name (keys servers)] (future (stop-server name))))) From ca7dfa33863744cd30f1e32f3e50012de8656f41 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 8 Jan 2016 13:21:45 -0600 Subject: [PATCH 157/854] Changelog updates for 1.8.0-RC5 Signed-off-by: Stuart Halloway --- changes.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/changes.md b/changes.md index 6f8b6cc25f..61b2eaf8ce 100644 --- a/changes.md +++ b/changes.md @@ -6,7 +6,7 @@ ### 1.1 Direct Linking -Direct linking can be enabled with -Dclojure.compiler.direct-linking=true +Direct linking can be enabled with `-Dclojure.compiler.direct-linking=true` Direct linking allows functions compiled with direct linking on to make direct static method calls to most other functions, instead of going through the var @@ -17,7 +17,9 @@ With this change, clojure.core itself is compiled with direct linking and therefore other namespaces cannot redefine core fns and have those redefinitions seen by core code. -A new metadata key ^:redef is provided. A function declared with this key can be redefined and will never be direct linked. Also, functions declared as ^:dynamic will never be direct linked. +A new metadata key ^:redef is provided. A function declared with this key can +be redefined and will never be direct linked. Also, functions declared as +^:dynamic will never be direct linked. * [CLJ-1809](http://dev.clojure.org/jira/browse/CLJ-1809) * [CLJ-1805](http://dev.clojure.org/jira/browse/CLJ-1805) @@ -45,7 +47,7 @@ REPL, but it also has many other potential uses for dynamically adding server capability to existing programs without code changes. A socket server will be started for each JVM system property like -"clojure.server.". The value for this property is an edn map +`clojure.server.`. The value for this property is an edn map representing the configuration of the socket server with the following properties: * address - host or address, defaults to loopback @@ -79,9 +81,11 @@ hello ``` See: + * [CLJ-1671](http://dev.clojure.org/jira/browse/CLJ-1671) * [CLJ-1853](http://dev.clojure.org/jira/browse/CLJ-1853) * [Socket REPL design page](http://dev.clojure.org/display/design/Socket+Server+REPL) +* [CLJ-1829](http://dev.clojure.org/jira/browse/CLJ-1829) ## 2 Enhancements @@ -215,6 +219,8 @@ See: * [CLJ-1453](http://dev.clojure.org/jira/browse/CLJ-1453) Ensure that all Iterator implementations throw NoSuchElementException on next() when exhausted +* [CLJ-1868](http://dev.clojure.org/jira/browse/CLJ-1868) + Avoid compiler NPE when checking class return type # Changes to Clojure in Version 1.7 From d88d3e76b90aeec77665fba7eb7aedb6238a2476 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 11 Jan 2016 08:33:18 -0600 Subject: [PATCH 158/854] [maven-release-plugin] prepare release clojure-1.8.0-RC5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b41c16264..eb5681aa88 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0-RC5 http://clojure.org/ Clojure core environment and runtime library. From 3e3907600f801158e957822cb75f4e0c069d9300 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 11 Jan 2016 08:33:18 -0600 Subject: [PATCH 159/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eb5681aa88..1b41c16264 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-RC5 + 1.8.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 4ff462372c29ff2bc22b4d39962ad526f7e2c73d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 19 Jan 2016 13:29:20 -0600 Subject: [PATCH 160/854] [maven-release-plugin] prepare release clojure-1.8.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b41c16264..1a5e65bb7f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0-master-SNAPSHOT + 1.8.0 http://clojure.org/ Clojure core environment and runtime library. From d5708425995e8c83157ad49007ec2f8f43d8eac8 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 19 Jan 2016 13:29:20 -0600 Subject: [PATCH 161/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1a5e65bb7f..5140e9dad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.8.0 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 3394bbe616c6202618983ec87185a3ed25d0f557 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 23 May 2016 11:38:00 -0400 Subject: [PATCH 162/854] added spec --- build.xml | 1 + pom.xml | 2 +- src/clj/clojure/spec.clj | 1415 ++++++++++++++++++++++++++++ src/clj/clojure/spec/gen.clj | 175 ++++ src/clj/clojure/spec/test.clj | 147 +++ test/clojure/test_clojure/spec.clj | 180 ++++ 6 files changed, 1919 insertions(+), 1 deletion(-) create mode 100644 src/clj/clojure/spec.clj create mode 100644 src/clj/clojure/spec/gen.clj create mode 100644 src/clj/clojure/spec/test.clj create mode 100644 test/clojure/test_clojure/spec.clj diff --git a/build.xml b/build.xml index cae30b21fb..f9764b361d 100644 --- a/build.xml +++ b/build.xml @@ -81,6 +81,7 @@ + diff --git a/pom.xml b/pom.xml index 5140e9dad0..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ org.clojure test.check - 0.5.9 + 0.9.0 test diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj new file mode 100644 index 0000000000..386a765d0d --- /dev/null +++ b/src/clj/clojure/spec.clj @@ -0,0 +1,1415 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.spec + (:refer-clojure :exclude [+ * and or cat def keys]) + (:require [clojure.walk :as walk] + [clojure.spec.gen :as gen] + [clojure.string :as str])) + +(alias 'c 'clojure.core) + +(set! *warn-on-reflection* true) + +(def ^:dynamic *recursion-limit* + "A soft limit on how many times a branching spec (or/alt/*/opt-keys) + can be recursed through during generation. After this a + non-recursive branch will be chosen." + 10) + +(def ^:dynamic *fspec-iterations* + "The number of times an anonymous fn specified by fspec will be (generatively) tested during conform" + 21) + +(def ^:dynamic *coll-check-limit* + "The number of items validated in a collection spec'ed with 'coll'" + 100) + +(def ^:private ^:dynamic *instrument-enabled* + "if false, instrumented fns call straight through" + true) + +(defprotocol Spec + (conform* [spec x]) + (explain* [spec path via x]) + (gen* [spec overrides path rmap]) + (with-gen* [spec gfn]) + (describe* [spec])) + +(defonce ^:private registry-ref (atom {})) + +(defn- named? [x] (instance? clojure.lang.Named x)) + +(defn- with-name [spec name] + (with-meta spec (assoc (meta spec) ::name name))) + +(defn- spec-name [spec] + (when (instance? clojure.lang.IObj spec) + (-> (meta spec) ::name))) + +(defn- reg-resolve + "returns the spec/regex at end of alias chain starting with k, nil if not found, k if k not Named" + [k] + (if (named? k) + (let [reg @registry-ref] + (loop [spec k] + (if (named? spec) + (recur (get reg spec)) + (when spec + (with-name spec k))))) + k)) + +(defn spec? + "returns x if x is a spec object, else logical false" + [x] + (c/and (extends? Spec (class x)) x)) + +(defn regex? + "returns x if x is a (clojure.spec) regex op, else logical false" + [x] + (c/and (::op x) x)) + +(declare spec-impl) +(declare regex-spec-impl) + +(defn- maybe-spec + "spec-or-k must be a spec, regex or resolvable kw/sym, else returns nil." + [spec-or-k] + (let [s (c/or (spec? spec-or-k) + (regex? spec-or-k) + (c/and (named? spec-or-k) (reg-resolve spec-or-k)) + nil)] + (if (regex? s) + (with-name (regex-spec-impl s nil) (spec-name s)) + s))) + +(defn- the-spec + "spec-or-k must be a spec, regex or kw/sym, else returns nil. Throws if unresolvable kw/sym" + [spec-or-k] + (c/or (maybe-spec spec-or-k) + (when (named? spec-or-k) + (throw (Exception. (str "Unable to resolve spec: " spec-or-k)))))) + +(defn- specize [s] + (c/or (the-spec s) (spec-impl ::unknown s nil nil))) + +(defn conform + "Given a spec and a value, returns :clojure.spec/invalid if value does not match spec, + else the (possibly destructured) value." + [spec x] + (conform* (specize spec) x)) + +(defn form + "returns the spec as data" + [spec] + ;;TODO - incorporate gens + (describe* (specize spec))) + +(defn abbrev [form] + (cond + (seq? form) + (walk/postwalk (fn [form] + (cond + (c/and (symbol? form) (namespace form)) + (-> form name symbol) + + (c/and (seq? form) (= 'fn (first form)) (= '[%] (second form))) + (last form) + + :else form)) + form) + + (c/and (symbol? form) (namespace form)) + (-> form name symbol) + + :else form)) + +(defn describe + "returns an abbreviated description of the spec as data" + [spec] + (abbrev (form spec))) + +(defn with-gen + "Takes a spec and a no-arg, generator-returning fn and returns a version of that spec that uses that generator" + [spec gen-fn] + (with-gen* (specize spec) gen-fn)) + +(defn explain-data* [spec path via x] + (when-let [probs (explain* (specize spec) path via x)] + {::problems probs})) + +(defn explain-data + "Given a spec and a value x which ought to conform, returns nil if x + conforms, else a map with at least the key ::problems whose value is + a path->problem-map, where problem-map has at least :pred and :val + keys describing the predicate and the value that failed at that + path." + [spec x] + (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) x)) + +(defn- explain-out + "prints an explanation to *out*." + [ed] + (if ed + (do + ;;(prn {:ed ed}) + (doseq [[path {:keys [pred val reason via] :as prob}] (::problems ed)] + (when-not (empty? path) + (print "At:" path "")) + (print "val: ") + (pr val) + (print " fails") + (when-let [specname (last via)] + (print " spec:" specname)) + (print " predicate: ") + (pr pred) + (when reason (print ", " reason)) + (doseq [[k v] prob] + (when-not (#{:pred :val :reason :via} k) + (print "\n\t" k " ") + (pr v))) + (newline)) + (doseq [[k v] ed] + (when-not (#{::problems} k) + (print k " ") + (pr v) + (newline)))) + (println "Success!"))) + +(defn explain + "Given a spec and a value that fails to conform, prints an explanation to *out*." + [spec x] + (explain-out (explain-data spec x))) + +(declare valid?) + +(defn- gensub + [spec overrides path rmap form] + ;;(prn {:spec spec :over overrides :path path :form form}) + (if-let [spec (specize spec)] + (if-let [g (c/or (get overrides path) (gen* spec overrides path rmap))] + (gen/such-that #(valid? spec %) g 100) + (throw (Exception. (str "Unable to construct gen at: " path " for: " (abbrev form))))) + (throw (Exception. (str "Unable to construct gen at: " path ", " (abbrev form) " can not be made a spec"))))) + +(defn gen + "Given a spec, returns the generator for it, or throws if none can + be constructed. Optionally an overrides map can be provided which + should map paths (vectors of keywords) to generators. These will be + used instead of the generators at those paths. Note that parent + generator (in the spec or overrides map) will supersede those of any + subtrees. A generator for a regex op must always return a + sequential collection (i.e. a generator for s/? should return either + an empty sequence/vector or a sequence/vector with one item in it)" + ([spec] (gen spec nil)) + ([spec overrides] (gensub spec overrides [] nil spec))) + +(defn- ->sym + "Returns a symbol from a symbol or var" + [x] + (if (var? x) + (let [^clojure.lang.Var v x] + (symbol (str (.name (.ns v))) + (str (.sym v)))) + x)) + +(defn- unfn [expr] + (if (c/and (seq? expr) + (symbol? (first expr)) + (= "fn*" (name (first expr)))) + (let [[[s] & form] (rest expr)] + (conj (walk/postwalk-replace {s '%} form) '[%] 'fn)) + expr)) + +(defn- res [form] + (cond + (keyword? form) form + (symbol? form) (c/or (-> form resolve ->sym) form) + (sequential? form) (walk/postwalk #(if (symbol? %) (res %) %) (unfn form)) + :else form)) + +(defn ^:skip-wiki def-impl + "Do not call this directly, use 'def'" + [k form spec] + (assert (c/and (named? k) (namespace k)) "k must be namespaced keyword/symbol") + (let [spec (if (c/or (spec? spec) (regex? spec) (get @registry-ref spec)) + spec + (spec-impl form spec nil nil))] + (swap! registry-ref assoc k spec) + k)) + +(defmacro def + "Given a namespace-qualified keyword or symbol k, and a spec, spec-name, predicate or regex-op + makes an entry in the registry mapping k to the spec" + [k spec-form] + `(def-impl ~k '~(res spec-form) ~spec-form)) + +(defn registry + "returns the registry map" + [] + @registry-ref) + +(declare map-spec) + +(defmacro spec + "Takes a single predicate form, e.g. can be the name of a predicate, + like even?, or a fn literal like #(< % 42). Note that it is not + generally necessary to wrap predicates in spec when using the rest + of the spec macros, only to attach a unique generator + + Can also be passed the result of one of the regex ops - + cat, alt, *, +, ?, in which case it will return a regex-conforming + spec, useful when nesting an independent regex. + --- + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator. + + Returns a spec." + [form & {:keys [gen]}] + `(spec-impl '~(res form) ~form ~gen nil)) + +(defmacro multi-spec + "Takes the name of a spec/predicate-returning multimethod and a + tag-restoring keyword or fn (retag). Returns a spec that when + conforming or explaining data will pass it to the multimethod to get + an appropriate spec. You can e.g. use multi-spec to dynamically and + extensibly associate specs with 'tagged' data (i.e. data where one + of the fields indicates the shape of the rest of the structure). + + The multimethod must use :clojure.spec/invalid as its default value + and should return nil from that dispatch value: + + (defmulti mspec :tag :default :clojure.spec/invalid) + (defmethod mspec :clojure.spec/invalid [_] nil) + + The methods should ignore their argument and return a predicate/spec: + (defmethod mspec :int [_] (s/keys :req-un [::i])) + + retag is used during generation to retag generated values with + matching tags. retag can either be a keyword, at which key the + dispatch-tag will be assoc'ed, or a fn of generated value and + dispatch-tag that should return an appropriately retagged value. + + Note that because the tags themselves comprise an open set, + the tag keys cannot be :req in the specs. +" + [mm retag] + `(multi-spec-impl '~(res mm) (var ~mm) ~retag)) + +(defmacro keys + "Creates and returns a map validating spec. :req and :opt are both + vectors of namespaced-qualified keywords. The validator will ensure + the :req keys are present. The :opt keys serve as documentation and + may be used by the generator. + + The :req key vector supports 'and' and 'or' for key groups: + + (s/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z]) + + There are also -un versions of :req and :opt. These allow + you to connect unqualified keys to specs. In each case, fully + qualfied keywords are passed, which name the specs, but unqualified + keys (with the same name component) are expected and checked at + conform-time, and generated during gen: + + (s/keys :req-un [:my.ns/x :my.ns/y]) + + The above says keys :x and :y are required, and will be validated + and generated by specs (if they exist) named :my.ns/x :my.ns/y + respectively. + + In addition, the values of *all* namespace-qualified keys will be validated + (and possibly destructured) by any registered specs. Note: there is + no support for inline value specification, by design. + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator." + [& {:keys [req req-un opt opt-un gen]}] + (let [unk #(-> % name keyword) + req-keys (filterv keyword? (flatten req)) + req-un-specs (filterv keyword? (flatten req-un)) + _ (assert (every? #(c/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un)) + "all keys must be namespace-qualified keywords") + req-specs (into req-keys req-un-specs) + req-keys (into req-keys (map unk req-un-specs)) + opt-keys (into (vec opt) (map unk opt-un)) + opt-specs (into (vec opt) opt-un) + parse-req (fn [rk f] + (map (fn [x] + (if (keyword? x) + `#(contains? % ~(f x)) + (let [gx (gensym)] + `(fn* [~gx] + ~(walk/postwalk + (fn [y] (if (keyword? y) `(contains? ~gx ~(f y)) y)) + x))))) + rk)) + pred-exprs [`map?] + pred-exprs (into pred-exprs (parse-req req identity)) + pred-exprs (into pred-exprs (parse-req req-un unk)) + pred-forms (walk/postwalk res pred-exprs)] + ;; `(map-spec-impl ~req-keys '~req ~opt '~pred-forms ~pred-exprs ~gen) + `(map-spec-impl {:req '~req :opt '~opt :req-un '~req-un :opt-un '~opt-un + :req-keys '~req-keys :req-specs '~req-specs + :opt-keys '~opt-keys :opt-specs '~opt-specs + :pred-forms '~pred-forms + :pred-exprs ~pred-exprs + :gfn ~gen}))) + +(defmacro or + "Takes key+pred pairs, e.g. + + (s/or :even even? :small #(< % 42)) + + Returns a destructuring spec that + returns a vector containing the key of the first matching pred and the + corresponding value." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv res pred-forms)] + (assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords") + `(or-spec-impl ~keys '~pf ~pred-forms nil))) + +(defmacro and + "Takes predicate/spec-forms, e.g. + + (s/and even? #(< % 42)) + + Returns a spec that returns the conformed value. Successive + conformed values propagate through rest of predicates." + [& pred-forms] + `(and-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) + +(defmacro * + "Returns a regex op that matches zero or more values matching + pred. Produces a vector of matches iff there is at least one match" + [pred-form] + `(rep-impl '~(res pred-form) ~pred-form)) + +(defmacro + + "Returns a regex op that matches one or more values matching + pred. Produces a vector of matches" + [pred-form] + `(rep+impl '~(res pred-form) ~pred-form)) + +(defmacro ? + "Returns a regex op that matches zero or one value matching + pred. Produces a single value (not a collection) if matched." + [pred-form] + `(maybe-impl ~pred-form '~pred-form)) + +(defmacro alt + "Takes key+pred pairs, e.g. + + (s/alt :even even? :small #(< % 42)) + + Returns a regex op that returns a vector containing the key of the + first matching pred and the corresponding value." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv res pred-forms)] + (assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords") + `(alt-impl ~keys ~pred-forms '~pf))) + +(defmacro cat + "Takes key+pred pairs, e.g. + + (s/cat :e even? :o odd?) + + Returns a regex op that matches (all) values in sequence, returning a map + containing the keys of each pred and the corresponding value." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv res pred-forms)] + ;;(prn key-pred-forms) + (assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords") + `(cat-impl ~keys ~pred-forms '~pf))) + +(defmacro & + "takes a regex op re, and predicates. Returns a regex-op that consumes + input as per re but subjects the resulting value to the + conjunction of the predicates, and any conforming they might perform." + [re & preds] + (let [pv (vec preds)] + `(amp-impl ~re ~pv '~pv))) + +(defmacro conformer + "takes a predicate function with the semantics of conform i.e. it should return either a + (possibly converted) value or :clojure.spec/invalid, and returns a + spec that uses it as a predicate/conformer" + [f] + `(spec-impl '~f ~f nil true)) + +(defmacro fspec + "takes :args :ret and (optional) :fn kwargs whose values are preds + and returns a spec whose conform/explain take a fn and validates it + using generative testing. The conformed value is always the fn itself. + + Optionally takes :gen generator-fn, which must be a fn of no args + that returns a test.check generator." + [& {:keys [args ret fn gen]}] + `(fspec-impl ~args '~(res args) ~ret '~(res ret) ~fn '~(res fn) ~gen)) + +(defmacro tuple + "takes one or more preds and returns a spec for a tuple, a vector + where each element conforms to the corresponding pred. Each element + will be referred to in paths using its ordinal." + [& preds] + (assert (not (empty? preds))) + `(tuple-impl '~(mapv res preds) ~(vec preds))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- ns-qualify + "Qualify symbol s by resolving it or using the current *ns*." + [s] + (if-let [resolved (resolve s)] + (->sym resolved) + (if (namespace s) + s + (symbol (str (.name *ns*)) (str s))))) + +(defn- fn-spec-sym + [sym role] + (symbol (str (ns-qualify sym) "$" (name role)))) + +(def ^:private fn-spec-roles [:args :ret :fn]) + +(defn- expect + "Returns nil if v conforms to spec, else throws ex-info with explain-data." + [spec v] + ) + +(defn- fn-specs? + "Fn-specs must include at least :args or :ret specs." + [m] + (c/or (:args m) (:ret m))) + +(defn fn-specs + "Returns :args/:ret/:fn map of specs for var or symbol v." + [v] + (let [s (->sym v) + reg (registry)] + (reduce + (fn [m role] + (assoc m role (get reg (fn-spec-sym s role)))) + {} + fn-spec-roles))) + +(defmacro with-instrument-disabled + "Disables instrument's checking of calls, within a scope." + [& body] + `(binding [*instrument-enabled* nil] + ~@body)) + +(defn- spec-checking-fn + [v f] + (let [conform! (fn [v role spec data args] + (let [conformed (conform spec data)] + (if (= ::invalid conformed) + (let [ed (assoc (explain-data* spec [role] [] data) + ::args args)] + (throw (ex-info + (str "Call to " v " did not conform to spec:\n" (with-out-str (explain-out ed))) + ed))) + conformed)))] + (c/fn + [& args] + (if *instrument-enabled* + (with-instrument-disabled + (let [specs (fn-specs v)] + (let [cargs (when (:args specs) (conform! v :args (:args specs) args args)) + ret (binding [*instrument-enabled* true] + (.applyTo ^clojure.lang.IFn f args)) + cret (when (:ret specs) (conform! v :ret (:ret specs) ret args))] + (when (c/and (:args specs) (:ret specs) (:fn specs)) + (conform! v :fn (:fn specs) {:args cargs :ret cret} args)) + ret))) + (.applyTo ^clojure.lang.IFn f args))))) + +(defn- macroexpand-check + [v args] + (let [specs (fn-specs v)] + (when-let [arg-spec (:args specs)] + (when (= ::invalid (conform arg-spec args)) + (let [ed (assoc (explain-data* arg-spec [:args] + (if-let [name (spec-name arg-spec)] [name] []) args) + ::args args)] + (throw (IllegalArgumentException. + (str + "Call to " (->sym v) " did not conform to spec:\n" + (with-out-str (explain-out ed)))))))))) + +(defmacro fdef + "Takes a symbol naming a function, and one or more of the following: + + :args A regex spec for the function arguments as they were a list to be + passed to apply - in this way, a single spec can handle functions with + multiple arities + :ret A spec for the function's return value + :fn A spec of the relationship between args and ret - the + value passed is {:args conformed-args :ret conformed-ret} and is + expected to contain predicates that relate those values + + Qualifies fn-sym with resolve, or using *ns* if no resolution found. + Registers specs in the global registry, where they can be retrieved + by calling fn-specs. + + Once registered, function specs are included in doc, checked by + instrument, tested by the runner clojure.spec.test/run-tests, and (if + a macro) used to explain errors during macroexpansion. + + Note that :fn specs require the presence of :args and :ret specs to + conform values, and so :fn specs will be ignored if :args or :ret + are missing. + + Returns the qualified fn-sym. + + For example, to register function specs for the symbol function: + + (s/fdef clojure.core/symbol + :args (s/alt :separate (s/cat :ns string? :n string?) + :str string? + :sym symbol?) + :ret symbol?)" + [fn-sym & {:keys [args ret fn] :as m}] + (let [qn (ns-qualify fn-sym)] + `(do ~@(reduce + (c/fn [defns role] + (if (contains? m role) + (let [s (fn-spec-sym qn (name role))] + (conj defns `(clojure.spec/def '~s ~(get m role)))) + defns)) + [] [:args :ret :fn]) + '~qn))) + +(defn- no-fn-specs + [v specs] + (ex-info (str "Fn at " v " is not spec'ed.") + {:var v :specs specs})) + +(def ^:private instrumented-vars + "Map for instrumented vars to :raw/:wrapped fns" + (atom {})) + +(defn- ->var + [s-or-v] + (if (var? s-or-v) + s-or-v + (let [v (c/and (symbol? s-or-v) (resolve s-or-v))] + (if (var? v) + v + (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) + +(defn instrument + "Instruments the var at v, a var or symbol, to check specs +registered with fdef. Wraps the fn at v to check :args/:ret/:fn +specs, if they exist, throwing an ex-info with explain-data if a +check fails. Idempotent." + [v] + (let [v (->var v) + specs (fn-specs v)] + (if (fn-specs? specs) + (locking instrumented-vars + (let [{:keys [raw wrapped]} (get @instrumented-vars v) + current @v] + (when-not (= wrapped current) + (let [checked (spec-checking-fn v current)] + (alter-var-root v (constantly checked)) + (swap! instrumented-vars assoc v {:raw current :wrapped checked})))) + v) + (throw (no-fn-specs v specs))))) + +(defn unstrument + "Undoes instrument on the var at v, a var or symbol. Idempotent." + [v] + (let [v (->var v)] + (locking instrumented-vars + (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] + (let [current @v] + (when (= wrapped current) + (alter-var-root v (constantly raw)))) + (swap! instrumented-vars dissoc v)) + v))) + +(defn speced-vars + "Returns the set of vars whose namespace is in ns-syms AND +whose vars have been speced with fdef. If no ns-syms are +specified, return speced vars from all namespaces." + [& ns-syms] + (let [ns-match? (if (seq ns-syms) + (set (map str ns-syms)) + (constantly true))] + (reduce-kv + (fn [s k _] + (if (c/and (symbol? k) + (re-find #"\$(args|ret)$" (name k)) + (ns-match? (namespace k))) + (if-let [v (resolve (symbol (str/replace (str k) #"\$(args|ret)$" "")))] + (conj s v) + s) + s)) + #{} + (registry)))) + +(defn instrument-ns + "Call instrument for all speced-vars in namespaces named +by ns-syms. Idempotent." + [& ns-syms] + (when (seq ns-syms) + (locking instrumented-vars + (doseq [v (apply speced-vars ns-syms)] + (instrument v))))) + +(defn unstrument-ns + "Call unstrument for all speced-vars in namespaces named +by ns-syms. Idempotent." + [& ns-syms] + (when (seq ns-syms) + (locking instrumented-vars + (doseq [v (apply speced-vars ns-syms)] + (unstrument v))))) + +(defn instrument-all + "Call instrument for all speced-vars. Idempotent." + [] + (locking instrumented-vars + (doseq [v (speced-vars)] + (instrument v)))) + +(defn unstrument-all + "Call unstrument for all speced-vars. Idempotent" + [] + (locking instrumented-vars + (doseq [v (speced-vars)] + (unstrument v)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- recur-limit? [rmap id path k] + (c/and (> (get rmap id) *recursion-limit*) + (contains? (set path) k))) + +(defn- inck [m k] + (assoc m k (inc (c/or (get m k) 0)))) + +(defn- dt + ([pred x form] (dt pred x form nil)) + ([pred x form cpred?] + (if pred + (if-let [spec (the-spec pred)] + (conform spec x) + (if (ifn? pred) + (if cpred? + (pred x) + (if (pred x) x ::invalid)) + (throw (Exception. (str (pr-str form) " is not a fn, expected predicate fn"))))) + x))) + +(defn valid? + "Helper function that returns true when x is valid for spec." + ([spec x] + (not= ::invalid (dt spec x ::unknown))) + ([spec x form] + (not= ::invalid (dt spec x form)))) + +(defn- explain-1 [form pred path via v] + (let [pred (maybe-spec pred)] + (if (spec? pred) + (explain* pred path (if-let [name (spec-name pred)] (conj via name) via) v) + {path {:pred (abbrev form) :val v :via via}}))) + +(defn ^:skip-wiki map-spec-impl + "Do not call this directly, use 'spec' with a map argument" + [{:keys [req-un opt-un pred-exprs opt-keys req-specs req req-keys opt-specs pred-forms opt gfn] + :as argm}] + (let [keys-pred (apply every-pred pred-exprs) + k->s (zipmap (concat req-keys opt-keys) (concat req-specs opt-specs)) + keys->specs #(c/or (k->s %) %) + id (java.util.UUID/randomUUID)] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ m] + (if (keys-pred m) + (let [reg (registry)] + (loop [ret m, [k & ks :as keys] (c/keys m)] + (if keys + (if (contains? reg (keys->specs k)) + (let [v (get m k) + cv (conform (keys->specs k) v)] + (if (= cv ::invalid) + ::invalid + (recur (if (identical? cv v) ret (assoc ret k cv)) + ks))) + (recur ret ks)) + ret))) + ::invalid)) + (explain* [_ path via x] + (if-not (map? x) + {path {:pred 'map? :val x :via via}} + (let [reg (registry)] + (apply merge + (when-let [probs (->> (map (fn [pred form] (when-not (pred x) (abbrev form))) + pred-exprs pred-forms) + (keep identity) + seq)] + {path {:pred (vec probs) :val x :via via}}) + (map (fn [[k v]] + (when-not (c/or (not (contains? reg (keys->specs k))) + (valid? (keys->specs k) v k)) + (explain-1 (keys->specs k) (keys->specs k) (conj path k) via v))) + (seq x)))))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [rmap (inck rmap id) + gen (fn [k s] (gensub s overrides (conj path k) rmap k)) + ogen (fn [k s] + (when-not (recur-limit? rmap id path k) + [k (gensub s overrides (conj path k) rmap k)])) + req-gens (map gen req-keys req-specs) + opt-gens (remove nil? (map ogen opt-keys opt-specs))] + (when (every? identity (concat req-gens opt-gens)) + (let [reqs (zipmap req-keys req-gens) + opts (into {} opt-gens)] + (gen/bind (gen/choose 0 (count opts)) + #(let [args (concat (seq reqs) (when (seq opts) (shuffle (seq opts))))] + (->> args + (take (c/+ % (count reqs))) + (apply concat) + (apply gen/hash-map))))))))) + (with-gen* [_ gfn] (map-spec-impl (assoc argm :gfn gfn))) + (describe* [_] (cons `keys + (cond-> [] + req (conj :req req) + opt (conj :opt opt) + req-un (conj :req-un req-un) + opt-un (conj :opt-un opt-un))))))) + + + + +(defn ^:skip-wiki spec-impl + "Do not call this directly, use 'spec'" + [form pred gfn cpred?] + (cond + (spec? pred) (cond-> pred gfn (with-gen gfn)) + (regex? pred) (regex-spec-impl pred gfn) + (named? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) + :else + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] (dt pred x form cpred?)) + (explain* [_ path via x] + (when (= ::invalid (dt pred x form cpred?)) + {path {:pred (abbrev form) :val x :via via}})) + (gen* [_ _ _ _] (if gfn + (gfn) + (gen/gen-for-pred pred))) + (with-gen* [_ gfn] (spec-impl form pred gfn cpred?)) + (describe* [_] form)))) + +(defn ^:skip-wiki multi-spec-impl + "Do not call this directly, use 'multi-spec'" + ([form mmvar retag] (multi-spec-impl form mmvar retag nil)) + ([form mmvar retag gfn] + (assert (when-let [dm (-> (methods @mmvar) ::invalid)] + (nil? (dm nil))) + (str "Multimethod :" form " does not contain nil-returning default method for :clojure.spec/invalid" )) + (let [predx #(@mmvar %) + tag (if (keyword? retag) + #(assoc %1 retag %2) + retag)] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] (if-let [pred (predx x)] + (dt pred x form) + ::invalid)) + (explain* [_ path via x] + (if-let [pred (predx x)] + (explain-1 form pred path via x) + {path {:pred form :val x :reason "no method" :via via}})) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [[k f]] + (let [p (f nil)] + (gen/fmap + #(tag % k) + (gensub p overrides path rmap (list 'method form k))))) + gs (->> (methods @mmvar) + (remove (fn [[k]] (= k ::invalid))) + (map gen) + (remove nil?))] + (when (every? identity gs) + (gen/one-of gs))))) + (with-gen* [_ gfn] (multi-spec-impl form mmvar retag gfn)) + (describe* [_] `(multi-spec ~form)))))) + +(defn ^:skip-wiki tuple-impl + "Do not call this directly, use 'tuple'" + ([forms preds] (tuple-impl forms preds nil)) + ([forms preds gfn] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] + (if-not (c/and (vector? x) + (= (count x) (count preds))) + ::invalid + (loop [ret x, i 0] + (if (= i (count x)) + ret + (let [v (x i) + cv (dt (preds i) v (forms i))] + (if (= ::invalid cv) + ::invalid + (recur (if (identical? cv v) ret (assoc ret i cv)) + (inc i)))))))) + (explain* [_ path via x] + (cond + (not (vector? x)) + {path {:pred 'vector? :val x :via via}} + + (not= (count x) (count preds)) + {path {:pred `(= (count ~'%) ~(count preds)) :val x :via via}} + + :else + (apply merge + (map (fn [i form pred] + (let [v (x i)] + (when-not (valid? pred v) + (explain-1 form pred (conj path i) via v)))) + (range (count preds)) forms preds)))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [i p f] + (gensub p overrides (conj path i) rmap f)) + gs (map gen (range (count preds)) preds forms)] + (when (every? identity gs) + (apply gen/tuple gs))))) + (with-gen* [_ gfn] (tuple-impl forms preds gfn)) + (describe* [_] `(tuple ~@forms))))) + + +(defn ^:skip-wiki or-spec-impl + "Do not call this directly, use 'or'" + [keys forms preds gfn] + (let [id (java.util.UUID/randomUUID) + cform (fn [x] + (loop [i 0] + (if (< i (count preds)) + (let [pred (preds i)] + (let [ret (dt pred x (nth forms i))] + (if (= ::invalid ret) + (recur (inc i)) + [(keys i) ret]))) + ::invalid)))] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] (cform x)) + (explain* [this path via x] + (when-not (valid? this x) + (apply merge + (map (fn [k form pred] + (when-not (valid? pred x) + (explain-1 form pred (conj path k) via x))) + keys forms preds)))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [k p f] + (let [rmap (inck rmap id)] + (when-not (recur-limit? rmap id path k) + (gensub p overrides (conj path k) rmap f)))) + gs (remove nil? (map gen keys preds forms))] + (when-not (empty? gs) + (gen/one-of gs))))) + (with-gen* [_ gfn] (or-spec-impl keys forms preds gfn)) + (describe* [_] `(or ~@(mapcat vector keys forms)))))) + +(defn- and-preds [x preds forms] + (loop [ret x + [pred & preds] preds + [form & forms] forms] + (if pred + (let [nret (dt pred ret form)] + (if (= ::invalid nret) + ::invalid + ;;propagate conformed values + (recur nret preds forms))) + ret))) + +(defn- explain-pred-list + [forms preds path via x] + (loop [ret x + [form & forms] forms + [pred & preds] preds] + (when pred + (let [nret (dt pred ret form)] + (if (not= ::invalid nret) + (recur nret forms preds) + (explain-1 form pred path via ret)))))) + +(defn ^:skip-wiki and-spec-impl + "Do not call this directly, use 'and'" + [forms preds gfn] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] (and-preds x preds forms)) + (explain* [_ path via x] (explain-pred-list forms preds path via x)) + (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) + (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) + (describe* [_] `(and ~@forms)))) + +;;;;;;;;;;;;;;;;;;;;;;; regex ;;;;;;;;;;;;;;;;;;; +;;See: +;; http://matt.might.net/articles/implementation-of-regular-expression-matching-in-scheme-with-derivatives/ +;; http://www.ccs.neu.edu/home/turon/re-deriv.pdf + +;;ctors +(defn- accept [x] {::op ::accept :ret x}) + +(defn- accept? [{:keys [::op]}] + (= ::accept op)) + +(defn- pcat* [{[p1 & pr :as ps] :ps, [k1 & kr :as ks] :ks, [f1 & fr :as forms] :forms, ret :ret}] + (when (every? identity ps) + (if (accept? p1) + (let [rp (:ret p1) + ret (conj ret (if ks {k1 rp} rp))] + (if pr + (pcat* {:ps pr :ks kr :forms fr :ret ret}) + (accept ret))) + {::op ::pcat, :ps ps, :ret ret, :ks ks, :forms forms}))) + +(defn- pcat [& ps] (pcat* {:ps ps :ret []})) + +(defn ^:skip-wiki cat-impl + "Do not call this directly, use 'cat'" + [ks ps forms] + (pcat* {:ks ks, :ps ps, :forms forms, :ret {}})) + +(defn- rep* [p1 p2 ret splice form] + (when p1 + (let [r {::op ::rep, :p2 p2, :splice splice, :forms form :id (java.util.UUID/randomUUID)}] + (if (accept? p1) + (assoc r :p1 p2 :ret (conj ret (:ret p1))) + (assoc r :p1 p1, :ret ret))))) + +(defn ^:skip-wiki rep-impl + "Do not call this directly, use '*'" + [form p] (rep* p p [] false form)) + +(defn ^:skip-wiki rep+impl + "Do not call this directly, use '+'" + [form p] + (pcat* {:ps [p (rep* p p [] true form)] :forms `[~form (* ~form)] :ret []})) + +(defn ^:skip-wiki amp-impl + "Do not call this directly, use '&'" + [re preds pred-forms] + {::op ::amp :p1 re :ps preds :forms pred-forms}) + +(defn- filter-alt [ps ks forms f] + (if (c/or ks forms) + (let [pks (->> (map vector ps + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil))) + (filter #(-> % first f)))] + [(seq (map first pks)) (when ks (seq (map second pks))) (when forms (seq (map #(nth % 2) pks)))]) + [(seq (filter f ps)) ks forms])) + +(defn- alt* [ps ks forms] + (let [[[p1 & pr :as ps] [k1 :as ks] forms] (filter-alt ps ks forms identity)] + (when ps + (let [ret {::op ::alt, :ps ps, :ks ks :forms forms}] + (if (nil? pr) + (if k1 + (if (accept? p1) + (accept [k1 (:ret p1)]) + ret) + p1) + ret))))) + +(defn- alts [& ps] (alt* ps nil nil)) +(defn- alt2 [p1 p2] (if (c/and p1 p2) (alts p1 p2) (c/or p1 p2))) + +(defn ^:skip-wiki alt-impl + "Do not call this directly, use 'alt'" + [ks ps forms] (assoc (alt* ps ks forms) :id (java.util.UUID/randomUUID))) + +(defn ^:skip-wiki maybe-impl + "Do not call this directly, use '?'" + [p form] (alt* [p (accept ::nil)] nil [form ::nil])) + +(defn- noret? [p1 pret] + (c/or (= pret ::nil) + (c/and (#{::rep ::pcat} (::op (reg-resolve p1))) ;;hrm, shouldn't know these + (empty? pret)) + nil)) + +(declare preturn) + +(defn- accept-nil? [p] + (let [{:keys [::op ps p1 p2 forms] :as p} (reg-resolve p)] + (case op + ::accept true + nil nil + ::amp (c/and (accept-nil? p1) + (c/or (noret? p1 (preturn p1)) + (let [ret (-> (preturn p1) (and-preds ps (next forms)))] + (if (= ret ::invalid) + nil + ret)))) + ::rep (c/or (identical? p1 p2) (accept-nil? p1)) + ::pcat (every? accept-nil? ps) + ::alt (c/some accept-nil? ps)))) + +(declare add-ret) + +(defn- preturn [p] + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms] :as p} (reg-resolve p)] + (case op + ::accept ret + nil nil + ::amp (let [pret (preturn p1)] + (if (noret? p1 pret) + ::nil + (and-preds pret ps forms))) + ::rep (add-ret p1 ret k) + ::pcat (add-ret p0 ret k) + ::alt (let [[[p0] [k0]] (filter-alt ps ks forms accept-nil?) + r (if (nil? p0) ::nil (preturn p0))] + (if k0 [k0 r] r))))) + +(defn- add-ret [p r k] + (let [{:keys [::op ps splice] :as p} (reg-resolve p) + prop #(let [ret (preturn p)] + (if (empty? ret) r ((if splice into conj) r (if k {k ret} ret))))] + (case op + nil r + (::alt ::accept ::amp) + (let [ret (preturn p)] + ;;(prn {:ret ret}) + (if (= ret ::nil) r (conj r (if k {k ret} ret)))) + + (::rep ::pcat) (prop)))) + +(defn- deriv + [p x] + (let [{[p0 & pr :as ps] :ps, [k0 & kr :as ks] :ks, :keys [::op p1 p2 ret splice forms] :as p} (reg-resolve p)] + (when p + (case op + ::accept nil + nil (let [ret (dt p x p)] + (when-not (= ::invalid ret) (accept ret))) + ::amp (when-let [p1 (deriv p1 x)] + (amp-impl p1 ps forms)) + ::pcat (alt2 (pcat* {:ps (cons (deriv p0 x) pr), :ks ks, :forms forms, :ret ret}) + (when (accept-nil? p0) (deriv (pcat* {:ps pr, :ks kr, :forms (next forms), :ret (add-ret p0 ret k0)}) x))) + ::alt (alt* (map #(deriv % x) ps) ks forms) + ::rep (rep* (deriv p1 x) p2 ret splice forms))))) + +(defn- op-describe [p] + ;;(prn {:op op :ks ks :forms forms}) + (let [{:keys [::op ps ks forms splice p1] :as p} (reg-resolve p)] + (when p + (case op + ::accept nil + nil p + ::amp (list* 'clojure.spec/& (op-describe p1) forms) + ::pcat (cons `cat (mapcat vector ks forms)) + ::alt (cons `alt (mapcat vector ks forms)) + ::rep (list (if splice `+ `*) forms))))) + +(defn- op-explain [form p path via input] + ;;(prn {:form form :p p :path path :input input}) + (let [[x :as input] input + via (if-let [name (spec-name p)] (conj via name) via) + {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve p) + insufficient (fn [path form] + {path {:reason "Insufficient input" + :pred (abbrev form) + :val () + :via via}})] + (when p + (case op + nil (if (empty? input) + (insufficient path form) + (explain-1 form p path via x)) + ::amp (if (empty? input) + (if (accept-nil? p1) + (explain-pred-list forms ps path via (preturn p1)) + (insufficient path (op-describe p1))) + (if-let [p1 (deriv p1 x)] + (explain-pred-list forms ps path via (preturn p1)) + (op-explain (op-describe p1) p1 path via input))) + ::pcat (let [[pred k form] (->> (map vector + ps + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil))) + (remove (fn [[p]] + (accept-nil? p))) + first) + path (if k (conj path k) path) + form (c/or form (op-describe pred))] + (if (c/and (empty? input) (not pred)) + (insufficient path form) + (op-explain form pred path via input))) + ::alt (if (empty? input) + (insufficient path (op-describe p)) + (apply merge + (map (fn [k form pred] + (op-explain (c/or form (op-describe pred)) + pred + (if k (conj path k) path) + via + input)) + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil)) + ps))) + ::rep (op-explain (if (identical? p1 p2) + forms + (op-describe p1)) + p1 path via input))))) + +(defn- re-gen [p overrides path rmap f] + ;;(prn {:op op :ks ks :forms forms}) + (let [{:keys [::op ps ks p1 p2 forms splice ret id] :as p} (reg-resolve p) + rmap (if id (inck rmap id) rmap) + ggens (fn [ps ks forms] + (let [gen (fn [p k f] + ;;(prn {:k k :path path :rmap rmap :op op :id id}) + (when-not (c/and rmap id k (recur-limit? rmap id path k)) + (re-gen p overrides (if k (conj path k) path) rmap (c/or f p))))] + (map gen ps (c/or (seq ks) (repeat nil)) (c/or (seq forms) (repeat nil)))))] + (c/or (when-let [g (get overrides path)] + (case op + (:accept nil) (gen/fmap vector g) + g)) + (when p + (case op + ::accept (if (= ret ::nil) + (gen/return []) + (gen/return [ret])) + nil (when-let [g (gensub p overrides path rmap f)] + (gen/fmap vector g)) + ::amp (re-gen p1 overrides path rmap (op-describe p1)) + ::pcat (let [gens (ggens ps ks forms)] + (when (every? identity gens) + (apply gen/cat gens))) + ::alt (let [gens (remove nil? (ggens ps ks forms))] + (when-not (empty? gens) + (gen/one-of gens))) + ::rep (if (recur-limit? rmap id [id] id) + (gen/return []) + (when-let [g (re-gen p2 overrides path rmap forms)] + (gen/fmap #(apply concat %) + (gen/vector g))))))))) + +(defn- re-conform [p [x & xs :as data]] + ;;(prn {:p p :x x :xs xs}) + (if (empty? data) + (if (accept-nil? p) + (let [ret (preturn p)] + (if (= ret ::nil) + nil + ret)) + ::invalid) + (if-let [dp (deriv p x)] + (recur dp xs) + ::invalid))) + +(defn- re-explain [path via re input] + (loop [p re [x & xs :as data] input] + ;;(prn {:p p :x x :xs xs}) (prn) + (if (empty? data) + (if (accept-nil? p) + nil ;;success + (op-explain (op-describe p) p path via nil)) + (if-let [dp (deriv p x)] + (recur dp xs) + (if (accept? p) + {path {:reason "Extra input" + :pred (abbrev (op-describe re)) + :val data + :via via}} + (c/or (op-explain (op-describe p) p path via (seq data)) + {path {:reason "Extra input" + :pred (abbrev (op-describe p)) + :val data + :via via}})))))) + +(defn ^:skip-wiki regex-spec-impl + "Do not call this directly, use 'spec' with a regex op argument" + [re gfn] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] + (if (c/or (nil? x) (coll? x)) + (re-conform re (seq x)) + ::invalid)) + (explain* [_ path via x] + (if (c/or (nil? x) (coll? x)) + (re-explain path via re (seq x)) + {path {:pred (abbrev (op-describe re)) :val x :via via}})) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (re-gen re overrides path rmap (op-describe re)))) + (describe* [_] (op-describe re)))) + +;;;;;;;;;;;;;;;;; HOFs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- call-valid? + [f specs args] + (let [cargs (conform (:args specs) args)] + (when-not (= cargs ::invalid) + (let [ret (apply f args) + cret (conform (:ret specs) ret)] + (c/and (not= cret ::invalid) + (if (:fn specs) + (valid? (:fn specs) {:args cargs :ret cret}) + true)))))) + +(defn- validate-fn + "returns f if valid, else smallest" + [f specs iters] + (let [g (gen (:args specs)) + prop (gen/for-all* [g] #(call-valid? f specs %))] + (let [ret (gen/quick-check iters prop)] + (if-let [[smallest] (-> ret :shrunk :smallest)] + smallest + f)))) + +(defn ^:skip-wiki fspec-impl + "Do not call this directly, use 'fspec'" + [argspec aform retspec rform fnspec fform gfn] + (assert (c/and argspec retspec)) + (let [specs {:args argspec :ret retspec :fn fnspec}] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ f] (if (fn? f) + (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) + ::invalid)) + (explain* [_ path via f] + (if (fn? f) + (let [args (validate-fn f specs 100)] + (if (identical? f args) ;;hrm, we might not be able to reproduce + nil + (let [ret (try (apply f args) (catch Throwable t t))] + (if (instance? Throwable ret) + ;;TODO add exception data + {path {:pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via}} + + (let [cret (dt retspec ret rform)] + (if (= ::invalid cret) + (explain-1 rform retspec (conj path :ret) via ret) + (when fnspec + (let [cargs (conform argspec args)] + (explain-1 fform fnspec (conj path :fn) via {:args cargs :ret cret}))))))))) + {path {:pred 'fn? :val f :via via}})) + (gen* [_ _ _ _] (if gfn + (gfn) + (when-not fnspec + (gen/return + (fn [& args] + (assert (valid? argspec args) (with-out-str (explain argspec args))) + (gen/generate (gen retspec))))))) + (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) + (describe* [_] `(fspec ~aform ~rform ~fform))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; non-primitives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(clojure.spec/def ::any (spec (constantly true) :gen gen/any)) +(clojure.spec/def ::kvs->map (conformer #(zipmap (map ::k %) (map ::v %)))) + +(defmacro keys* + "takes the same arguments as spec/keys and returns a regex op that matches sequences of key/values, + converts them into a map, and conforms that map with a corresponding + spec/keys call: + + user=> (s/conform (s/keys :req-un [::a ::c]) {:a 1 :c 2}) + {:a 1, :c 2} + user=> (s/conform (s/keys* :req-un [::a ::c]) [:a 1 :c 2]) + {:a 1, :c 2} + + the resulting regex op can be composed into a larger regex: + + user=> (s/conform (s/cat :i1 integer? :m (s/keys* :req-un [::a ::c]) :i2 integer?) [42 :a 1 :c 2 :d 4 99]) + {:i1 42, :m {:a 1, :c 2, :d 4}, :i2 99}" + [& kspecs] + `(clojure.spec/& (* (cat ::k keyword? ::v ::any)) ::kvs->map (keys ~@kspecs))) + +(defmacro nilable + "returns a spec that accepts nil and values satisfiying pred" + [pred] + `(and (or ::nil nil? ::pred ~pred) (conformer second))) + +(defn exercise + "generates a number (default 10) of values compatible with spec and maps conform over them, + returning a sequence of [val conformed-val] tuples. Optionally takes + a generator overrides map as per gen" + ([spec] (exercise spec 10)) + ([spec n] (exercise spec n nil)) + ([spec n overrides] + (map #(vector % (conform spec %)) (gen/sample (gen spec overrides) n)))) + +(defn coll-checker + "returns a predicate function that checks *coll-check-limit* items in a collection with pred" + [pred] + (let [check? #(valid? pred %)] + (fn [coll] + (c/or (nil? coll) + (c/and + (coll? coll) + (every? check? (take *coll-check-limit* coll))))))) + +(defn coll-gen + "returns a function of no args that returns a generator of + collections of items conforming to pred, with the same shape as + init-coll" + [pred init-coll] + (let [init (empty init-coll)] + (fn [] + (gen/fmap + #(if (vector? init) % (into init %)) + (gen/vector (gen pred)))))) + +(defmacro coll-of + "Returns a spec for a collection of items satisfying pred. The generator will fill an empty init-coll." + [pred init-coll] + `(spec (coll-checker ~pred) :gen (coll-gen ~pred ~init-coll))) + +(defmacro map-of + "Returns a spec for a map whose keys satisfy kpred and vals satisfy vpred." + [kpred vpred] + `(and (coll-of (tuple ~kpred ~vpred) {}) map?)) + + diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj new file mode 100644 index 0000000000..b986790eda --- /dev/null +++ b/src/clj/clojure/spec/gen.clj @@ -0,0 +1,175 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.spec.gen + (:refer-clojure :exclude [boolean cat hash-map list map not-empty set vector + char double int keyword symbol string uuid])) + +(alias 'c 'clojure.core) + +(defn- dynaload + [s] + (let [ns (namespace s)] + (assert ns) + (require (c/symbol ns)) + (let [v (resolve s)] + (if v + @v + (throw (RuntimeException. (str "Var " s " is not on the classpath"))))))) + +(def ^:private quick-check-ref + (delay (dynaload 'clojure.test.check/quick-check))) +(defn quick-check + [& args] + (apply @quick-check-ref args)) + +(def ^:private for-all*-ref + (delay (dynaload 'clojure.test.check.properties/for-all*))) +(defn for-all* + "Dynamically loaded clojure.test.check.properties/for-all*." + [& args] + (apply @for-all*-ref args)) + +(let [g? (delay (dynaload 'clojure.test.check.generators/generator?)) + g (delay (dynaload 'clojure.test.check.generators/generate))] + (defn- generator? + [x] + (@g? x)) + (defn generate + "Generate a single value using generator." + [generator] + (@g generator))) + +(defn gen-for-name + "Dynamically loads test.check generator named s." + [s] + (let [g (dynaload s)] + (if (generator? g) + g + (throw (RuntimeException. (str "Var " s " is not a generator")))))) + +(defmacro ^:skip-wiki lazy-combinator + "Implementation macro, do not call directly." + [s] + (let [fqn (c/symbol "clojure.test.check.generators" (name s)) + doc (str "Lazy loaded version of " fqn)] + `(let [g# (delay (dynaload '~fqn))] + (defn ~s + ~doc + [& ~'args] + (apply @g# ~'args))))) + +(defmacro ^:skip-wiki lazy-combinators + "Implementation macro, do not call directly." + [& syms] + `(do + ~@(c/map + (fn [s] (c/list 'lazy-combinator s)) + syms))) + +(lazy-combinators hash-map list map not-empty set vector fmap elements + bind choose fmap one-of such-that tuple sample return) + +(defmacro ^:skip-wiki lazy-prim + "Implementation macro, do not call directly." + [s] + (let [fqn (c/symbol "clojure.test.check.generators" (name s)) + doc (str "Fn returning " fqn)] + `(let [g# (delay (dynaload '~fqn))] + (defn ~s + ~doc + [& ~'args] + @g#)))) + +(defmacro ^:skip-wiki lazy-prims + "Implementation macro, do not call directly." + [& syms] + `(do + ~@(c/map + (fn [s] (c/list 'lazy-prim s)) + syms))) + +(lazy-prims any any-printable boolean char char-alpha char-alphanumeric char-ascii double + int keyword keyword-ns large-integer ratio simple-type simple-type-printable + string string-ascii string-alphanumeric symbol symbol-ns uuid) + +(defn cat + "Returns a generator of a sequence catenated from results of +gens, each of which should generate something sequential." + [& gens] + (fmap #(apply concat %) + (apply tuple gens))) + +(def ^:private + gen-builtins + (delay + (let [simple (simple-type-printable)] + {number? (one-of [(large-integer) (double)]) + integer? (large-integer) + float? (double) + string? (string-alphanumeric) + keyword? (keyword-ns) + symbol? (symbol-ns) + map? (map simple simple) + vector? (vector simple) + list? (list simple) + seq? (list simple) + char? (char) + set? (set simple) + nil? (return nil) + false? (return false) + true? (return true) + zero? (return 0) + rational? (one-of [(large-integer) (ratio)]) + coll? (one-of [(map simple simple) + (list simple) + (vector simple) + (set simple)]) + empty? (elements [nil '() [] {} #{}]) + associative? (one-of [(map simple simple) (vector simple)]) + sequential? (one-of [(list simple) (vector simple)]) + ratio? (such-that ratio? (ratio))}))) + +(defn gen-for-pred + "Given a predicate, returns a built-in generator if one exists." + [pred] + (if (set? pred) + (elements pred) + (get @gen-builtins pred))) + +(comment + (require :reload 'clojure.spec.gen) + (in-ns 'clojure.spec.gen) + + ;; combinators, see call to lazy-combinators above for complete list + (generate (one-of [(gen-for-pred integer?) (gen-for-pred string?)])) + (generate (such-that #(< 10000 %) (gen-for-pred integer?))) + (let [reqs {:a (gen-for-pred number?) + :b (gen-for-pred ratio?)} + opts {:c (gen-for-pred string?)}] + (generate (bind (choose 0 (count opts)) + #(let [args (concat (seq reqs) (shuffle (seq opts)))] + (->> args + (take (+ % (count reqs))) + (mapcat identity) + (apply hash-map)))))) + (generate (cat (list (gen-for-pred string?)) + (list (gen-for-pred ratio?)))) + + ;; load your own generator + (gen-for-name 'clojure.test.check.generators/int) + + ;; failure modes + (gen-for-name 'unqualified) + (gen-for-name 'clojure.core/+) + (gen-for-name 'clojure.core/name-does-not-exist) + (gen-for-name 'ns.does.not.exist/f) + + ) + + diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj new file mode 100644 index 0000000000..ebe3dd88bc --- /dev/null +++ b/src/clj/clojure/spec/test.clj @@ -0,0 +1,147 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.spec.test + (:require + [clojure.spec :as spec] + [clojure.spec.gen :as gen])) + +;; wrap spec/explain-data until specs always return nil for ok data +(defn- explain-data* + [spec v] + (when-not (spec/valid? spec v nil) + (spec/explain-data spec v))) + +;; wrap and unwrap spec failure data in an exception so that +;; quick-check will treat it as a failure. +(defn- wrap-failing + [explain-data step] + (ex-info "Wrapper" {::check-call (assoc explain-data :failed-on step)})) + +(defn- unwrap-failing + [ret] + (let [ret (if-let [explain (-> ret :result ex-data ::check-call)] + (assoc ret :result explain) + ret)] + (if-let [shrunk-explain (-> ret :shrunk :result ex-data ::check-call)] + (assoc-in ret [:shrunk :result] shrunk-explain)))) + +(defn- check-call + "Returns true if call passes specs, otherwise *returns* an exception +with explain-data plus a :failed-on key under ::check-call." + [f specs args] + (let [cargs (when (:args specs) (spec/conform (:args specs) args))] + (if (= cargs ::spec/invalid) + (wrap-failing (explain-data* (:args specs) args) :args) + (let [ret (apply f args) + cret (when (:ret specs) (spec/conform (:ret specs) ret))] + (if (= cret ::spec/invalid) + (wrap-failing (explain-data* (:ret specs) ret) :ret) + (if (and (:args specs) (:ret specs) (:fn specs)) + (if (spec/valid? (:fn specs) {:args cargs :ret cret}) + true + (wrap-failing (explain-data* (:fn specs) {:args cargs :ret cret}) :fn)) + true)))))) + +(defn check-fn + "Check a function using provided specs and test.check. +Same options and return as check-var" + [f specs + & {:keys [num-tests seed max-size reporter-fn] + :or {num-tests 100 max-size 200 reporter-fn (constantly nil)}}] + (let [g (spec/gen (:args specs)) + prop (gen/for-all* [g] #(check-call f specs %))] + (let [ret (gen/quick-check num-tests prop :seed seed :max-size max-size :reporter-fn reporter-fn)] + (if-let [[smallest] (-> ret :shrunk :smallest)] + (unwrap-failing ret) + ret)))) + +(defn check-var + "Checks a var's specs using test.check. Optional args are +passed through to test.check/quick-check: + + num-tests number of tests to run, default 100 + seed random seed + max-size how large an input to generate, max 200 + reporter-fn reporting fn + +Returns a map as quick-check, with :explain-data added if +:result is false." + [v & opts] + (let [specs (spec/fn-specs v)] + (if (:args specs) + (apply check-fn @v specs opts) + (throw (IllegalArgumentException. (str "No :args spec for " v)))))) + +(defn- run-var-tests + "Helper for run-tests, run-all-tests." + [vs] + (let [reporter-fn println] + (reduce + (fn [totals v] + (let [_ (println "Checking" v) + ret (check-var v :reporter-fn reporter-fn)] + (prn ret) + (cond-> totals + true (update :test inc) + (true? (:result ret)) (update :pass inc) + (::spec/problems (:result ret)) (update :fail inc) + (instance? Throwable (:result ret)) (update :error inc)))) + {:test 0, :pass 0, :fail 0, :error 0} + vs))) + +(defn run-tests + "Like run-all-tests, but scoped to specific namespaces, or to +*ns* if no ns-sym are specified." + [& ns-syms] + (if (seq ns-syms) + (run-var-tests (->> (apply spec/speced-vars ns-syms) + (filter (fn [v] (:args (spec/fn-specs v)))))) + (run-tests (.name ^clojure.lang.Namespace *ns*)))) + +(defn run-all-tests + "Like clojure.test/run-all-tests, but runs test.check tests +for all speced vars. Prints per-test results to *out*, and +returns a map with :test,:pass,:fail, and :error counts." + [] + (run-var-tests (spec/speced-vars))) + +(comment + (require '[clojure.pprint :as pp] + '[clojure.spec :as s] + '[clojure.spec.gen :as gen] + '[clojure.test :as ctest]) + + (require :reload '[clojure.spec.test :as test]) + + (load-file "examples/broken_specs.clj") + (load-file "examples/correct_specs.clj") + + ;; discover speced vars for your own test runner + (s/speced-vars) + + ;; check a single var + (test/check-var #'-) + (test/check-var #'+) + (test/check-var #'clojure.spec.broken-specs/throwing-fn) + + ;; old style example tests + (ctest/run-all-tests) + + (s/speced-vars 'clojure.spec.correct-specs) + ;; new style spec tests return same kind of map + (test/check-var #'subs) + (clojure.spec.test/run-tests 'clojure.core) + (test/run-all-tests) + + ) + + + + + diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj new file mode 100644 index 0000000000..58ba334adc --- /dev/null +++ b/test/clojure/test_clojure/spec.clj @@ -0,0 +1,180 @@ +(ns clojure.test-clojure.spec + (:require [clojure.spec :as s] + [clojure.spec.gen :as gen] + [clojure.spec.test :as stest] + [clojure.test :refer :all])) + +(set! *warn-on-reflection* true) + +(defmacro result-or-ex [x] + `(try + ~x + (catch Throwable t# + (.getName (class t#))))) + +(def even-count? #(even? (count %))) + +(deftest conform-explain + (let [a (s/and #(> % 5) #(< % 10)) + o (s/or :s string? :k keyword?) + c (s/cat :a string? :b keyword?) + either (s/alt :a string? :b keyword?) + star (s/* keyword?) + plus (s/+ keyword?) + opt (s/? keyword?) + andre (s/& (s/* keyword?) even-count?) + m (s/map-of keyword? string?) + coll (s/coll-of keyword? [])] + (are [spec x conformed ed] + (let [co (result-or-ex (s/conform spec x)) + e (result-or-ex (::s/problems (s/explain-data spec x)))] + (when (not= conformed co) (println "conform fail\n\texpect=" conformed "\n\tactual=" co)) + (when (not= ed e) (println "explain fail\n\texpect=" ed "\n\tactual=" e)) + (and (= conformed co) (= ed e))) + + keyword? :k :k nil + keyword? nil ::s/invalid {[] {:pred ::s/unknown :val nil :via []}} + keyword? "abc" ::s/invalid {[] {:pred ::s/unknown :val "abc" :via []}} + + a 6 6 nil + a 3 ::s/invalid '{[] {:pred (> % 5), :val 3 :via []}} + a 20 ::s/invalid '{[] {:pred (< % 10), :val 20 :via []}} + a nil "java.lang.NullPointerException" "java.lang.NullPointerException" + a :k "java.lang.ClassCastException" "java.lang.ClassCastException" + + o "a" [:s "a"] nil + o :a [:k :a] nil + o 'a ::s/invalid '{[:s] {:pred string?, :val a :via []}, [:k] {:pred keyword?, :val a :via []}} + + c nil ::s/invalid '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}} + c [] ::s/invalid '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}} + c [:a] ::s/invalid '{[:a] {:pred string?, :val :a, :via []}} + c ["a"] ::s/invalid '{[:b] {:reason "Insufficient input", :pred keyword?, :val (), :via []}} + c ["s" :k] '{:a "s" :b :k} nil + c ["s" :k 5] ::s/invalid '{[] {:reason "Extra input", :pred (cat :a string? :b keyword?), :val (5), :via []}} + + either nil ::s/invalid '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}} + either [] ::s/invalid '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}} + either [:k] [:b :k] nil + either ["s"] [:a "s"] nil + either [:b "s"] ::s/invalid '{[] {:reason "Extra input", :pred (alt :a string? :b keyword?), :val ("s") :via []}} + + star nil [] nil + star [] [] nil + star [:k] [:k] nil + star [:k1 :k2] [:k1 :k2] nil + star [:k1 :k2 "x"] ::s/invalid '{[] {:pred keyword?, :val "x" :via []}} + star ["a"] ::s/invalid {[] '{:pred keyword?, :val "a" :via []}} + + plus nil ::s/invalid '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}} + plus [] ::s/invalid '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}} + plus [:k] [:k] nil + plus [:k1 :k2] [:k1 :k2] nil + plus [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (cat), :val ("x") :via []}} + plus ["a"] ::s/invalid '{[] {:pred keyword?, :val "a" :via []}} + + opt nil nil nil + opt [] nil nil + opt :k ::s/invalid '{[] {:pred (alt), :val :k, :via []}} + opt [:k] :k nil + opt [:k1 :k2] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2), :via []}} + opt [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2 "x"), :via []}} + opt ["a"] ::s/invalid "java.lang.IllegalArgumentException" + + andre nil nil nil + andre [] nil nil + andre :k :clojure.spec/invalid '{[] {:pred (& (* keyword?) even-count?), :val :k, :via []}} + andre [:k] ::s/invalid '{[] {:pred even-count?, :val [:k], :via []}} + andre [:j :k] [:j :k] nil + + m nil ::s/invalid '{[] {:pred map?, :val nil, :via []}} + m {} {} nil + m {:a "b"} {:a "b"} nil + m {:a :b} ::s/invalid '{[] {:pred (coll-checker (tuple keyword? string?)), :val {:a :b}, :via []}} + + coll nil nil nil + coll [] [] nil + coll [:a] [:a] nil + coll [:a :b] [:a :b] nil + ;;coll [:a "b"] ::s/invalid '{[] {:pred (coll-checker keyword?), :val [:a b], :via []}} + ))) + +(s/fdef flip-nums + :args (s/cat :arg1 integer? :arg2 integer?) + :ret vector? + :fn (fn [{:keys [args ret]}] + (= ret [(:arg2 args) (:arg1 args)]))) + +(def ^:dynamic *break-flip-nums* false) +(defn flip-nums + "Set *break-flip-nums* to break this fns compatibility with +its spec for test purposes." + [a b] + (if *break-flip-nums* + (when-not (= a b) + (vec (sort [a b]))) + [b a])) + +(defmacro get-ex-data + [x] + `(try + ~x + nil + (catch Throwable t# + (ex-data t#)))) + +;; Note the the complicated equality comparisons below are exactly the +;; kind of thing that spec helps you avoid, used here only because we +;; are near the bottom, testing spec itself. +(deftest test-instrument-flip-nums + (when-not (= "true" (System/getProperty "clojure.compiler.direct-linking")) + (binding [*break-flip-nums* true] + (try + (= [1 2] (flip-nums 2 1)) + (= [:a :b] (flip-nums :a :b)) + (= [1 2] (flip-nums 1 2)) + (is (nil? (flip-nums 1 1))) + (s/instrument `flip-nums) + (is (= [1 2] (flip-nums 2 1))) + (is (= '{:clojure.spec/problems {[:args :arg1] {:pred integer?, :val :a, :via []}}, :clojure.spec/args (:a :b)} + (get-ex-data (flip-nums :a :b)))) + (is (= '{:clojure.spec/problems {[:fn] {:pred (fn [{:keys [args ret]}] (= ret [(:arg2 args) (:arg1 args)])), :val {:args {:arg1 1, :arg2 2}, :ret [1 2]}, :via []}}, :clojure.spec/args (1 2)} + (get-ex-data (flip-nums 1 2)))) + (is (= '{:clojure.spec/problems {[:ret] {:pred vector?, :val nil, :via []}}, :clojure.spec/args (1 1)} + (get-ex-data (flip-nums 1 1)))) + (s/unstrument `flip-nums) + (= [1 2] (flip-nums 2 1)) + (= [:a :b] (flip-nums :a :b)) + (= [1 2] (flip-nums 1 2)) + (is (nil? (flip-nums 1 1))) + (s/unstrument `flip-nums))))) + +(def core-pred-syms + (into #{} + (comp (map first) (filter (fn [s] (.endsWith (name s) "?")))) + (ns-publics 'clojure.core))) + +(def generatable-core-pred-syms + (into #{} + (filter #(gen/gen-for-pred @ (resolve %))) + core-pred-syms)) + +(s/fdef generate-from-core-pred + :args (s/cat :s generatable-core-pred-syms) + :ret ::s/any + :fn (fn [{:keys [args ret]}] + (@(resolve (:s args)) ret))) + +(defn generate-from-core-pred + [s] + (gen/generate (gen/gen-for-pred @(resolve s)))) + +(comment + (require '[clojure.test :refer (run-tests)]) + (in-ns 'test-clojure.spec) + (run-tests) + + (stest/run-all-tests) + (stest/check-var #'generate-from-core-pred :num-tests 10000) + + ) From 4c8efbc42efa22ec1d08a1e9fa5dd25db99766a9 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Sat, 21 May 2016 12:13:50 -0500 Subject: [PATCH 163/854] Enhance doc to include spec and to doc registered specs Signed-off-by: Rich Hickey --- src/clj/clojure/repl.clj | 49 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index 70ea94f5aa..f38e2f4905 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -12,6 +12,7 @@ ^{:author "Chris Houser, Christophe Grand, Stephen Gilardi, Michel Salim" :doc "Utilities meant to be used interactively at the REPL"} clojure.repl + (:require [clojure.spec :as spec]) (:import (java.io LineNumberReader InputStreamReader PushbackReader) (clojure.lang RT Reflector))) @@ -79,27 +80,39 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (defn- namespace-doc [nspace] (assoc (meta nspace) :name (ns-name nspace))) -(defn- print-doc [m] +(defn- print-doc [{n :ns + nm :name + :keys [forms arglists special-form doc url macro spec] + :as m}] (println "-------------------------") - (println (str (when-let [ns (:ns m)] (str (ns-name ns) "/")) (:name m))) + (println (or spec (str (when n (str (ns-name n) "/")) nm))) + (when forms + (doseq [f forms] + (print " ") + (prn f))) + (when arglists + (prn arglists)) (cond - (:forms m) (doseq [f (:forms m)] - (print " ") - (prn f)) - (:arglists m) (prn (:arglists m))) - (if (:special-form m) + special-form (do (println "Special Form") - (println " " (:doc m)) + (println " " doc) (if (contains? m :url) - (when (:url m) - (println (str "\n Please see http://clojure.org/" (:url m)))) - (println (str "\n Please see http://clojure.org/special_forms#" - (:name m))))) - (do - (when (:macro m) - (println "Macro")) - (println " " (:doc m))))) + (when url + (println (str "\n Please see http://clojure.org/" url))) + (println (str "\n Please see http://clojure.org/special_forms#" nm)))) + macro + (println "Macro") + spec + (println "Spec")) + (when doc (println " " doc)) + (when n + (when-let [specs (spec/fn-specs (symbol (str (ns-name n)) (name nm)))] + (println "Spec") + (run! (fn [[role spec]] + (when (and spec (not (= spec ::spec/unknown))) + (println " " (str (name role) ":") (spec/describe spec)))) + specs)))) (defn find-doc "Prints documentation for any var whose documentation or name @@ -118,13 +131,15 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (print-doc m)))) (defmacro doc - "Prints documentation for a var or special form given its name" + "Prints documentation for a var or special form given its name, + or for a spec if given a keyword" {:added "1.0"} [name] (if-let [special-name ('{& fn catch try finally try} name)] (#'print-doc (#'special-doc special-name)) (cond (special-doc-map name) `(#'print-doc (#'special-doc '~name)) + (keyword? name) `(#'print-doc {:spec '~name :doc '~(spec/describe name)}) (find-ns name) `(#'print-doc (#'namespace-doc (find-ns '~name))) (resolve name) `(#'print-doc (meta (var ~name)))))) From 7f5f53b881b15aaf81bac40d218a3eb535e90193 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Mon, 23 May 2016 13:51:38 -0400 Subject: [PATCH 164/854] check specs on macroexpand Signed-off-by: Rich Hickey --- src/clj/clojure/core.clj | 2 ++ src/jvm/clojure/lang/Compiler.java | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 32d04d8e96..cf49bb63df 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7512,6 +7512,8 @@ (reduce load-data-reader-file mappings (data-reader-urls))))) +(load "spec") + (try (load-data-readers) (catch Throwable t diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 60668258ff..e096adb0d8 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6771,17 +6771,32 @@ public static Object macroexpand1(Object x) { Var v = isMacro(op); if(v != null) { + ISeq args = RT.cons(form, RT.cons(Compiler.LOCAL_ENV.get(), form.next())); try { - return v.applyTo(RT.cons(form,RT.cons(LOCAL_ENV.get(),form.next()))); + final Namespace checkns = Namespace.find(Symbol.intern("clojure.spec")); + if (checkns != null) + { + final Var check = Var.find(Symbol.intern("clojure.spec/macroexpand-check")); + if ((check != null) && (check.isBound())) + check.applyTo(RT.cons(v, RT.list(args))); + } + Symbol.intern("clojure.spec"); + } + catch(IllegalArgumentException e) + { + throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e); + } + try + { + return v.applyTo(args); } catch(ArityException e) { // hide the 2 extra params for a macro throw new ArityException(e.actual - 2, e.name); } - } - else + } else { if(op instanceof Symbol) { From db17177f01a2d100f454db1d74f070fe1e764e5d Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 23 May 2016 14:58:46 -0400 Subject: [PATCH 165/854] add recursion limit to multi-spec, lower default limit to 4 --- src/clj/clojure/spec.clj | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 386a765d0d..03de2659a6 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -17,10 +17,10 @@ (set! *warn-on-reflection* true) (def ^:dynamic *recursion-limit* - "A soft limit on how many times a branching spec (or/alt/*/opt-keys) + "A soft limit on how many times a branching spec (or/alt/*/opt-keys/multi-spec) can be recursed through during generation. After this a non-recursive branch will be chosen." - 10) + 4) (def ^:dynamic *fspec-iterations* "The number of times an anonymous fn specified by fspec will be (generatively) tested during conform" @@ -830,7 +830,8 @@ by ns-syms. Idempotent." (assert (when-let [dm (-> (methods @mmvar) ::invalid)] (nil? (dm nil))) (str "Multimethod :" form " does not contain nil-returning default method for :clojure.spec/invalid" )) - (let [predx #(@mmvar %) + (let [id (java.util.UUID/randomUUID) + predx #(@mmvar %) tag (if (keyword? retag) #(assoc %1 retag %2) retag)] @@ -850,9 +851,12 @@ by ns-syms. Idempotent." (gfn) (let [gen (fn [[k f]] (let [p (f nil)] - (gen/fmap - #(tag % k) - (gensub p overrides path rmap (list 'method form k))))) + (let [idk [id k] + rmap (inck rmap idk)] + (when-not (recur-limit? rmap idk [idk] idk) + (gen/fmap + #(tag % k) + (gensub p overrides path rmap (list 'method form k))))))) gs (->> (methods @mmvar) (remove (fn [[k]] (= k ::invalid))) (map gen) @@ -941,7 +945,7 @@ by ns-syms. Idempotent." (let [gen (fn [k p f] (let [rmap (inck rmap id)] (when-not (recur-limit? rmap id path k) - (gensub p overrides (conj path k) rmap f)))) + (gensub p overrides (conj path k) rmap f)))) gs (remove nil? (map gen keys preds forms))] (when-not (empty? gs) (gen/one-of gs))))) From 9e7526072248e1a50a8c6055b6563033298cd9d0 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 23 May 2016 17:52:45 -0400 Subject: [PATCH 166/854] missing case in op-explain --- src/clj/clojure/spec.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 03de2659a6..095f6e8664 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1161,6 +1161,7 @@ by ns-syms. Idempotent." :via via}})] (when p (case op + ::accept nil nil (if (empty? input) (insufficient path form) (explain-1 form p path via x)) From 6e23d29115f3d63adbe1a601a84eae5658b52909 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 23 May 2016 17:15:57 -0500 Subject: [PATCH 167/854] opt test fix Signed-off-by: Rich Hickey --- test/clojure/test_clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 58ba334adc..d2147fda3d 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -79,7 +79,7 @@ opt [:k] :k nil opt [:k1 :k2] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2), :via []}} opt [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2 "x"), :via []}} - opt ["a"] ::s/invalid "java.lang.IllegalArgumentException" + opt ["a"] ::s/invalid '{[] {:pred keyword?, :val "a", :via []}} andre nil nil nil andre [] nil nil From 8213c934c0b5178aed731b433242b52ddc798cc0 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 23 May 2016 20:07:19 -0400 Subject: [PATCH 168/854] spec.gen and spec.test missing from build.xml, move spec load from core.clj to RT doInit --- build.xml | 2 ++ src/clj/clojure/core.clj | 2 -- src/jvm/clojure/lang/RT.java | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml index f9764b361d..a2e867c1a1 100644 --- a/build.xml +++ b/build.xml @@ -82,6 +82,8 @@ + + diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index cf49bb63df..32d04d8e96 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7512,8 +7512,6 @@ (reduce load-data-reader-file mappings (data-reader-urls))))) -(load "spec") - (try (load-data-readers) (catch Throwable t diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 324616e643..6faad4c703 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -459,6 +459,7 @@ else if(!loaded && failIfNotFound) static void doInit() throws ClassNotFoundException, IOException{ load("clojure/core"); + load("clojure/spec"); Var.pushThreadBindings( RT.mapUniqueKeys(CURRENT_NS, CURRENT_NS.deref(), From 37ec54584437b4d8beb3ffb3879068b9afe1de0b Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 24 May 2016 11:28:01 -0400 Subject: [PATCH 169/854] use delayed generators in branching (potentially recursive) specs --- src/clj/clojure/spec.clj | 18 +++++++++++------- src/clj/clojure/spec/gen.clj | 33 +++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 095f6e8664..c301c02d9e 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -20,7 +20,7 @@ "A soft limit on how many times a branching spec (or/alt/*/opt-keys/multi-spec) can be recursed through during generation. After this a non-recursive branch will be chosen." - 4) + 8) (def ^:dynamic *fspec-iterations* "The number of times an anonymous fn specified by fspec will be (generatively) tested during conform" @@ -778,7 +778,7 @@ by ns-syms. Idempotent." gen (fn [k s] (gensub s overrides (conj path k) rmap k)) ogen (fn [k s] (when-not (recur-limit? rmap id path k) - [k (gensub s overrides (conj path k) rmap k)])) + [k (gen/delay (gensub s overrides (conj path k) rmap k))])) req-gens (map gen req-keys req-specs) opt-gens (remove nil? (map ogen opt-keys opt-specs))] (when (every? identity (concat req-gens opt-gens)) @@ -854,9 +854,10 @@ by ns-syms. Idempotent." (let [idk [id k] rmap (inck rmap idk)] (when-not (recur-limit? rmap idk [idk] idk) - (gen/fmap - #(tag % k) - (gensub p overrides path rmap (list 'method form k))))))) + (gen/delay + (gen/fmap + #(tag % k) + (gensub p overrides path rmap (list 'method form k)))))))) gs (->> (methods @mmvar) (remove (fn [[k]] (= k ::invalid))) (map gen) @@ -945,7 +946,8 @@ by ns-syms. Idempotent." (let [gen (fn [k p f] (let [rmap (inck rmap id)] (when-not (recur-limit? rmap id path k) - (gensub p overrides (conj path k) rmap f)))) + (gen/delay + (gensub p overrides (conj path k) rmap f))))) gs (remove nil? (map gen keys preds forms))] (when-not (empty? gs) (gen/one-of gs))))) @@ -1209,7 +1211,9 @@ by ns-syms. Idempotent." (let [gen (fn [p k f] ;;(prn {:k k :path path :rmap rmap :op op :id id}) (when-not (c/and rmap id k (recur-limit? rmap id path k)) - (re-gen p overrides (if k (conj path k) path) rmap (c/or f p))))] + (if id + (gen/delay (re-gen p overrides (if k (conj path k) path) rmap (c/or f p))) + (re-gen p overrides (if k (conj path k) path) rmap (c/or f p)))))] (map gen ps (c/or (seq ks) (repeat nil)) (c/or (seq forms) (repeat nil)))))] (c/or (when-let [g (get overrides path)] (case op diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index b986790eda..a5ef475f8d 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -8,7 +8,7 @@ (ns clojure.spec.gen (:refer-clojure :exclude [boolean cat hash-map list map not-empty set vector - char double int keyword symbol string uuid])) + char double int keyword symbol string uuid delay])) (alias 'c 'clojure.core) @@ -23,28 +23,45 @@ (throw (RuntimeException. (str "Var " s " is not on the classpath"))))))) (def ^:private quick-check-ref - (delay (dynaload 'clojure.test.check/quick-check))) + (c/delay (dynaload 'clojure.test.check/quick-check))) (defn quick-check [& args] (apply @quick-check-ref args)) (def ^:private for-all*-ref - (delay (dynaload 'clojure.test.check.properties/for-all*))) + (c/delay (dynaload 'clojure.test.check.properties/for-all*))) (defn for-all* "Dynamically loaded clojure.test.check.properties/for-all*." [& args] (apply @for-all*-ref args)) -(let [g? (delay (dynaload 'clojure.test.check.generators/generator?)) - g (delay (dynaload 'clojure.test.check.generators/generate))] +(let [g? (c/delay (dynaload 'clojure.test.check.generators/generator?)) + g (c/delay (dynaload 'clojure.test.check.generators/generate)) + mkg (c/delay (dynaload 'clojure.test.check.generators/->Generator))] (defn- generator? [x] (@g? x)) + (defn- generator + [gfn] + (@mkg gfn)) (defn generate "Generate a single value using generator." [generator] (@g generator))) +(defn ^:skip-wiki delay-impl + [gfnd] + ;;N.B. depends on test.check impl details + (generator (fn [rnd size] + ((:gen @gfnd) rnd size)))) + +(defmacro delay + "given body that returns a generator, returns a + generator that delegates to that, but delays + creation until used." + [& body] + `(delay-impl (c/delay ~@body))) + (defn gen-for-name "Dynamically loads test.check generator named s." [s] @@ -58,7 +75,7 @@ [s] (let [fqn (c/symbol "clojure.test.check.generators" (name s)) doc (str "Lazy loaded version of " fqn)] - `(let [g# (delay (dynaload '~fqn))] + `(let [g# (c/delay (dynaload '~fqn))] (defn ~s ~doc [& ~'args] @@ -80,7 +97,7 @@ [s] (let [fqn (c/symbol "clojure.test.check.generators" (name s)) doc (str "Fn returning " fqn)] - `(let [g# (delay (dynaload '~fqn))] + `(let [g# (c/delay (dynaload '~fqn))] (defn ~s ~doc [& ~'args] @@ -107,7 +124,7 @@ gens, each of which should generate something sequential." (def ^:private gen-builtins - (delay + (c/delay (let [simple (simple-type-printable)] {number? (one-of [(large-integer) (double)]) integer? (large-integer) From f8539f55a78b2a19f7cca10d2ad7928156ae90fe Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 24 May 2016 11:46:36 -0500 Subject: [PATCH 170/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..bf20d32f2f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha1 http://clojure.org/ Clojure core environment and runtime library. From 53a83f4b33c3763c57ae5cc9f444db787c560b6d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 24 May 2016 11:46:36 -0500 Subject: [PATCH 171/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bf20d32f2f..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha1 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From ac59179c09d57dc3db2cc078ac06845b625555da Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 24 May 2016 14:24:30 -0400 Subject: [PATCH 172/854] better describe for s/+ --- src/clj/clojure/spec.clj | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index c301c02d9e..97c2ac8437 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1001,7 +1001,7 @@ by ns-syms. Idempotent." (defn- accept? [{:keys [::op]}] (= ::accept op)) -(defn- pcat* [{[p1 & pr :as ps] :ps, [k1 & kr :as ks] :ks, [f1 & fr :as forms] :forms, ret :ret}] +(defn- pcat* [{[p1 & pr :as ps] :ps, [k1 & kr :as ks] :ks, [f1 & fr :as forms] :forms, ret :ret, rep+ :rep+}] (when (every? identity ps) (if (accept? p1) (let [rp (:ret p1) @@ -1009,7 +1009,7 @@ by ns-syms. Idempotent." (if pr (pcat* {:ps pr :ks kr :forms fr :ret ret}) (accept ret))) - {::op ::pcat, :ps ps, :ret ret, :ks ks, :forms forms}))) + {::op ::pcat, :ps ps, :ret ret, :ks ks, :forms forms :rep+ rep+}))) (defn- pcat [& ps] (pcat* {:ps ps :ret []})) @@ -1032,7 +1032,7 @@ by ns-syms. Idempotent." (defn ^:skip-wiki rep+impl "Do not call this directly, use '+'" [form p] - (pcat* {:ps [p (rep* p p [] true form)] :forms `[~form (* ~form)] :ret []})) + (pcat* {:ps [p (rep* p p [] true form)] :forms `[~form (* ~form)] :ret [] :rep+ form})) (defn ^:skip-wiki amp-impl "Do not call this directly, use '&'" @@ -1139,15 +1139,17 @@ by ns-syms. Idempotent." ::alt (alt* (map #(deriv % x) ps) ks forms) ::rep (rep* (deriv p1 x) p2 ret splice forms))))) -(defn- op-describe [p] - ;;(prn {:op op :ks ks :forms forms}) - (let [{:keys [::op ps ks forms splice p1] :as p} (reg-resolve p)] +(defn- op-describe [p] + (let [{:keys [::op ps ks forms splice p1 rep+] :as p} (reg-resolve p)] + ;;(prn {:op op :ks ks :forms forms :p p}) (when p (case op ::accept nil nil p ::amp (list* 'clojure.spec/& (op-describe p1) forms) - ::pcat (cons `cat (mapcat vector ks forms)) + ::pcat (if rep+ + (list `+ rep+) + (cons `cat (mapcat vector ks forms))) ::alt (cons `alt (mapcat vector ks forms)) ::rep (list (if splice `+ `*) forms))))) From 6d68a0a955ac89ca5e5924ed1d31de373a97e4e4 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 24 May 2016 15:00:04 -0400 Subject: [PATCH 173/854] capture *recursion-limit* on gen call --- src/clj/clojure/spec.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 97c2ac8437..76f1e6b7f1 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -20,7 +20,7 @@ "A soft limit on how many times a branching spec (or/alt/*/opt-keys/multi-spec) can be recursed through during generation. After this a non-recursive branch will be chosen." - 8) + 4) (def ^:dynamic *fspec-iterations* "The number of times an anonymous fn specified by fspec will be (generatively) tested during conform" @@ -207,7 +207,7 @@ sequential collection (i.e. a generator for s/? should return either an empty sequence/vector or a sequence/vector with one item in it)" ([spec] (gen spec nil)) - ([spec overrides] (gensub spec overrides [] nil spec))) + ([spec overrides] (gensub spec overrides [] {::recursion-limit *recursion-limit*} spec))) (defn- ->sym "Returns a symbol from a symbol or var" @@ -697,7 +697,7 @@ by ns-syms. Idempotent." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- recur-limit? [rmap id path k] - (c/and (> (get rmap id) *recursion-limit*) + (c/and (> (get rmap id) (::recursion-limit rmap)) (contains? (set path) k))) (defn- inck [m k] From 0298149d169759eacef59702b26db008cd782dac Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 24 May 2016 17:28:42 -0400 Subject: [PATCH 174/854] first cut at capturing :in - path in input data, uses indexes in regexes --- src/clj/clojure/spec.clj | 116 +++++++++++++++-------------- test/clojure/test_clojure/spec.clj | 2 +- 2 files changed, 62 insertions(+), 56 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 76f1e6b7f1..63ee87b25f 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -36,7 +36,7 @@ (defprotocol Spec (conform* [spec x]) - (explain* [spec path via x]) + (explain* [spec path via in x]) (gen* [spec overrides path rmap]) (with-gen* [spec gfn]) (describe* [spec])) @@ -139,8 +139,8 @@ [spec gen-fn] (with-gen* (specize spec) gen-fn)) -(defn explain-data* [spec path via x] - (when-let [probs (explain* (specize spec) path via x)] +(defn explain-data* [spec path via in x] + (when-let [probs (explain* (specize spec) path via in x)] {::problems probs})) (defn explain-data @@ -150,7 +150,7 @@ keys describing the predicate and the value that failed at that path." [spec x] - (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) x)) + (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) [] x)) (defn- explain-out "prints an explanation to *out*." @@ -158,19 +158,20 @@ (if ed (do ;;(prn {:ed ed}) - (doseq [[path {:keys [pred val reason via] :as prob}] (::problems ed)] - (when-not (empty? path) - (print "At:" path "")) + (doseq [[path {:keys [pred val reason via in] :as prob}] (::problems ed)] + (when-not (empty? in) + (print "In:" in "")) (print "val: ") (pr val) - (print " fails") - (when-let [specname (last via)] - (print " spec:" specname)) + (print " fails spec: ") + (print (c/or (last via) "_")) + (when-not (empty? path) + (print " at:" path)) (print " predicate: ") (pr pred) (when reason (print ", " reason)) (doseq [[k v] prob] - (when-not (#{:pred :val :reason :via} k) + (when-not (#{:pred :val :reason :via :in} k) (print "\n\t" k " ") (pr v))) (newline)) @@ -723,11 +724,12 @@ by ns-syms. Idempotent." ([spec x form] (not= ::invalid (dt spec x form)))) -(defn- explain-1 [form pred path via v] +(defn- explain-1 [form pred path via in v] + ;;(prn {:form form :pred pred :path path :in in :v v}) (let [pred (maybe-spec pred)] (if (spec? pred) - (explain* pred path (if-let [name (spec-name pred)] (conj via name) via) v) - {path {:pred (abbrev form) :val v :via via}}))) + (explain* pred path (if-let [name (spec-name pred)] (conj via name) via) in v) + {path {:pred (abbrev form) :val v :via via :in in}}))) (defn ^:skip-wiki map-spec-impl "Do not call this directly, use 'spec' with a map argument" @@ -756,20 +758,20 @@ by ns-syms. Idempotent." (recur ret ks)) ret))) ::invalid)) - (explain* [_ path via x] + (explain* [_ path via in x] (if-not (map? x) - {path {:pred 'map? :val x :via via}} + {path {:pred 'map? :val x :via via :in in}} (let [reg (registry)] (apply merge (when-let [probs (->> (map (fn [pred form] (when-not (pred x) (abbrev form))) pred-exprs pred-forms) (keep identity) seq)] - {path {:pred (vec probs) :val x :via via}}) + {path {:pred (vec probs) :val x :via via :in in}}) (map (fn [[k v]] (when-not (c/or (not (contains? reg (keys->specs k))) (valid? (keys->specs k) v k)) - (explain-1 (keys->specs k) (keys->specs k) (conj path k) via v))) + (explain-1 (keys->specs k) (keys->specs k) (conj path k) via (conj in k) v))) (seq x)))))) (gen* [_ overrides path rmap] (if gfn @@ -814,9 +816,9 @@ by ns-syms. Idempotent." (invoke [this x] (valid? this x)) Spec (conform* [_ x] (dt pred x form cpred?)) - (explain* [_ path via x] + (explain* [_ path via in x] (when (= ::invalid (dt pred x form cpred?)) - {path {:pred (abbrev form) :val x :via via}})) + {path {:pred (abbrev form) :val x :via via :in in}})) (gen* [_ _ _ _] (if gfn (gfn) (gen/gen-for-pred pred))) @@ -842,10 +844,10 @@ by ns-syms. Idempotent." (conform* [_ x] (if-let [pred (predx x)] (dt pred x form) ::invalid)) - (explain* [_ path via x] + (explain* [_ path via in x] (if-let [pred (predx x)] - (explain-1 form pred path via x) - {path {:pred form :val x :reason "no method" :via via}})) + (explain-1 form pred path via in x) + {path {:pred form :val x :reason "no method" :via via :in in}})) (gen* [_ overrides path rmap] (if gfn (gfn) @@ -888,20 +890,20 @@ by ns-syms. Idempotent." ::invalid (recur (if (identical? cv v) ret (assoc ret i cv)) (inc i)))))))) - (explain* [_ path via x] + (explain* [_ path via in x] (cond (not (vector? x)) - {path {:pred 'vector? :val x :via via}} + {path {:pred 'vector? :val x :via via :in in}} (not= (count x) (count preds)) - {path {:pred `(= (count ~'%) ~(count preds)) :val x :via via}} + {path {:pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}} :else (apply merge (map (fn [i form pred] (let [v (x i)] (when-not (valid? pred v) - (explain-1 form pred (conj path i) via v)))) + (explain-1 form pred (conj path i) via (conj in i) v)))) (range (count preds)) forms preds)))) (gen* [_ overrides path rmap] (if gfn @@ -933,12 +935,12 @@ by ns-syms. Idempotent." (invoke [this x] (valid? this x)) Spec (conform* [_ x] (cform x)) - (explain* [this path via x] + (explain* [this path via in x] (when-not (valid? this x) (apply merge (map (fn [k form pred] (when-not (valid? pred x) - (explain-1 form pred (conj path k) via x))) + (explain-1 form pred (conj path k) via in x))) keys forms preds)))) (gen* [_ overrides path rmap] (if gfn @@ -967,7 +969,7 @@ by ns-syms. Idempotent." ret))) (defn- explain-pred-list - [forms preds path via x] + [forms preds path via in x] (loop [ret x [form & forms] forms [pred & preds] preds] @@ -975,7 +977,7 @@ by ns-syms. Idempotent." (let [nret (dt pred ret form)] (if (not= ::invalid nret) (recur nret forms preds) - (explain-1 form pred path via ret)))))) + (explain-1 form pred path via in ret)))))) (defn ^:skip-wiki and-spec-impl "Do not call this directly, use 'and'" @@ -985,7 +987,7 @@ by ns-syms. Idempotent." (invoke [this x] (valid? this x)) Spec (conform* [_ x] (and-preds x preds forms)) - (explain* [_ path via x] (explain-pred-list forms preds path via x)) + (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) (describe* [_] `(and ~@forms)))) @@ -1153,7 +1155,7 @@ by ns-syms. Idempotent." ::alt (cons `alt (mapcat vector ks forms)) ::rep (list (if splice `+ `*) forms))))) -(defn- op-explain [form p path via input] +(defn- op-explain [form p path via in input] ;;(prn {:form form :p p :path path :input input}) (let [[x :as input] input via (if-let [name (spec-name p)] (conj via name) via) @@ -1162,20 +1164,21 @@ by ns-syms. Idempotent." {path {:reason "Insufficient input" :pred (abbrev form) :val () - :via via}})] + :via via + :in in}})] (when p (case op ::accept nil nil (if (empty? input) (insufficient path form) - (explain-1 form p path via x)) + (explain-1 form p path via in x)) ::amp (if (empty? input) (if (accept-nil? p1) - (explain-pred-list forms ps path via (preturn p1)) + (explain-pred-list forms ps path via in (preturn p1)) (insufficient path (op-describe p1))) (if-let [p1 (deriv p1 x)] - (explain-pred-list forms ps path via (preturn p1)) - (op-explain (op-describe p1) p1 path via input))) + (explain-pred-list forms ps path via in (preturn p1)) + (op-explain (op-describe p1) p1 path via in input))) ::pcat (let [[pred k form] (->> (map vector ps (c/or (seq ks) (repeat nil)) @@ -1187,7 +1190,7 @@ by ns-syms. Idempotent." form (c/or form (op-describe pred))] (if (c/and (empty? input) (not pred)) (insufficient path form) - (op-explain form pred path via input))) + (op-explain form pred path via in input))) ::alt (if (empty? input) (insufficient path (op-describe p)) (apply merge @@ -1196,6 +1199,7 @@ by ns-syms. Idempotent." pred (if k (conj path k) path) via + in input)) (c/or (seq ks) (repeat nil)) (c/or (seq forms) (repeat nil)) @@ -1203,7 +1207,7 @@ by ns-syms. Idempotent." ::rep (op-explain (if (identical? p1 p2) forms (op-describe p1)) - p1 path via input))))) + p1 path via in input))))) (defn- re-gen [p overrides path rmap f] ;;(prn {:op op :ks ks :forms forms}) @@ -1254,25 +1258,27 @@ by ns-syms. Idempotent." (recur dp xs) ::invalid))) -(defn- re-explain [path via re input] - (loop [p re [x & xs :as data] input] +(defn- re-explain [path via in re input] + (loop [p re [x & xs :as data] input i 0] ;;(prn {:p p :x x :xs xs}) (prn) (if (empty? data) (if (accept-nil? p) nil ;;success - (op-explain (op-describe p) p path via nil)) + (op-explain (op-describe p) p path via in nil)) (if-let [dp (deriv p x)] - (recur dp xs) + (recur dp xs (inc i)) (if (accept? p) {path {:reason "Extra input" :pred (abbrev (op-describe re)) :val data - :via via}} - (c/or (op-explain (op-describe p) p path via (seq data)) + :via via + :in (conj in i)}} + (c/or (op-explain (op-describe p) p path via (conj in i) (seq data)) {path {:reason "Extra input" :pred (abbrev (op-describe p)) :val data - :via via}})))))) + :via via + :in (conj in i)}})))))) (defn ^:skip-wiki regex-spec-impl "Do not call this directly, use 'spec' with a regex op argument" @@ -1285,10 +1291,10 @@ by ns-syms. Idempotent." (if (c/or (nil? x) (coll? x)) (re-conform re (seq x)) ::invalid)) - (explain* [_ path via x] + (explain* [_ path via in x] (if (c/or (nil? x) (coll? x)) - (re-explain path via re (seq x)) - {path {:pred (abbrev (op-describe re)) :val x :via via}})) + (re-explain path via in re (seq x)) + {path {:pred (abbrev (op-describe re)) :val x :via via :in in}})) (gen* [_ overrides path rmap] (if gfn (gfn) @@ -1330,7 +1336,7 @@ by ns-syms. Idempotent." (conform* [_ f] (if (fn? f) (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) ::invalid)) - (explain* [_ path via f] + (explain* [_ path via in f] (if (fn? f) (let [args (validate-fn f specs 100)] (if (identical? f args) ;;hrm, we might not be able to reproduce @@ -1338,15 +1344,15 @@ by ns-syms. Idempotent." (let [ret (try (apply f args) (catch Throwable t t))] (if (instance? Throwable ret) ;;TODO add exception data - {path {:pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via}} + {path {:pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via :in in}} (let [cret (dt retspec ret rform)] (if (= ::invalid cret) - (explain-1 rform retspec (conj path :ret) via ret) + (explain-1 rform retspec (conj path :ret) via in ret) (when fnspec (let [cargs (conform argspec args)] - (explain-1 fform fnspec (conj path :fn) via {:args cargs :ret cret}))))))))) - {path {:pred 'fn? :val f :via via}})) + (explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret}))))))))) + {path {:pred 'fn? :val f :via via :in in}})) (gen* [_ _ _ _] (if gfn (gfn) (when-not fnspec diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index d2147fda3d..cc33832e3e 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -14,7 +14,7 @@ (def even-count? #(even? (count %))) -(deftest conform-explain +#_(deftest conform-explain (let [a (s/and #(> % 5) #(< % 10)) o (s/or :s string? :k keyword?) c (s/cat :a string? :b keyword?) From dbce643265b8888d0c2d26cc2034387a222ccbaa Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 24 May 2016 20:47:49 -0400 Subject: [PATCH 175/854] add in arg to explain-data* calls --- src/clj/clojure/spec.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 63ee87b25f..de877a06e7 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -519,7 +519,7 @@ (let [conform! (fn [v role spec data args] (let [conformed (conform spec data)] (if (= ::invalid conformed) - (let [ed (assoc (explain-data* spec [role] [] data) + (let [ed (assoc (explain-data* spec [role] [] [] data) ::args args)] (throw (ex-info (str "Call to " v " did not conform to spec:\n" (with-out-str (explain-out ed))) @@ -545,7 +545,7 @@ (when-let [arg-spec (:args specs)] (when (= ::invalid (conform arg-spec args)) (let [ed (assoc (explain-data* arg-spec [:args] - (if-let [name (spec-name arg-spec)] [name] []) args) + (if-let [name (spec-name arg-spec)] [name] []) [] args) ::args args)] (throw (IllegalArgumentException. (str From eacfcf08010e91e96da5233782fcdf82a593a0b3 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 24 May 2016 16:53:29 -0500 Subject: [PATCH 176/854] make spec tests tolerant of additive explain-data Signed-off-by: Rich Hickey --- test/clojure/test_clojure/spec.clj | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index cc33832e3e..8012ccbc80 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -14,7 +14,16 @@ (def even-count? #(even? (count %))) -#_(deftest conform-explain +(defn submap? + "Is m1 a subset of m2?" + [m1 m2] + (if (and (map? m1) (map? m2)) + (every? (fn [[k v]] (and (contains? m2 k) + (submap? v (get m2 k)))) + m1) + (= m1 m2))) + +(deftest conform-explain (let [a (s/and #(> % 5) #(< % 10)) o (s/or :s string? :k keyword?) c (s/cat :a string? :b keyword?) @@ -29,8 +38,8 @@ (let [co (result-or-ex (s/conform spec x)) e (result-or-ex (::s/problems (s/explain-data spec x)))] (when (not= conformed co) (println "conform fail\n\texpect=" conformed "\n\tactual=" co)) - (when (not= ed e) (println "explain fail\n\texpect=" ed "\n\tactual=" e)) - (and (= conformed co) (= ed e))) + (when (not (submap? ed e)) (println "explain fail\n\texpect=" ed "\n\tactual=" e)) + (and (= conformed co) (submap? ed e))) keyword? :k :k nil keyword? nil ::s/invalid {[] {:pred ::s/unknown :val nil :via []}} From ec2512edad9c0c4a006980eedd2a6ee8679d4b5d Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 25 May 2016 10:26:44 -0400 Subject: [PATCH 177/854] missing with-gen on regex --- src/clj/clojure/spec.clj | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index de877a06e7..5b22b842c6 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1283,22 +1283,23 @@ by ns-syms. Idempotent." (defn ^:skip-wiki regex-spec-impl "Do not call this directly, use 'spec' with a regex op argument" [re gfn] - (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) - Spec - (conform* [_ x] - (if (c/or (nil? x) (coll? x)) - (re-conform re (seq x)) - ::invalid)) - (explain* [_ path via in x] - (if (c/or (nil? x) (coll? x)) - (re-explain path via in re (seq x)) - {path {:pred (abbrev (op-describe re)) :val x :via via :in in}})) + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] + (if (c/or (nil? x) (coll? x)) + (re-conform re (seq x)) + ::invalid)) + (explain* [_ path via in x] + (if (c/or (nil? x) (coll? x)) + (re-explain path via in re (seq x)) + {path {:pred (abbrev (op-describe re)) :val x :via via :in in}})) (gen* [_ overrides path rmap] - (if gfn - (gfn) + (if gfn + (gfn) (re-gen re overrides path rmap (op-describe re)))) + (with-gen* [_ gfn] (regex-spec-impl re gfn)) (describe* [_] (op-describe re)))) ;;;;;;;;;;;;;;;;; HOFs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 37f38851d343c878860460e585b8d54c38237882 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 25 May 2016 09:37:48 -0500 Subject: [PATCH 178/854] make spec tests tolerant of adding explain-data Signed-off-by: Rich Hickey --- test/clojure/test_clojure/spec.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 8012ccbc80..af3dc71afe 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -145,11 +145,11 @@ its spec for test purposes." (is (nil? (flip-nums 1 1))) (s/instrument `flip-nums) (is (= [1 2] (flip-nums 2 1))) - (is (= '{:clojure.spec/problems {[:args :arg1] {:pred integer?, :val :a, :via []}}, :clojure.spec/args (:a :b)} + (is (submap? '{:clojure.spec/problems {[:args :arg1] {:pred integer?, :val :a, :via []}}, :clojure.spec/args (:a :b)} (get-ex-data (flip-nums :a :b)))) - (is (= '{:clojure.spec/problems {[:fn] {:pred (fn [{:keys [args ret]}] (= ret [(:arg2 args) (:arg1 args)])), :val {:args {:arg1 1, :arg2 2}, :ret [1 2]}, :via []}}, :clojure.spec/args (1 2)} + (is (submap? '{:clojure.spec/problems {[:fn] {:pred (fn [{:keys [args ret]}] (= ret [(:arg2 args) (:arg1 args)])), :val {:args {:arg1 1, :arg2 2}, :ret [1 2]}, :via []}}, :clojure.spec/args (1 2)} (get-ex-data (flip-nums 1 2)))) - (is (= '{:clojure.spec/problems {[:ret] {:pred vector?, :val nil, :via []}}, :clojure.spec/args (1 1)} + (is (submap? '{:clojure.spec/problems {[:ret] {:pred vector?, :val nil, :via []}}, :clojure.spec/args (1 1)} (get-ex-data (flip-nums 1 1)))) (s/unstrument `flip-nums) (= [1 2] (flip-nums 2 1)) From 881fcb966397648e7f39fcce07d5a8201c345536 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 25 May 2016 11:14:51 -0500 Subject: [PATCH 179/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..1dc9aa9bc7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha2 http://clojure.org/ Clojure core environment and runtime library. From bb6d305ffd24d1ce0286d3bf6903b6c68e2c95fd Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 25 May 2016 11:14:52 -0500 Subject: [PATCH 180/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1dc9aa9bc7..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha2 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From bf25f9956435966c90d12af91bc7320edf79d393 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 25 May 2016 13:24:37 -0400 Subject: [PATCH 181/854] macro specs do not include &form and &env args --- src/jvm/clojure/lang/Compiler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index e096adb0d8..8d5ce3abd8 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6771,7 +6771,6 @@ public static Object macroexpand1(Object x) { Var v = isMacro(op); if(v != null) { - ISeq args = RT.cons(form, RT.cons(Compiler.LOCAL_ENV.get(), form.next())); try { final Namespace checkns = Namespace.find(Symbol.intern("clojure.spec")); @@ -6779,7 +6778,7 @@ public static Object macroexpand1(Object x) { { final Var check = Var.find(Symbol.intern("clojure.spec/macroexpand-check")); if ((check != null) && (check.isBound())) - check.applyTo(RT.cons(v, RT.list(args))); + check.applyTo(RT.cons(v, RT.list(form.next()))); } Symbol.intern("clojure.spec"); } @@ -6789,6 +6788,7 @@ public static Object macroexpand1(Object x) { } try { + ISeq args = RT.cons(form, RT.cons(Compiler.LOCAL_ENV.get(), form.next())); return v.applyTo(args); } catch(ArityException e) From bfb82f86631bde45a8e3749ea7df509e59a0791c Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 25 May 2016 15:23:16 -0400 Subject: [PATCH 182/854] multi-specs include dispatch values in path, fix for rep* --- src/clj/clojure/spec.clj | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 5b22b842c6..207710784b 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -298,7 +298,12 @@ dispatch-tag that should return an appropriately retagged value. Note that because the tags themselves comprise an open set, - the tag keys cannot be :req in the specs. + the tag key spec cannot enumerate the values, but can e.g. + test for keyword?. + + Note also that the dispatch values of the multimethod will be + included in the path, i.e. in reporting and gen overrides, even + though those values are not evident in the spec. " [mm retag] `(multi-spec-impl '~(res mm) (var ~mm) ~retag)) @@ -834,6 +839,7 @@ by ns-syms. Idempotent." (str "Multimethod :" form " does not contain nil-returning default method for :clojure.spec/invalid" )) (let [id (java.util.UUID/randomUUID) predx #(@mmvar %) + dval #((.dispatchFn ^clojure.lang.MultiFn @mmvar) %) tag (if (keyword? retag) #(assoc %1 retag %2) retag)] @@ -845,21 +851,22 @@ by ns-syms. Idempotent." (dt pred x form) ::invalid)) (explain* [_ path via in x] - (if-let [pred (predx x)] - (explain-1 form pred path via in x) - {path {:pred form :val x :reason "no method" :via via :in in}})) + (let [dv (dval x) + path (conj path dv)] + (if-let [pred (predx x)] + (explain-1 form pred path via in x) + {path {:pred form :val x :reason "no method" :via via :in in}}))) (gen* [_ overrides path rmap] (if gfn (gfn) (let [gen (fn [[k f]] (let [p (f nil)] - (let [idk [id k] - rmap (inck rmap idk)] - (when-not (recur-limit? rmap idk [idk] idk) + (let [rmap (inck rmap id)] + (when-not (recur-limit? rmap id path k) (gen/delay (gen/fmap #(tag % k) - (gensub p overrides path rmap (list 'method form k)))))))) + (gensub p overrides (conj path k) rmap (list 'method form k)))))))) gs (->> (methods @mmvar) (remove (fn [[k]] (= k ::invalid))) (map gen) @@ -1139,7 +1146,8 @@ by ns-syms. Idempotent." ::pcat (alt2 (pcat* {:ps (cons (deriv p0 x) pr), :ks ks, :forms forms, :ret ret}) (when (accept-nil? p0) (deriv (pcat* {:ps pr, :ks kr, :forms (next forms), :ret (add-ret p0 ret k0)}) x))) ::alt (alt* (map #(deriv % x) ps) ks forms) - ::rep (rep* (deriv p1 x) p2 ret splice forms))))) + ::rep (alt2 (rep* (deriv p1 x) p2 ret splice forms) + (when (accept-nil? p1) (deriv (rep* p2 p2 (add-ret p1 ret nil) splice forms) x))))))) (defn- op-describe [p] (let [{:keys [::op ps ks forms splice p1 rep+] :as p} (reg-resolve p)] From 2ecf6c651ecc154754867592f954b5d13bb0d94e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 25 May 2016 15:55:20 -0400 Subject: [PATCH 183/854] get rid of default method requirements for multi-specs --- src/clj/clojure/spec.clj | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 207710784b..680cc61afe 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -283,14 +283,10 @@ extensibly associate specs with 'tagged' data (i.e. data where one of the fields indicates the shape of the rest of the structure). - The multimethod must use :clojure.spec/invalid as its default value - and should return nil from that dispatch value: - - (defmulti mspec :tag :default :clojure.spec/invalid) - (defmethod mspec :clojure.spec/invalid [_] nil) + (defmulti mspec :tag) The methods should ignore their argument and return a predicate/spec: - (defmethod mspec :int [_] (s/keys :req-un [::i])) + (defmethod mspec :int [_] (s/keys :req-un [::tag ::i])) retag is used during generation to retag generated values with matching tags. retag can either be a keyword, at which key the @@ -834,11 +830,11 @@ by ns-syms. Idempotent." "Do not call this directly, use 'multi-spec'" ([form mmvar retag] (multi-spec-impl form mmvar retag nil)) ([form mmvar retag gfn] - (assert (when-let [dm (-> (methods @mmvar) ::invalid)] - (nil? (dm nil))) - (str "Multimethod :" form " does not contain nil-returning default method for :clojure.spec/invalid" )) (let [id (java.util.UUID/randomUUID) - predx #(@mmvar %) + predx #(let [^clojure.lang.MultiFn mm @mmvar] + (c/and (contains? (methods mm) + ((.dispatchFn mm) %)) + (mm %))) dval #((.dispatchFn ^clojure.lang.MultiFn @mmvar) %) tag (if (keyword? retag) #(assoc %1 retag %2) @@ -856,7 +852,7 @@ by ns-syms. Idempotent." (if-let [pred (predx x)] (explain-1 form pred path via in x) {path {:pred form :val x :reason "no method" :via via :in in}}))) - (gen* [_ overrides path rmap] + (gen* [_ overrides path rmap] (if gfn (gfn) (let [gen (fn [[k f]] @@ -873,7 +869,7 @@ by ns-syms. Idempotent." (remove nil?))] (when (every? identity gs) (gen/one-of gs))))) - (with-gen* [_ gfn] (multi-spec-impl form mmvar retag gfn)) + (with-gen* [_ gfn] (multi-spec-impl form mmvar retag gfn)) (describe* [_] `(multi-spec ~form)))))) (defn ^:skip-wiki tuple-impl From c3abff893a8def21cb1dca969816dae83de9828b Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Wed, 25 May 2016 16:29:59 -0400 Subject: [PATCH 184/854] fix test reporting Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index ebe3dd88bc..de81c4de91 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -29,7 +29,8 @@ (assoc ret :result explain) ret)] (if-let [shrunk-explain (-> ret :shrunk :result ex-data ::check-call)] - (assoc-in ret [:shrunk :result] shrunk-explain)))) + (assoc-in ret [:shrunk :result] shrunk-explain) + ret))) (defn- check-call "Returns true if call passes specs, otherwise *returns* an exception From 575b0216fc016b481e49549b747de5988f9b455c Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 26 May 2016 09:16:15 -0400 Subject: [PATCH 185/854] tweak explain, added explain-str, improve s/+ explain --- src/clj/clojure/spec.clj | 14 ++++++++++---- test/clojure/test_clojure/spec.clj | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 680cc61afe..a1e1a60335 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -163,8 +163,9 @@ (print "In:" in "")) (print "val: ") (pr val) - (print " fails spec: ") - (print (c/or (last via) "_")) + (print " fails") + (when-not (empty? via) + (print " spec:" (last via))) (when-not (empty? path) (print " at:" path)) (print " predicate: ") @@ -187,6 +188,11 @@ [spec x] (explain-out (explain-data spec x))) +(defn explain-str + "Given a spec and a value that fails to conform, returns an explanation as a string." + [spec x] + (with-out-str (explain spec x))) + (declare valid?) (defn- gensub @@ -1155,7 +1161,7 @@ by ns-syms. Idempotent." ::amp (list* 'clojure.spec/& (op-describe p1) forms) ::pcat (if rep+ (list `+ rep+) - (cons `cat (mapcat vector ks forms))) + (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) (c/or (seq forms) (repeat nil))))) ::alt (cons `alt (mapcat vector ks forms)) ::rep (list (if splice `+ `*) forms))))) @@ -1264,7 +1270,7 @@ by ns-syms. Idempotent." (defn- re-explain [path via in re input] (loop [p re [x & xs :as data] input i 0] - ;;(prn {:p p :x x :xs xs}) (prn) + ;;(prn {:p p :x x :xs xs :re re}) (prn) (if (empty? data) (if (accept-nil? p) nil ;;success diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index af3dc71afe..5904ecfc6f 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -79,7 +79,7 @@ plus [] ::s/invalid '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}} plus [:k] [:k] nil plus [:k1 :k2] [:k1 :k2] nil - plus [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (cat), :val ("x") :via []}} + ;;plus [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (cat :_ (* keyword?)), :val (x), :via [], :in [2]}} plus ["a"] ::s/invalid '{[] {:pred keyword?, :val "a" :via []}} opt nil nil nil From 35dbe87984358ef4862e42b6f8e81fc0ec711553 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 26 May 2016 08:55:52 -0500 Subject: [PATCH 186/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..dd44261eb4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha3 http://clojure.org/ Clojure core environment and runtime library. From acd7c7a13719c8d8e7c2cc7eae7e8c93272f40d4 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 26 May 2016 08:55:52 -0500 Subject: [PATCH 187/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd44261eb4..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha3 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 0aa346766c4b065728cde9f9fcb4b2276a6fe7b5 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Sat, 28 May 2016 12:25:49 -0400 Subject: [PATCH 188/854] optimize seq (&) destructuring --- src/clj/clojure/core.clj | 132 ++++++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 59 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 32d04d8e96..81a6c5b1aa 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -4255,68 +4255,82 @@ ([& keyvals] (clojure.lang.PersistentArrayMap/createAsIfByAssoc (to-array keyvals)))) -;redefine let and loop with destructuring +;;redefine let and loop with destructuring (defn destructure [bindings] (let [bents (partition 2 bindings) pb (fn pb [bvec b v] - (let [pvec - (fn [bvec b val] - (let [gvec (gensym "vec__")] - (loop [ret (-> bvec (conj gvec) (conj val)) - n 0 - bs b - seen-rest? false] - (if (seq bs) - (let [firstb (first bs)] - (cond - (= firstb '&) (recur (pb ret (second bs) (list `nthnext gvec n)) - n - (nnext bs) - true) - (= firstb :as) (pb ret (second bs) gvec) - :else (if seen-rest? - (throw (new Exception "Unsupported binding form, only :as can follow & parameter")) - (recur (pb ret firstb (list `nth gvec n nil)) - (inc n) - (next bs) - seen-rest?)))) - ret)))) - pmap - (fn [bvec b v] - (let [gmap (gensym "map__") - gmapseq (with-meta gmap {:tag 'clojure.lang.ISeq}) - defaults (:or b)] - (loop [ret (-> bvec (conj gmap) (conj v) - (conj gmap) (conj `(if (seq? ~gmap) (clojure.lang.PersistentHashMap/create (seq ~gmapseq)) ~gmap)) - ((fn [ret] - (if (:as b) - (conj ret (:as b) gmap) - ret)))) - bes (reduce1 - (fn [bes entry] - (reduce1 #(assoc %1 %2 ((val entry) %2)) - (dissoc bes (key entry)) - ((key entry) bes))) - (dissoc b :as :or) - {:keys #(if (keyword? %) % (keyword (str %))), - :strs str, :syms #(list `quote %)})] - (if (seq bes) - (let [bb (key (first bes)) - bk (val (first bes)) - bv (if (contains? defaults bb) - (list `get gmap bk (defaults bb)) - (list `get gmap bk))] - (recur (cond - (symbol? bb) (-> ret (conj (if (namespace bb) (symbol (name bb)) bb)) (conj bv)) - (keyword? bb) (-> ret (conj (symbol (name bb)) bv)) - :else (pb ret bb bv)) - (next bes))) - ret))))] - (cond - (symbol? b) (-> bvec (conj b) (conj v)) - (vector? b) (pvec bvec b v) - (map? b) (pmap bvec b v) - :else (throw (new Exception (str "Unsupported binding form: " b)))))) + (let [pvec + (fn [bvec b val] + (let [gvec (gensym "vec__") + gseq (gensym "seq__") + gfirst (gensym "first__") + has-rest (some #{'&} b)] + (loop [ret (let [ret (conj bvec gvec val)] + (if has-rest + (conj ret gseq (list `seq gvec)) + ret)) + n 0 + bs b + seen-rest? false] + (if (seq bs) + (let [firstb (first bs)] + (cond + (= firstb '&) (recur (pb ret (second bs) gseq) + n + (nnext bs) + true) + (= firstb :as) (pb ret (second bs) gvec) + :else (if seen-rest? + (throw (new Exception "Unsupported binding form, only :as can follow & parameter")) + (recur (pb (if has-rest + (conj ret + gfirst `(first ~gseq) + gseq `(next ~gseq)) + ret) + firstb + (if has-rest + gfirst + (list `nth gvec n nil))) + (inc n) + (next bs) + seen-rest?)))) + ret)))) + pmap + (fn [bvec b v] + (let [gmap (gensym "map__") + gmapseq (with-meta gmap {:tag 'clojure.lang.ISeq}) + defaults (:or b)] + (loop [ret (-> bvec (conj gmap) (conj v) + (conj gmap) (conj `(if (seq? ~gmap) (clojure.lang.PersistentHashMap/create (seq ~gmapseq)) ~gmap)) + ((fn [ret] + (if (:as b) + (conj ret (:as b) gmap) + ret)))) + bes (reduce1 + (fn [bes entry] + (reduce1 #(assoc %1 %2 ((val entry) %2)) + (dissoc bes (key entry)) + ((key entry) bes))) + (dissoc b :as :or) + {:keys #(if (keyword? %) % (keyword (str %))), + :strs str, :syms #(list `quote %)})] + (if (seq bes) + (let [bb (key (first bes)) + bk (val (first bes)) + bv (if (contains? defaults bb) + (list `get gmap bk (defaults bb)) + (list `get gmap bk))] + (recur (cond + (symbol? bb) (-> ret (conj (if (namespace bb) (symbol (name bb)) bb)) (conj bv)) + (keyword? bb) (-> ret (conj (symbol (name bb)) bv)) + :else (pb ret bb bv)) + (next bes))) + ret))))] + (cond + (symbol? b) (-> bvec (conj b) (conj v)) + (vector? b) (pvec bvec b v) + (map? b) (pmap bvec b v) + :else (throw (new Exception (str "Unsupported binding form: " b)))))) process-entry (fn [bvec b] (pb bvec (first b) (second b)))] (if (every? symbol? (map first bents)) bindings From 53b02ef20c32386c4b4e23e1018e1a19140fee06 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Sat, 28 May 2016 12:36:03 -0400 Subject: [PATCH 189/854] improve update-in perf --- src/clj/clojure/core.clj | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 81a6c5b1aa..cb6255a1e8 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5958,10 +5958,13 @@ created." {:added "1.0" :static true} - ([m [k & ks] f & args] - (if ks - (assoc m k (apply update-in (get m k) ks f args)) - (assoc m k (apply f (get m k) args))))) + ([m ks f & args] + (let [up (fn up [m ks f args] + (let [[k & ks] ks] + (if ks + (assoc m k (up (get m k) ks f args)) + (assoc m k (apply f (get m k) args)))))] + (up m ks f args)))) (defn update "'Updates' a value in an associative structure, where k is a From b853e28d384146ed1344189aa20a2ddb108556eb Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 31 May 2016 10:43:27 -0400 Subject: [PATCH 190/854] fix describe empty cat --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index a1e1a60335..02c4675164 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1161,7 +1161,7 @@ by ns-syms. Idempotent." ::amp (list* 'clojure.spec/& (op-describe p1) forms) ::pcat (if rep+ (list `+ rep+) - (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) (c/or (seq forms) (repeat nil))))) + (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) forms))) ::alt (cons `alt (mapcat vector ks forms)) ::rep (list (if splice `+ `*) forms))))) From 5882d068262b5c46ad54dc12366093fb4f80f124 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 31 May 2016 09:54:05 -0500 Subject: [PATCH 191/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..93e70f23a9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha4 http://clojure.org/ Clojure core environment and runtime library. From 9051714f2b4623beb27b0d44dbeba7612f3182b1 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 31 May 2016 09:54:05 -0500 Subject: [PATCH 192/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 93e70f23a9..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha4 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 68fe71f0153bb6062754442c0a61c075b58fd9bc Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 1 Jun 2016 09:47:14 -0400 Subject: [PATCH 193/854] explain tail of single-pred cat --- src/clj/clojure/spec.clj | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 02c4675164..b3dfe468e3 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1168,8 +1168,8 @@ by ns-syms. Idempotent." (defn- op-explain [form p path via in input] ;;(prn {:form form :p p :path path :input input}) (let [[x :as input] input - via (if-let [name (spec-name p)] (conj via name) via) {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve p) + via (if-let [name (spec-name p)] (conj via name) via) insufficient (fn [path form] {path {:reason "Insufficient input" :pred (abbrev form) @@ -1189,13 +1189,13 @@ by ns-syms. Idempotent." (if-let [p1 (deriv p1 x)] (explain-pred-list forms ps path via in (preturn p1)) (op-explain (op-describe p1) p1 path via in input))) - ::pcat (let [[pred k form] (->> (map vector - ps - (c/or (seq ks) (repeat nil)) - (c/or (seq forms) (repeat nil))) - (remove (fn [[p]] - (accept-nil? p))) - first) + ::pcat (let [pkfs (map vector + ps + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil))) + [pred k form] (if (= 1 (count pkfs)) + (first pkfs) + (first (remove (fn [[p]] (accept-nil? p)) pkfs))) path (if k (conj path k) path) form (c/or form (op-describe pred))] (if (c/and (empty? input) (not pred)) @@ -1278,11 +1278,13 @@ by ns-syms. Idempotent." (if-let [dp (deriv p x)] (recur dp xs (inc i)) (if (accept? p) - {path {:reason "Extra input" - :pred (abbrev (op-describe re)) - :val data - :via via - :in (conj in i)}} + (if (= (::op p) ::pcat) + (op-explain (op-describe p) p path via (conj in i) (seq data)) + {path {:reason "Extra input" + :pred (abbrev (op-describe re)) + :val data + :via via + :in (conj in i)}}) (c/or (op-explain (op-describe p) p path via (conj in i) (seq data)) {path {:reason "Extra input" :pred (abbrev (op-describe p)) From 47b8d6b47a7c87272334c77878b92fd988448c41 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 3 Jun 2016 08:50:51 -0400 Subject: [PATCH 194/854] keywords are their own spec-names --- src/clj/clojure/spec.clj | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index b3dfe468e3..aab5381138 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -49,8 +49,11 @@ (with-meta spec (assoc (meta spec) ::name name))) (defn- spec-name [spec] - (when (instance? clojure.lang.IObj spec) - (-> (meta spec) ::name))) + (cond + (keyword? spec) spec + + (instance? clojure.lang.IObj spec) + (-> (meta spec) ::name))) (defn- reg-resolve "returns the spec/regex at end of alias chain starting with k, nil if not found, k if k not Named" From 71e5969e72a4cfcc52f06a8b393839e4c662b01c Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 3 Jun 2016 08:51:15 -0400 Subject: [PATCH 195/854] added canSeq --- src/jvm/clojure/lang/RT.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 6faad4c703..95e9a944ba 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -524,6 +524,7 @@ else if(coll instanceof LazySeq) return seqFrom(coll); } +// N.B. canSeq must be kept in sync with this! static ISeq seqFrom(Object coll){ if(coll instanceof Seqable) return ((Seqable) coll).seq(); @@ -544,6 +545,16 @@ else if(coll instanceof Map) } } +static public boolean canSeq(Object coll){ + return coll instanceof ISeq + || coll instanceof Seqable + || coll == null + || coll instanceof Iterable + || coll.getClass().isArray() + || coll instanceof CharSequence + || coll instanceof Map; +} + static public Iterator iter(Object coll){ if(coll instanceof Iterable) return ((Iterable)coll).iterator(); From 1f25347a7b219488d5d9f8d17b04f2cc7828b30e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 3 Jun 2016 10:27:10 -0400 Subject: [PATCH 196/854] throw IllegalStateException on missing gen --- src/clj/clojure/spec.clj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index aab5381138..3761d4f508 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -201,11 +201,10 @@ (defn- gensub [spec overrides path rmap form] ;;(prn {:spec spec :over overrides :path path :form form}) - (if-let [spec (specize spec)] + (let [spec (specize spec)] (if-let [g (c/or (get overrides path) (gen* spec overrides path rmap))] (gen/such-that #(valid? spec %) g 100) - (throw (Exception. (str "Unable to construct gen at: " path " for: " (abbrev form))))) - (throw (Exception. (str "Unable to construct gen at: " path ", " (abbrev form) " can not be made a spec"))))) + (throw (IllegalStateException. (str "Unable to construct gen at: " path " for: " (abbrev form))))))) (defn gen "Given a spec, returns the generator for it, or throws if none can From 080121d5de5f23d56025743572064ba1371ee4f3 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 6 Jun 2016 14:23:00 -0400 Subject: [PATCH 197/854] first cut of unform --- src/clj/clojure/spec.clj | 155 +++++++++++++++++++++-------- test/clojure/test_clojure/spec.clj | 6 +- 2 files changed, 114 insertions(+), 47 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 3761d4f508..90577286c7 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -36,6 +36,7 @@ (defprotocol Spec (conform* [spec x]) + (unform* [spec y]) (explain* [spec path via in x]) (gen* [spec overrides path rmap]) (with-gen* [spec gfn]) @@ -107,6 +108,13 @@ [spec x] (conform* (specize spec) x)) +(defn unform + "Given a spec and a value created by or compliant with a call to + 'conform' with the same spec, returns a value with all conform + destructuring undone." + [spec x] + (unform* (specize spec) x)) + (defn form "returns the spec as data" [spec] @@ -458,9 +466,10 @@ (defmacro conformer "takes a predicate function with the semantics of conform i.e. it should return either a (possibly converted) value or :clojure.spec/invalid, and returns a - spec that uses it as a predicate/conformer" - [f] - `(spec-impl '~f ~f nil true)) + spec that uses it as a predicate/conformer. Optionally takes a + second fn that does unform of result of first" + ([f] `(spec-impl '~f ~f nil true)) + ([f unf] `(spec-impl '~f ~f nil true ~unf))) (defmacro fspec "takes :args :ret and (optional) :fn kwargs whose values are preds @@ -767,6 +776,17 @@ by ns-syms. Idempotent." (recur ret ks)) ret))) ::invalid)) + (unform* [_ m] + (let [reg (registry)] + (loop [ret m, [k & ks :as keys] (c/keys m)] + (if keys + (if (contains? reg (keys->specs k)) + (let [cv (get m k) + v (unform (keys->specs k) cv)] + (recur (if (identical? cv v) ret (assoc ret k v)) + ks)) + (recur ret ks)) + ret)))) (explain* [_ path via in x] (if-not (map? x) {path {:pred 'map? :val x :via via :in in}} @@ -814,7 +834,8 @@ by ns-syms. Idempotent." (defn ^:skip-wiki spec-impl "Do not call this directly, use 'spec'" - [form pred gfn cpred?] + ([form pred gfn cpred?] (spec-impl form pred gfn cpred? nil)) + ([form pred gfn cpred? unc] (cond (spec? pred) (cond-> pred gfn (with-gen gfn)) (regex? pred) (regex-spec-impl pred gfn) @@ -825,14 +846,19 @@ by ns-syms. Idempotent." (invoke [this x] (valid? this x)) Spec (conform* [_ x] (dt pred x form cpred?)) + (unform* [_ x] (if cpred? + (if unc + (unc x) + (throw (IllegalStateException. "no unform fn for conformer"))) + x)) (explain* [_ path via in x] (when (= ::invalid (dt pred x form cpred?)) {path {:pred (abbrev form) :val x :via via :in in}})) - (gen* [_ _ _ _] (if gfn + (gen* [_ _ _ _] (if gfn (gfn) (gen/gen-for-pred pred))) - (with-gen* [_ gfn] (spec-impl form pred gfn cpred?)) - (describe* [_] form)))) + (with-gen* [_ gfn] (spec-impl form pred gfn cpred?)) + (describe* [_] form))))) (defn ^:skip-wiki multi-spec-impl "Do not call this directly, use 'multi-spec'" @@ -854,6 +880,9 @@ by ns-syms. Idempotent." (conform* [_ x] (if-let [pred (predx x)] (dt pred x form) ::invalid)) + (unform* [_ x] (if-let [pred (predx x)] + (unform pred x) + (throw (IllegalStateException. (str "No method of: " form " for dispatch value: " (dval x)))))) (explain* [_ path via in x] (let [dv (dval x) path (conj path dv)] @@ -901,6 +930,16 @@ by ns-syms. Idempotent." ::invalid (recur (if (identical? cv v) ret (assoc ret i cv)) (inc i)))))))) + (unform* [_ x] + (assert (c/and (vector? x) + (= (count x) (count preds)))) + (loop [ret x, i 0] + (if (= i (count x)) + ret + (let [cv (x i) + v (unform (preds i) cv)] + (recur (if (identical? cv v) ret (assoc ret i v)) + (inc i)))))) (explain* [_ path via in x] (cond (not (vector? x)) @@ -931,39 +970,41 @@ by ns-syms. Idempotent." (defn ^:skip-wiki or-spec-impl "Do not call this directly, use 'or'" [keys forms preds gfn] - (let [id (java.util.UUID/randomUUID) - cform (fn [x] - (loop [i 0] - (if (< i (count preds)) - (let [pred (preds i)] - (let [ret (dt pred x (nth forms i))] - (if (= ::invalid ret) - (recur (inc i)) - [(keys i) ret]))) - ::invalid)))] - (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) - Spec - (conform* [_ x] (cform x)) - (explain* [this path via in x] - (when-not (valid? this x) - (apply merge - (map (fn [k form pred] - (when-not (valid? pred x) - (explain-1 form pred (conj path k) via in x))) - keys forms preds)))) + (let [id (java.util.UUID/randomUUID) + kps (zipmap keys preds) + cform (fn [x] + (loop [i 0] + (if (< i (count preds)) + (let [pred (preds i)] + (let [ret (dt pred x (nth forms i))] + (if (= ::invalid ret) + (recur (inc i)) + [(keys i) ret]))) + ::invalid)))] + (reify + clojure.lang.IFn + (invoke [this x] (valid? this x)) + Spec + (conform* [_ x] (cform x)) + (unform* [_ [k x]] (unform (kps k) x)) + (explain* [this path via in x] + (when-not (valid? this x) + (apply merge + (map (fn [k form pred] + (when-not (valid? pred x) + (explain-1 form pred (conj path k) via in x))) + keys forms preds)))) (gen* [_ overrides path rmap] - (if gfn - (gfn) - (let [gen (fn [k p f] - (let [rmap (inck rmap id)] - (when-not (recur-limit? rmap id path k) - (gen/delay - (gensub p overrides (conj path k) rmap f))))) - gs (remove nil? (map gen keys preds forms))] - (when-not (empty? gs) - (gen/one-of gs))))) + (if gfn + (gfn) + (let [gen (fn [k p f] + (let [rmap (inck rmap id)] + (when-not (recur-limit? rmap id path k) + (gen/delay + (gensub p overrides (conj path k) rmap f))))) + gs (remove nil? (map gen keys preds forms))] + (when-not (empty? gs) + (gen/one-of gs))))) (with-gen* [_ gfn] (or-spec-impl keys forms preds gfn)) (describe* [_] `(or ~@(mapcat vector keys forms)))))) @@ -998,6 +1039,7 @@ by ns-syms. Idempotent." (invoke [this x] (valid? this x)) Spec (conform* [_ x] (and-preds x preds forms)) + (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) @@ -1082,7 +1124,7 @@ by ns-syms. Idempotent." (defn ^:skip-wiki maybe-impl "Do not call this directly, use '?'" - [p form] (alt* [p (accept ::nil)] nil [form ::nil])) + [p form] (assoc (alt* [p (accept ::nil)] nil [form ::nil]) :maybe form)) (defn- noret? [p1 pret] (c/or (= pret ::nil) @@ -1124,6 +1166,27 @@ by ns-syms. Idempotent." r (if (nil? p0) ::nil (preturn p0))] (if k0 [k0 r] r))))) +(defn- op-unform [p x] + ;;(prn {:p p :x x}) + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms rep+ maybe] :as p} (reg-resolve p) + kps (zipmap ks ps)] + (case op + ::accept [ret] + nil [(unform p x)] + ::amp (let [px (reduce #(unform %2 %1) x (reverse ps))] + (op-unform p1 px)) + ::rep (mapcat #(op-unform p1 %) x) + ::pcat (if rep+ + (mapcat #(op-unform p0 %) x) + (mapcat (fn [k] + (when (contains? x k) + (op-unform (kps k) (get x k)))) + ks)) + ::alt (if maybe + [(unform p0 x)] + (let [[k v] x] + (op-unform (kps k) v)))))) + (defn- add-ret [p r k] (let [{:keys [::op ps splice] :as p} (reg-resolve p) prop #(let [ret (preturn p)] @@ -1154,7 +1217,7 @@ by ns-syms. Idempotent." (when (accept-nil? p1) (deriv (rep* p2 p2 (add-ret p1 ret nil) splice forms) x))))))) (defn- op-describe [p] - (let [{:keys [::op ps ks forms splice p1 rep+] :as p} (reg-resolve p)] + (let [{:keys [::op ps ks forms splice p1 rep+ maybe] :as p} (reg-resolve p)] ;;(prn {:op op :ks ks :forms forms :p p}) (when p (case op @@ -1164,7 +1227,9 @@ by ns-syms. Idempotent." ::pcat (if rep+ (list `+ rep+) (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) forms))) - ::alt (cons `alt (mapcat vector ks forms)) + ::alt (if maybe + (list `? maybe) + (cons `alt (mapcat vector ks forms))) ::rep (list (if splice `+ `*) forms))))) (defn- op-explain [form p path via in input] @@ -1305,6 +1370,7 @@ by ns-syms. Idempotent." (if (c/or (nil? x) (coll? x)) (re-conform re (seq x)) ::invalid)) + (unform* [_ x] (op-unform re x)) (explain* [_ path via in x] (if (c/or (nil? x) (coll? x)) (re-explain path via in re (seq x)) @@ -1351,6 +1417,7 @@ by ns-syms. Idempotent." (conform* [_ f] (if (fn? f) (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) ::invalid)) + (unform* [_ f] f) (explain* [_ path via in f] (if (fn? f) (let [args (validate-fn f specs 100)] @@ -1380,7 +1447,7 @@ by ns-syms. Idempotent." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; non-primitives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (clojure.spec/def ::any (spec (constantly true) :gen gen/any)) -(clojure.spec/def ::kvs->map (conformer #(zipmap (map ::k %) (map ::v %)))) +(clojure.spec/def ::kvs->map (conformer #(zipmap (map ::k %) (map ::v %)) #(map (fn [[k v]] {::k k ::v v}) %))) (defmacro keys* "takes the same arguments as spec/keys and returns a regex op that matches sequences of key/values, @@ -1402,7 +1469,7 @@ by ns-syms. Idempotent." (defmacro nilable "returns a spec that accepts nil and values satisfiying pred" [pred] - `(and (or ::nil nil? ::pred ~pred) (conformer second))) + `(and (or ::nil nil? ::pred ~pred) (conformer second #(if (nil? %) [::nil nil] [::pred %])))) (defn exercise "generates a number (default 10) of values compatible with spec and maps conform over them, diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 5904ecfc6f..8c7d60cd42 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -84,10 +84,10 @@ opt nil nil nil opt [] nil nil - opt :k ::s/invalid '{[] {:pred (alt), :val :k, :via []}} + ;;opt :k ::s/invalid '{[] {:pred (alt), :val :k, :via []}} opt [:k] :k nil - opt [:k1 :k2] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2), :via []}} - opt [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2 "x"), :via []}} + ;;opt [:k1 :k2] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2), :via []}} + ;;opt [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2 "x"), :via []}} opt ["a"] ::s/invalid '{[] {:pred keyword?, :val "a", :via []}} andre nil nil nil From daf8056503196709a4f1aa315a5888841dacd322 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 7 Jun 2016 09:09:48 -0500 Subject: [PATCH 198/854] avoid printing Spec in doc when none exist Signed-off-by: Rich Hickey --- src/clj/clojure/repl.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index f38e2f4905..2e8c02a536 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -107,11 +107,11 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (println "Spec")) (when doc (println " " doc)) (when n - (when-let [specs (spec/fn-specs (symbol (str (ns-name n)) (name nm)))] + (when-let [specs (seq (remove (fn [[role spec]] (nil? spec)) + (spec/fn-specs (symbol (str (ns-name n)) (name nm)))))] (println "Spec") (run! (fn [[role spec]] - (when (and spec (not (= spec ::spec/unknown))) - (println " " (str (name role) ":") (spec/describe spec)))) + (println " " (str (name role) ":") (spec/describe spec))) specs)))) (defn find-doc From 58227c5de080110cb2ce5bc9f987d995a911b13e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 7 Jun 2016 10:31:38 -0500 Subject: [PATCH 199/854] new preds, specs, and gens Signed-off-by: Rich Hickey --- src/clj/clojure/core.clj | 119 +++++++++++++++++++++++ src/clj/clojure/spec.clj | 48 ++++++++- src/clj/clojure/spec/gen.clj | 30 +++++- test/clojure/test_clojure/predicates.clj | 32 ++++++ test/clojure/test_clojure/spec.clj | 33 +++++-- 5 files changed, 254 insertions(+), 8 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index cb6255a1e8..9cf7fc19af 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -516,6 +516,11 @@ :static true} [x] (clojure.lang.Util/identical x true)) +(defn boolean? + "Return true if x is a Boolean" + {:added "1.9"} + [x] (instance? Boolean x)) + (defn not "Returns true if x is logical false, false otherwise." {:tag Boolean @@ -1378,6 +1383,38 @@ :static true} [n] (not (even? n))) +(defn long? + "Return true if x is a Long" + {:added "1.9"} + [x] (instance? Long x)) + +(defn pos-long? + "Return true if x is a positive Long" + {:added "1.9"} + [x] (and (instance? Long x) + (pos? x))) + +(defn neg-long? + "Return true if x is a negative Long" + {:added "1.9"} + [x] (and (instance? Long x) + (neg? x))) + +(defn nat-long? + "Return true if x is a non-negative Long" + {:added "1.9"} + [x] (and (instance? Long x) + (not (neg? x)))) + +(defn double? + "Return true if x is a Double" + {:added "1.9"} + [x] (instance? Double x)) + +(defn bigdec? + "Return true if x is a BigDecimal" + {:added "1.9"} + [x] (instance? java.math.BigDecimal x)) ;; @@ -1553,6 +1590,41 @@ [^clojure.lang.Named x] (. x (getNamespace))) +(defn ident? + "Return true if x is a symbol or keyword" + {:added "1.9"} + [x] (or (keyword? x) (symbol? x))) + +(defn simple-ident? + "Return true if x is a symbol or keyword without a namespace" + {:added "1.9"} + [x] (and (ident? x) (nil? (namespace x)))) + +(defn qualified-ident? + "Return true if x is a symbol or keyword with a namespace" + {:added "1.9"} + [x] (and (ident? x) (namespace x) true)) + +(defn simple-symbol? + "Return true if x is a symbol without a namespace" + {:added "1.9"} + [x] (and (symbol? x) (nil? (namespace x)))) + +(defn qualified-symbol? + "Return true if x is a symbol with a namespace" + {:added "1.9"} + [x] (and (symbol? x) (namespace x) true)) + +(defn simple-keyword? + "Return true if x is a keyword without a namespace" + {:added "1.9"} + [x] (and (keyword? x) (nil? (namespace x)))) + +(defn qualified-keyword? + "Return true if x is a keyword with a namespace" + {:added "1.9"} + [x] (and (keyword? x) (namespace x) true)) + (defmacro locking "Executes exprs in an implicit do, while holding the monitor of x. Will release the monitor of x in all circumstances." @@ -5195,6 +5267,13 @@ {:added "1.0"} [xs] `(. clojure.lang.Numbers longs ~xs)) +(defn bytes? + "Return true if x is a byte array" + {:added "1.9"} + [x] (if (nil? x) + false + (-> x class .getComponentType (= Byte/TYPE)))) + (import '(java.util.concurrent BlockingQueue LinkedBlockingQueue)) (defn seque @@ -6003,6 +6082,11 @@ :static true} [x] (instance? clojure.lang.IPersistentList x)) +(defn seqable? + "Return true if the seq function is supported for x" + {:added "1.9"} + [x] (clojure.lang.RT/canSeq x)) + (defn ifn? "Returns true if x implements IFn. Note that many data structures (e.g. sets and maps) implement IFn" @@ -6047,6 +6131,11 @@ :static true} [coll] (instance? clojure.lang.Reversible coll)) +(defn indexed? + "Return true if coll implements Indexed, indicating efficient lookup by index" + {:added "1.9"} + [coll] (instance? clojure.lang.Indexed coll)) + (def ^:dynamic ^{:doc "bound in a repl thread to the most recent value printed" :added "1.0"} @@ -6539,8 +6628,33 @@ (load "core/protocols") (load "gvec") (load "instant") + +(defprotocol Inst + (inst-ms* [inst])) + +(extend-protocol Inst + java.util.Date + (inst-ms* [inst] (.getTime ^java.util.Date inst))) + +(defn inst-ms + "Return the number of milliseconds since January 1, 1970, 00:00:00 GMT" + {:added "1.9"} + [inst] + (inst-ms* inst)) + +(defn inst? + "Return true if x satisfies Inst" + {:added "1.9"} + [x] + (satisfies? Inst x)) + (load "uuid") +(defn uuid? + "Return true if x is a java.util.UUID" + {:added "1.9"} + [x] (instance? java.util.UUID x)) + (defn reduce "f should be a function of 2 arguments. If val is not supplied, returns the result of applying f to the first 2 items in coll, then @@ -7534,3 +7648,8 @@ (catch Throwable t (.printStackTrace t) (throw t))) + +(defn uri? + "Return true if x is a java.net.URI" + {:added "1.9"} + [x] (instance? java.net.URI x)) \ No newline at end of file diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 90577286c7..55d7ddff3f 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1511,4 +1511,50 @@ by ns-syms. Idempotent." [kpred vpred] `(and (coll-of (tuple ~kpred ~vpred) {}) map?)) - +(defn inst-in-range? + "Return true if inst at or after start and before end" + [start end inst] + (c/and (inst? inst) + (let [t (inst-ms inst)] + (c/and (<= (inst-ms start) t) (< t (inst-ms end)))))) + +(defmacro inst-in + "Returns a spec that validates insts in the range from start +(inclusive) to end (exclusive)." + [start end] + `(let [st# (inst-ms ~start) + et# (inst-ms ~end) + mkdate# (fn [d#] (java.util.Date. ^{:tag ~'long} d#))] + (spec (and inst? #(inst-in-range? ~start ~end %)) + :gen (fn [] + (gen/fmap mkdate# + (gen/large-integer* {:min st# :max et#})))))) + +(defn long-in-range? + "Return true if start <= val and val < end" + [start end val] + (c/and (long? val) (<= start val) (< val end))) + +(defmacro long-in + "Returns a spec that validates longs in the range from start +(inclusive) to end (exclusive)." + [start end] + `(spec (and c/long? #(long-in-range? ~start ~end %)) + :gen #(gen/large-integer* {:min ~start :max (dec ~end)}))) + +(defmacro double-in + "Specs a 64-bit floating point number. Options: + + :infinite? - whether +/- infinity allowed (default true) + :NaN? - whether NaN allowed (default true) + :min - minimum value (inclusive, default none) + :max - maximum value (inclusive, default none)" + [& {:keys [infinite? NaN? min max] + :or {infinite? true NaN? true} + :as m}] + `(spec (and c/double? + ~@(when-not infinite? '[#(not (Double/isInfinite %))]) + ~@(when-not NaN? '[#(not (Double/isNaN %))]) + ~@(when max `[#(<= % ~max)]) + ~@(when min `[#(<= ~min %)])) + :gen #(gen/double* ~m))) \ No newline at end of file diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index a5ef475f8d..ecb1897b8e 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -90,7 +90,8 @@ syms))) (lazy-combinators hash-map list map not-empty set vector fmap elements - bind choose fmap one-of such-that tuple sample return) + bind choose fmap one-of such-that tuple sample return + large-integer* double*) (defmacro ^:skip-wiki lazy-prim "Implementation macro, do not call directly." @@ -122,16 +123,43 @@ gens, each of which should generate something sequential." (fmap #(apply concat %) (apply tuple gens))) +(defn- qualified? [ident] (not (nil? (namespace ident)))) + (def ^:private gen-builtins (c/delay (let [simple (simple-type-printable)] {number? (one-of [(large-integer) (double)]) integer? (large-integer) + long? (large-integer) + pos-long? (large-integer* {:min 1}) + neg-long? (large-integer* {:max -1}) + nat-long? (large-integer* {:min 0}) float? (double) + double? (double) + boolean? (boolean) string? (string-alphanumeric) + ident? (one-of [(keyword-ns) (symbol-ns)]) + simple-ident? (one-of [(keyword) (symbol)]) + qualified-ident? (such-that qualified? (one-of [(keyword-ns) (symbol-ns)])) keyword? (keyword-ns) + simple-keyword? (keyword) + qualified-keyword? (such-that qualified? (keyword-ns)) symbol? (symbol-ns) + simple-symbol? (symbol) + qualified-symbol? (such-that qualified? (symbol-ns)) + uuid? (uuid) + bigdec? (fmap #(BigDecimal/valueOf %) + (double* {:infinite? false :NaN? false})) + inst? (fmap #(java.util.Date. %) + (large-integer)) + seqable? (one-of [(return nil) + (list simple) + (vector simple) + (map simple simple) + (set simple) + (string-alphanumeric)]) + indexed? (vector simple) map? (map simple simple) vector? (vector simple) list? (list simple) diff --git a/test/clojure/test_clojure/predicates.clj b/test/clojure/test_clojure/predicates.clj index 2923ef3d47..150f6a2769 100644 --- a/test/clojure/test_clojure/predicates.clj +++ b/test/clojure/test_clojure/predicates.clj @@ -140,3 +140,35 @@ (are [x] (not (string? x)) (new java.lang.StringBuilder "abc") (new java.lang.StringBuffer "xyz"))) + +(def pred-val-table + (let [now (java.util.Date.) + uuid (java.util.UUID/randomUUID) + barray (byte-array 0) + uri (java.net.URI. "http://clojure.org")] + [' + [identity long? pos-long? neg-long? nat-long? double? boolean? indexed? seqable? ident? uuid? bigdec? inst? uri? bytes?] + [0 true false false true false false false false false false false false false false] + [1 true true false true false false false false false false false false false false] + [-1 true false true false false false false false false false false false false false] + [1.0 false false false false true false false false false false false false false false] + [true false false false false false true false false false false false false false false] + [[] false false false false false false true true false false false false false false] + [nil false false false false false false false true false false false false false false] + [{} false false false false false false false true false false false false false false] + [:foo false false false false false false false false true false false false false false] + ['foo false false false false false false false false true false false false false false] + [0.0M false false false false false false false false false false true false false false] + [0N false false false false false false false false false false false false false false] + [uuid false false false false false false false false false true false false false false] + [uri false false false false false false false false false false false false true false] + [now false false false false false false false false false false false true false false] + [barray false false false false false false false true false false false false false true]])) + +(deftest test-preds + (let [[preds & rows] pred-val-table] + (doseq [row rows] + (let [v (first row)] + (dotimes [i (count row)] + (is (= ((resolve (nth preds i)) v) (nth row i)) + (pr-str (list (nth preds i) v)))))))) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 8c7d60cd42..264e117348 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -33,7 +33,11 @@ opt (s/? keyword?) andre (s/& (s/* keyword?) even-count?) m (s/map-of keyword? string?) - coll (s/coll-of keyword? [])] + coll (s/coll-of keyword? []) + lrange (s/long-in 7 42) + drange (s/double-in :infinite? false :NaN? false :min 3.1 :max 3.2) + irange (s/inst-in #inst "1939" #inst "1946") + ] (are [spec x conformed ed] (let [co (result-or-ex (s/conform spec x)) e (result-or-ex (::s/problems (s/explain-data spec x)))] @@ -41,6 +45,21 @@ (when (not (submap? ed e)) (println "explain fail\n\texpect=" ed "\n\tactual=" e)) (and (= conformed co) (submap? ed e))) + lrange 7 7 nil + lrange 8 8 nil + lrange 42 ::s/invalid {[] {:pred '(long-in-range? 7 42 %), :val 42, :via [], :in []}} + + irange #inst "1938" ::s/invalid {[] {:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1938", :via [], :in []}} + irange #inst "1942" #inst "1942" nil + irange #inst "1946" ::s/invalid {[] {:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1946", :via [], :in []}} + + drange 3.0 ::s/invalid {[] {:pred '(<= 3.1 %), :val 3.0, :via [], :in []}} + drange 3.1 3.1 nil + drange 3.2 3.2 nil + drange Double/POSITIVE_INFINITY ::s/invalid {[] {:pred '(not (isInfinite %)), :val Double/POSITIVE_INFINITY, :via [], :in []}} + ;; can't use equality-based test for Double/NaN + ;; drange Double/NaN ::s/invalid {[] {:pred '(not (isNaN %)), :val Double/NaN, :via [], :in []}} + keyword? :k :k nil keyword? nil ::s/invalid {[] {:pred ::s/unknown :val nil :via []}} keyword? "abc" ::s/invalid {[] {:pred ::s/unknown :val "abc" :via []}} @@ -61,6 +80,8 @@ c ["a"] ::s/invalid '{[:b] {:reason "Insufficient input", :pred keyword?, :val (), :via []}} c ["s" :k] '{:a "s" :b :k} nil c ["s" :k 5] ::s/invalid '{[] {:reason "Extra input", :pred (cat :a string? :b keyword?), :val (5), :via []}} + (s/cat) nil {} nil + (s/cat) [5] ::s/invalid '{[] {:reason "Extra input", :pred (cat), :val (5), :via [], :in [0]}} either nil ::s/invalid '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}} either [] ::s/invalid '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}} @@ -79,15 +100,15 @@ plus [] ::s/invalid '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}} plus [:k] [:k] nil plus [:k1 :k2] [:k1 :k2] nil - ;;plus [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (cat :_ (* keyword?)), :val (x), :via [], :in [2]}} + plus [:k1 :k2 "x"] ::s/invalid '{[] {:pred keyword?, :val "x", :via [], :in [2]}} plus ["a"] ::s/invalid '{[] {:pred keyword?, :val "a" :via []}} opt nil nil nil opt [] nil nil - ;;opt :k ::s/invalid '{[] {:pred (alt), :val :k, :via []}} + opt :k ::s/invalid '{[] {:pred (? keyword?), :val :k, :via []}} opt [:k] :k nil - ;;opt [:k1 :k2] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2), :via []}} - ;;opt [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (alt), :val (:k2 "x"), :via []}} + opt [:k1 :k2] ::s/invalid '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2), :via []}} + opt [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2 "x"), :via []}} opt ["a"] ::s/invalid '{[] {:pred keyword?, :val "a", :via []}} andre nil nil nil @@ -180,7 +201,7 @@ its spec for test purposes." (comment (require '[clojure.test :refer (run-tests)]) - (in-ns 'test-clojure.spec) + (in-ns 'clojure.test-clojure.spec) (run-tests) (stest/run-all-tests) From cfc32100db5f54009a1b8b589145b99cef8d7bb9 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 7 Jun 2016 12:08:40 -0500 Subject: [PATCH 200/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..979b40766e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha5 http://clojure.org/ Clojure core environment and runtime library. From 0ef22d85189e108cea0fc5332c8c5eeb7fd79652 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 7 Jun 2016 12:08:40 -0500 Subject: [PATCH 201/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 979b40766e..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha5 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From b7199fb338b7788d5d61763525ab82029174592e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 8 Jun 2016 15:56:55 -0400 Subject: [PATCH 202/854] fail fast & when accept re and failed secondary preds --- src/clj/clojure/spec.clj | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 55d7ddff3f..5645285af5 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -461,7 +461,7 @@ conjunction of the predicates, and any conforming they might perform." [re & preds] (let [pv (vec preds)] - `(amp-impl ~re ~pv '~pv))) + `(amp-impl ~re ~pv '~(mapv res pv)))) (defmacro conformer "takes a predicate function with the semantics of conform i.e. it should return either a @@ -1142,9 +1142,7 @@ by ns-syms. Idempotent." ::amp (c/and (accept-nil? p1) (c/or (noret? p1 (preturn p1)) (let [ret (-> (preturn p1) (and-preds ps (next forms)))] - (if (= ret ::invalid) - nil - ret)))) + (not= ret ::invalid)))) ::rep (c/or (identical? p1 p2) (accept-nil? p1)) ::pcat (every? accept-nil? ps) ::alt (c/some accept-nil? ps)))) @@ -1209,7 +1207,11 @@ by ns-syms. Idempotent." nil (let [ret (dt p x p)] (when-not (= ::invalid ret) (accept ret))) ::amp (when-let [p1 (deriv p1 x)] - (amp-impl p1 ps forms)) + (if (= ::accept (::op p1)) + (let [ret (-> (preturn p1) (and-preds ps (next forms)))] + (when-not (= ret ::invalid) + (accept ret))) + (amp-impl p1 ps forms))) ::pcat (alt2 (pcat* {:ps (cons (deriv p0 x) pr), :ks ks, :forms forms, :ret ret}) (when (accept-nil? p0) (deriv (pcat* {:ps pr, :ks kr, :forms (next forms), :ret (add-ret p0 ret k0)}) x))) ::alt (alt* (map #(deriv % x) ps) ks forms) From 0bc837b9c25ae62185795b2bf2c7952bf6e12d9e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 10 Jun 2016 17:02:12 -0400 Subject: [PATCH 203/854] add :args/:ret/:fn accessors for fspecs --- src/clj/clojure/spec.clj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 5645285af5..f94a1236a2 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1415,6 +1415,16 @@ by ns-syms. Idempotent." (reify clojure.lang.IFn (invoke [this x] (valid? this x)) + + clojure.lang.ILookup + (valAt [this k] (.valAt this k nil)) + (valAt [_ k not-found] + (case k + :args argspec + :ret retspec + :fn fnspec + not-found)) + Spec (conform* [_ f] (if (fn? f) (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) From f571c4bb05e82f8a13d557bfde89f50026a3570d Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Sat, 11 Jun 2016 10:38:40 -0400 Subject: [PATCH 204/854] tagged returns from or/alt are now map entries, support 'key' and 'val' --- src/clj/clojure/spec.clj | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index f94a1236a2..d35e954510 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -385,9 +385,10 @@ (s/or :even even? :small #(< % 42)) - Returns a destructuring spec that - returns a vector containing the key of the first matching pred and the - corresponding value." + Returns a destructuring spec that returns a map entry containing the + key of the first matching pred and the corresponding value. Thus the + 'key' and 'val' functions can be used to refer generically to the + components of the tagged return." [& key-pred-forms] (let [pairs (partition 2 key-pred-forms) keys (mapv first pairs) @@ -429,8 +430,10 @@ (s/alt :even even? :small #(< % 42)) - Returns a regex op that returns a vector containing the key of the - first matching pred and the corresponding value." + Returns a regex op that returns a map entry containing the key of the + first matching pred and the corresponding value. Thus the + 'key' and 'val' functions can be used to refer generically to the + components of the tagged return" [& key-pred-forms] (let [pairs (partition 2 key-pred-forms) keys (mapv first pairs) @@ -966,6 +969,8 @@ by ns-syms. Idempotent." (with-gen* [_ gfn] (tuple-impl forms preds gfn)) (describe* [_] `(tuple ~@forms))))) +(defn- tagged-ret [tag ret] + (clojure.lang.MapEntry. tag ret)) (defn ^:skip-wiki or-spec-impl "Do not call this directly, use 'or'" @@ -979,7 +984,7 @@ by ns-syms. Idempotent." (let [ret (dt pred x (nth forms i))] (if (= ::invalid ret) (recur (inc i)) - [(keys i) ret]))) + (tagged-ret (keys i) ret)))) ::invalid)))] (reify clojure.lang.IFn @@ -1110,7 +1115,7 @@ by ns-syms. Idempotent." (if (nil? pr) (if k1 (if (accept? p1) - (accept [k1 (:ret p1)]) + (accept (tagged-ret k1 (:ret p1))) ret) p1) ret))))) @@ -1162,7 +1167,7 @@ by ns-syms. Idempotent." ::pcat (add-ret p0 ret k) ::alt (let [[[p0] [k0]] (filter-alt ps ks forms accept-nil?) r (if (nil? p0) ::nil (preturn p0))] - (if k0 [k0 r] r))))) + (if k0 (tagged-ret k0 r) r))))) (defn- op-unform [p x] ;;(prn {:p p :x x}) From 43e1c7f3b9df408b8cb79d4b4dfbeb780aabb846 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Sat, 11 Jun 2016 11:11:51 -0400 Subject: [PATCH 205/854] use throwing resolve in regex ops --- src/clj/clojure/spec.clj | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index d35e954510..b2a194be88 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -68,6 +68,14 @@ (with-name spec k))))) k)) +(defn- reg-resolve! + "returns the spec/regex at end of alias chain starting with k, throws if not found, k if k not ident" + [k] + (if (ident? k) + (c/or (reg-resolve k) + (throw (Exception. (str "Unable to resolve spec: " k)))) + k)) + (defn spec? "returns x if x is a spec object, else logical false" [x] @@ -1133,14 +1141,14 @@ by ns-syms. Idempotent." (defn- noret? [p1 pret] (c/or (= pret ::nil) - (c/and (#{::rep ::pcat} (::op (reg-resolve p1))) ;;hrm, shouldn't know these + (c/and (#{::rep ::pcat} (::op (reg-resolve! p1))) ;;hrm, shouldn't know these (empty? pret)) nil)) (declare preturn) (defn- accept-nil? [p] - (let [{:keys [::op ps p1 p2 forms] :as p} (reg-resolve p)] + (let [{:keys [::op ps p1 p2 forms] :as p} (reg-resolve! p)] (case op ::accept true nil nil @@ -1155,7 +1163,7 @@ by ns-syms. Idempotent." (declare add-ret) (defn- preturn [p] - (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms] :as p} (reg-resolve p)] + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms] :as p} (reg-resolve! p)] (case op ::accept ret nil nil @@ -1171,7 +1179,7 @@ by ns-syms. Idempotent." (defn- op-unform [p x] ;;(prn {:p p :x x}) - (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms rep+ maybe] :as p} (reg-resolve p) + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms rep+ maybe] :as p} (reg-resolve! p) kps (zipmap ks ps)] (case op ::accept [ret] @@ -1191,7 +1199,7 @@ by ns-syms. Idempotent." (op-unform (kps k) v)))))) (defn- add-ret [p r k] - (let [{:keys [::op ps splice] :as p} (reg-resolve p) + (let [{:keys [::op ps splice] :as p} (reg-resolve! p) prop #(let [ret (preturn p)] (if (empty? ret) r ((if splice into conj) r (if k {k ret} ret))))] (case op @@ -1205,7 +1213,7 @@ by ns-syms. Idempotent." (defn- deriv [p x] - (let [{[p0 & pr :as ps] :ps, [k0 & kr :as ks] :ks, :keys [::op p1 p2 ret splice forms] :as p} (reg-resolve p)] + (let [{[p0 & pr :as ps] :ps, [k0 & kr :as ks] :ks, :keys [::op p1 p2 ret splice forms] :as p} (reg-resolve! p)] (when p (case op ::accept nil @@ -1224,7 +1232,7 @@ by ns-syms. Idempotent." (when (accept-nil? p1) (deriv (rep* p2 p2 (add-ret p1 ret nil) splice forms) x))))))) (defn- op-describe [p] - (let [{:keys [::op ps ks forms splice p1 rep+ maybe] :as p} (reg-resolve p)] + (let [{:keys [::op ps ks forms splice p1 rep+ maybe] :as p} (reg-resolve! p)] ;;(prn {:op op :ks ks :forms forms :p p}) (when p (case op @@ -1242,7 +1250,7 @@ by ns-syms. Idempotent." (defn- op-explain [form p path via in input] ;;(prn {:form form :p p :path path :input input}) (let [[x :as input] input - {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve p) + {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve! p) via (if-let [name (spec-name p)] (conj via name) via) insufficient (fn [path form] {path {:reason "Insufficient input" @@ -1295,7 +1303,7 @@ by ns-syms. Idempotent." (defn- re-gen [p overrides path rmap f] ;;(prn {:op op :ks ks :forms forms}) - (let [{:keys [::op ps ks p1 p2 forms splice ret id] :as p} (reg-resolve p) + (let [{:keys [::op ps ks p1 p2 forms splice ret id] :as p} (reg-resolve! p) rmap (if id (inck rmap id) rmap) ggens (fn [ps ks forms] (let [gen (fn [p k f] From 84c16fd9faa6f9a48c292c86d2f7da8b88ff9dec Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 13 Jun 2016 12:48:05 -0400 Subject: [PATCH 206/854] emit keys in fspec describe --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index b2a194be88..356a6f4ce8 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1468,7 +1468,7 @@ by ns-syms. Idempotent." (assert (valid? argspec args) (with-out-str (explain argspec args))) (gen/generate (gen retspec))))))) (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) - (describe* [_] `(fspec ~aform ~rform ~fform))))) + (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; non-primitives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (clojure.spec/def ::any (spec (constantly true) :gen gen/any)) From 69dd29d2c8c1593bb283f9bfcd47b1ec280520e9 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 13 Jun 2016 13:20:20 -0400 Subject: [PATCH 207/854] spec-ify predicates entering fspec --- src/clj/clojure/spec.clj | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 356a6f4ce8..75b102fdda 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -297,7 +297,8 @@ Returns a spec." [form & {:keys [gen]}] - `(spec-impl '~(res form) ~form ~gen nil)) + (when form + `(spec-impl '~(res form) ~form ~gen nil))) (defmacro multi-spec "Takes the name of a spec/predicate-returning multimethod and a @@ -490,7 +491,9 @@ Optionally takes :gen generator-fn, which must be a fn of no args that returns a test.check generator." [& {:keys [args ret fn gen]}] - `(fspec-impl ~args '~(res args) ~ret '~(res ret) ~fn '~(res fn) ~gen)) + `(fspec-impl (spec ~args) '~(res args) + (spec ~ret) '~(res ret) + (spec ~fn) '~(res fn) ~gen)) (defmacro tuple "takes one or more preds and returns a spec for a tuple, a vector @@ -1423,20 +1426,14 @@ by ns-syms. Idempotent." (defn ^:skip-wiki fspec-impl "Do not call this directly, use 'fspec'" [argspec aform retspec rform fnspec fform gfn] - (assert (c/and argspec retspec)) (let [specs {:args argspec :ret retspec :fn fnspec}] (reify clojure.lang.IFn (invoke [this x] (valid? this x)) clojure.lang.ILookup - (valAt [this k] (.valAt this k nil)) - (valAt [_ k not-found] - (case k - :args argspec - :ret retspec - :fn fnspec - not-found)) + (valAt [this k] (get specs k)) + (valAt [_ k not-found] (get specs k not-found)) Spec (conform* [_ f] (if (fn? f) From 92df7b2a72dad83a901f86c1a9ec8fbc5dc1d1c7 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 13 Jun 2016 12:58:50 -0500 Subject: [PATCH 208/854] fdef registers single fspec. fn-specs -> fn-spec. Signed-off-by: Rich Hickey --- src/clj/clojure/repl.clj | 9 +++--- src/clj/clojure/spec.clj | 52 +++++++++++------------------------ src/clj/clojure/spec/test.clj | 4 +-- 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index 2e8c02a536..b59987ca83 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -107,12 +107,11 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (println "Spec")) (when doc (println " " doc)) (when n - (when-let [specs (seq (remove (fn [[role spec]] (nil? spec)) - (spec/fn-specs (symbol (str (ns-name n)) (name nm)))))] + (when-let [fnspec (spec/fn-spec (symbol (str (ns-name n)) (name nm)))] (println "Spec") - (run! (fn [[role spec]] - (println " " (str (name role) ":") (spec/describe spec))) - specs)))) + (doseq [role [:args :ret :fn]] + (when-let [spec (get fnspec role)] + (println " " (str (name role) ":") (spec/describe spec))))))) (defn find-doc "Prints documentation for any var whose documentation or name diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 75b102fdda..53ed0640b2 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -513,32 +513,20 @@ s (symbol (str (.name *ns*)) (str s))))) -(defn- fn-spec-sym - [sym role] - (symbol (str (ns-qualify sym) "$" (name role)))) - -(def ^:private fn-spec-roles [:args :ret :fn]) - (defn- expect "Returns nil if v conforms to spec, else throws ex-info with explain-data." [spec v] ) -(defn- fn-specs? - "Fn-specs must include at least :args or :ret specs." +(defn- fn-spec? + "Fn-spec must include at least :args or :ret specs." [m] (c/or (:args m) (:ret m))) -(defn fn-specs - "Returns :args/:ret/:fn map of specs for var or symbol v." +(defn fn-spec + "Returns fspec of specs for var or symbol v, or nil." [v] - (let [s (->sym v) - reg (registry)] - (reduce - (fn [m role] - (assoc m role (get reg (fn-spec-sym s role)))) - {} - fn-spec-roles))) + (get (registry) (->sym v))) (defmacro with-instrument-disabled "Disables instrument's checking of calls, within a scope." @@ -561,7 +549,7 @@ [& args] (if *instrument-enabled* (with-instrument-disabled - (let [specs (fn-specs v)] + (let [specs (fn-spec v)] (let [cargs (when (:args specs) (conform! v :args (:args specs) args args)) ret (binding [*instrument-enabled* true] (.applyTo ^clojure.lang.IFn f args)) @@ -573,7 +561,7 @@ (defn- macroexpand-check [v args] - (let [specs (fn-specs v)] + (let [specs (fn-spec v)] (when-let [arg-spec (:args specs)] (when (= ::invalid (conform arg-spec args)) (let [ed (assoc (explain-data* arg-spec [:args] @@ -597,7 +585,7 @@ Qualifies fn-sym with resolve, or using *ns* if no resolution found. Registers specs in the global registry, where they can be retrieved - by calling fn-specs. + by calling fn-spec. Once registered, function specs are included in doc, checked by instrument, tested by the runner clojure.spec.test/run-tests, and (if @@ -616,18 +604,11 @@ :str string? :sym symbol?) :ret symbol?)" - [fn-sym & {:keys [args ret fn] :as m}] + [fn-sym & specs] (let [qn (ns-qualify fn-sym)] - `(do ~@(reduce - (c/fn [defns role] - (if (contains? m role) - (let [s (fn-spec-sym qn (name role))] - (conj defns `(clojure.spec/def '~s ~(get m role)))) - defns)) - [] [:args :ret :fn]) - '~qn))) - -(defn- no-fn-specs + `(clojure.spec/def '~qn (clojure.spec/fspec ~@specs)))) + +(defn- no-fn-spec [v specs] (ex-info (str "Fn at " v " is not spec'ed.") {:var v :specs specs})) @@ -652,8 +633,8 @@ specs, if they exist, throwing an ex-info with explain-data if a check fails. Idempotent." [v] (let [v (->var v) - specs (fn-specs v)] - (if (fn-specs? specs) + spec (fn-spec v)] + (if (fn-spec? spec) (locking instrumented-vars (let [{:keys [raw wrapped]} (get @instrumented-vars v) current @v] @@ -662,7 +643,7 @@ check fails. Idempotent." (alter-var-root v (constantly checked)) (swap! instrumented-vars assoc v {:raw current :wrapped checked})))) v) - (throw (no-fn-specs v specs))))) + (throw (no-fn-spec v spec))))) (defn unstrument "Undoes instrument on the var at v, a var or symbol. Idempotent." @@ -687,9 +668,8 @@ specified, return speced vars from all namespaces." (reduce-kv (fn [s k _] (if (c/and (symbol? k) - (re-find #"\$(args|ret)$" (name k)) (ns-match? (namespace k))) - (if-let [v (resolve (symbol (str/replace (str k) #"\$(args|ret)$" "")))] + (if-let [v (resolve k)] (conj s v) s) s)) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index de81c4de91..02ed804925 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -74,7 +74,7 @@ passed through to test.check/quick-check: Returns a map as quick-check, with :explain-data added if :result is false." [v & opts] - (let [specs (spec/fn-specs v)] + (let [specs (spec/fn-spec v)] (if (:args specs) (apply check-fn @v specs opts) (throw (IllegalArgumentException. (str "No :args spec for " v)))))) @@ -102,7 +102,7 @@ Returns a map as quick-check, with :explain-data added if [& ns-syms] (if (seq ns-syms) (run-var-tests (->> (apply spec/speced-vars ns-syms) - (filter (fn [v] (:args (spec/fn-specs v)))))) + (filter (fn [v] (:args (spec/fn-spec v)))))) (run-tests (.name ^clojure.lang.Namespace *ns*)))) (defn run-all-tests From 30dd3d8554ff96f1acda7cbe31470d92df2f565a Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 14 Jun 2016 08:58:25 -0500 Subject: [PATCH 209/854] Instrument checks only :args spec Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 53ed0640b2..4dd5731703 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -550,13 +550,9 @@ (if *instrument-enabled* (with-instrument-disabled (let [specs (fn-spec v)] - (let [cargs (when (:args specs) (conform! v :args (:args specs) args args)) - ret (binding [*instrument-enabled* true] - (.applyTo ^clojure.lang.IFn f args)) - cret (when (:ret specs) (conform! v :ret (:ret specs) ret args))] - (when (c/and (:args specs) (:ret specs) (:fn specs)) - (conform! v :fn (:fn specs) {:args cargs :ret cret} args)) - ret))) + (when (:args specs) (conform! v :args (:args specs) args args)) + (binding [*instrument-enabled* true] + (.applyTo ^clojure.lang.IFn f args)))) (.applyTo ^clojure.lang.IFn f args))))) (defn- macroexpand-check @@ -628,8 +624,8 @@ (defn instrument "Instruments the var at v, a var or symbol, to check specs -registered with fdef. Wraps the fn at v to check :args/:ret/:fn -specs, if they exist, throwing an ex-info with explain-data if a +registered with fdef. Wraps the fn at v to check the :args +spec, if it exists, throwing an ex-info with explain-data if a check fails. Idempotent." [v] (let [v (->var v) From 544d01d0f9657f6d5bb43b4cd29bc0ffec104fa7 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 14 Jun 2016 11:45:33 -0400 Subject: [PATCH 210/854] fspecs accept ifns --- src/clj/clojure/spec.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 4dd5731703..c515c58a75 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1412,12 +1412,12 @@ by ns-syms. Idempotent." (valAt [_ k not-found] (get specs k not-found)) Spec - (conform* [_ f] (if (fn? f) + (conform* [_ f] (if (ifn? f) (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) ::invalid)) (unform* [_ f] f) (explain* [_ path via in f] - (if (fn? f) + (if (ifn? f) (let [args (validate-fn f specs 100)] (if (identical? f args) ;;hrm, we might not be able to reproduce nil @@ -1432,7 +1432,7 @@ by ns-syms. Idempotent." (when fnspec (let [cargs (conform argspec args)] (explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret}))))))))) - {path {:pred 'fn? :val f :via via :in in}})) + {path {:pred 'ifn? :val f :via via :in in}})) (gen* [_ _ _ _] (if gfn (gfn) (when-not fnspec From 1c44c559ea79695e003a77be056cfd49ec15ec2e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Sat, 11 Jun 2016 09:13:19 -0500 Subject: [PATCH 211/854] CLJ-1957 Add generator for bytes? Signed-off-by: Rich Hickey --- src/clj/clojure/spec/gen.clj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index ecb1897b8e..19d802ff89 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -7,7 +7,7 @@ ; You must not remove this notice, or any other, from this software. (ns clojure.spec.gen - (:refer-clojure :exclude [boolean cat hash-map list map not-empty set vector + (:refer-clojure :exclude [boolean bytes cat hash-map list map not-empty set vector char double int keyword symbol string uuid delay])) (alias 'c 'clojure.core) @@ -112,7 +112,7 @@ (fn [s] (c/list 'lazy-prim s)) syms))) -(lazy-prims any any-printable boolean char char-alpha char-alphanumeric char-ascii double +(lazy-prims any any-printable boolean bytes char char-alpha char-alphanumeric char-ascii double int keyword keyword-ns large-integer ratio simple-type simple-type-printable string string-ascii string-alphanumeric symbol symbol-ns uuid) @@ -178,7 +178,8 @@ gens, each of which should generate something sequential." empty? (elements [nil '() [] {} #{}]) associative? (one-of [(map simple simple) (vector simple)]) sequential? (one-of [(list simple) (vector simple)]) - ratio? (such-that ratio? (ratio))}))) + ratio? (such-that ratio? (ratio)) + bytes? (bytes)}))) (defn gen-for-pred "Given a predicate, returns a built-in generator if one exists." From 1109dd4eafd95c169add38dd0051be413d9e49d0 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Sun, 12 Jun 2016 02:22:01 -0500 Subject: [PATCH 212/854] CLJ-1958 generator for uri? Signed-off-by: Rich Hickey --- src/clj/clojure/spec/gen.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index 19d802ff89..1ea343f517 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -149,6 +149,7 @@ gens, each of which should generate something sequential." simple-symbol? (symbol) qualified-symbol? (such-that qualified? (symbol-ns)) uuid? (uuid) + uri? (fmap #(java.net.URI/create (str "http://" % ".com")) (uuid)) bigdec? (fmap #(BigDecimal/valueOf %) (double* {:infinite? false :NaN? false})) inst? (fmap #(java.util.Date. %) From 5f21ca96a0810a5a51a25c580eaf1f78ac809aac Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 14 Jun 2016 11:45:01 -0500 Subject: [PATCH 213/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..4ed76e38bd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha6 http://clojure.org/ Clojure core environment and runtime library. From 4f8593bac44fb303d8f0e56f9ee03a888569df05 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 14 Jun 2016 11:45:01 -0500 Subject: [PATCH 214/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4ed76e38bd..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha6 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 20f67081b7654e44e960defb1e4e491c3a0c2c8b Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 14 Jun 2016 16:52:05 -0500 Subject: [PATCH 215/854] rename long preds to int and cover all fixed precision integer types Signed-off-by: Rich Hickey --- src/clj/clojure/core.clj | 27 +++++++++++++----------- src/clj/clojure/spec.clj | 10 ++++----- src/clj/clojure/spec/gen.clj | 8 +++---- test/clojure/test_clojure/predicates.clj | 2 +- test/clojure/test_clojure/spec.clj | 4 ++-- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 9cf7fc19af..67f3aa339b 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1383,27 +1383,30 @@ :static true} [n] (not (even? n))) -(defn long? - "Return true if x is a Long" +(defn int? + "Return true if x is a fixed precision integer" {:added "1.9"} - [x] (instance? Long x)) + [x] (or (instance? Long x) + (instance? Integer x) + (instance? Short x) + (instance? Byte x))) -(defn pos-long? - "Return true if x is a positive Long" +(defn pos-int? + "Return true if x is a positive fixed precision integer" {:added "1.9"} - [x] (and (instance? Long x) + [x] (and (int? x) (pos? x))) -(defn neg-long? - "Return true if x is a negative Long" +(defn neg-int? + "Return true if x is a negative fixed precision integer" {:added "1.9"} - [x] (and (instance? Long x) + [x] (and (int? x) (neg? x))) -(defn nat-long? - "Return true if x is a non-negative Long" +(defn nat-int? + "Return true if x is a non-negative fixed precision integer" {:added "1.9"} - [x] (and (instance? Long x) + [x] (and (int? x) (not (neg? x)))) (defn double? diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index c515c58a75..918bd0a6f0 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1528,16 +1528,16 @@ by ns-syms. Idempotent." (gen/fmap mkdate# (gen/large-integer* {:min st# :max et#})))))) -(defn long-in-range? +(defn int-in-range? "Return true if start <= val and val < end" [start end val] - (c/and (long? val) (<= start val) (< val end))) + (c/and int? (<= start val) (< val end))) -(defmacro long-in - "Returns a spec that validates longs in the range from start +(defmacro int-in + "Returns a spec that validates ints in the range from start (inclusive) to end (exclusive)." [start end] - `(spec (and c/long? #(long-in-range? ~start ~end %)) + `(spec (and int? #(int-in-range? ~start ~end %)) :gen #(gen/large-integer* {:min ~start :max (dec ~end)}))) (defmacro double-in diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index 1ea343f517..04ba20364b 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -131,10 +131,10 @@ gens, each of which should generate something sequential." (let [simple (simple-type-printable)] {number? (one-of [(large-integer) (double)]) integer? (large-integer) - long? (large-integer) - pos-long? (large-integer* {:min 1}) - neg-long? (large-integer* {:max -1}) - nat-long? (large-integer* {:min 0}) + int? (large-integer) + pos-int? (large-integer* {:min 1}) + neg-int? (large-integer* {:max -1}) + nat-int? (large-integer* {:min 0}) float? (double) double? (double) boolean? (boolean) diff --git a/test/clojure/test_clojure/predicates.clj b/test/clojure/test_clojure/predicates.clj index 150f6a2769..906819624b 100644 --- a/test/clojure/test_clojure/predicates.clj +++ b/test/clojure/test_clojure/predicates.clj @@ -147,7 +147,7 @@ barray (byte-array 0) uri (java.net.URI. "http://clojure.org")] [' - [identity long? pos-long? neg-long? nat-long? double? boolean? indexed? seqable? ident? uuid? bigdec? inst? uri? bytes?] + [identity int? pos-int? neg-int? nat-int? double? boolean? indexed? seqable? ident? uuid? bigdec? inst? uri? bytes?] [0 true false false true false false false false false false false false false false] [1 true true false true false false false false false false false false false false] [-1 true false true false false false false false false false false false false false] diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 264e117348..76a15150e6 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -34,7 +34,7 @@ andre (s/& (s/* keyword?) even-count?) m (s/map-of keyword? string?) coll (s/coll-of keyword? []) - lrange (s/long-in 7 42) + lrange (s/int-in 7 42) drange (s/double-in :infinite? false :NaN? false :min 3.1 :max 3.2) irange (s/inst-in #inst "1939" #inst "1946") ] @@ -47,7 +47,7 @@ lrange 7 7 nil lrange 8 8 nil - lrange 42 ::s/invalid {[] {:pred '(long-in-range? 7 42 %), :val 42, :via [], :in []}} + lrange 42 ::s/invalid {[] {:pred '(int-in-range? 7 42 %), :val 42, :via [], :in []}} irange #inst "1938" ::s/invalid {[] {:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1938", :via [], :in []}} irange #inst "1942" #inst "1942" nil From 046d0ecd0db89804b8aad2a2b903b683fd521c31 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 15 Jun 2016 13:33:41 -0500 Subject: [PATCH 216/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..373c51d674 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha7 http://clojure.org/ Clojure core environment and runtime library. From 546ae41c5ba1cdad54c564a2f48f1400761a9acc Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 15 Jun 2016 13:33:41 -0500 Subject: [PATCH 217/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 373c51d674..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha7 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 4978bf5cee35f74df87c49720fa82de7287d60a5 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 15 Jun 2016 16:05:28 -0400 Subject: [PATCH 218/854] fix def/fdef symbol resolution, replace fn-spec with more general get-spec --- src/clj/clojure/repl.clj | 2 +- src/clj/clojure/spec.clj | 57 +++++++++++++++++++---------------- src/clj/clojure/spec/test.clj | 4 +-- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index b59987ca83..e77b78845d 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -107,7 +107,7 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) (println "Spec")) (when doc (println " " doc)) (when n - (when-let [fnspec (spec/fn-spec (symbol (str (ns-name n)) (name nm)))] + (when-let [fnspec (spec/get-spec (symbol (str (ns-name n)) (name nm)))] (println "Spec") (doseq [role [:args :ret :fn]] (when-let [spec (get fnspec role)] diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 918bd0a6f0..8c417240f4 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -261,24 +261,40 @@ (defn ^:skip-wiki def-impl "Do not call this directly, use 'def'" [k form spec] - (assert (c/and (named? k) (namespace k)) "k must be namespaced keyword/symbol") + (assert (c/and (named? k) (namespace k)) "k must be namespaced keyword or resolvable symbol") (let [spec (if (c/or (spec? spec) (regex? spec) (get @registry-ref spec)) spec (spec-impl form spec nil nil))] (swap! registry-ref assoc k spec) k)) +(defn ns-qualify + "Qualify symbol s by resolving it or using the current *ns*." + [s] + (if (namespace s) + (let [v (resolve s)] + (assert v (str "Unable to resolve: " s)) + (->sym v)) + (symbol (str (.name *ns*)) (str s)))) + (defmacro def - "Given a namespace-qualified keyword or symbol k, and a spec, spec-name, predicate or regex-op - makes an entry in the registry mapping k to the spec" + "Given a namespace-qualified keyword or resolvable symbol k, and a + spec, spec-name, predicate or regex-op makes an entry in the + registry mapping k to the spec" [k spec-form] - `(def-impl ~k '~(res spec-form) ~spec-form)) + (let [k (if (symbol? k) (ns-qualify k) k)] + `(def-impl '~k '~(res spec-form) ~spec-form))) (defn registry - "returns the registry map" + "returns the registry map, prefer 'get-spec' to lookup a spec by name" [] @registry-ref) +(defn get-spec + "Returns spec registered for keyword/symbol/var k, or nil." + [k] + (get (registry) (if (keyword? k) k (->sym k)))) + (declare map-spec) (defmacro spec @@ -488,8 +504,12 @@ and returns a spec whose conform/explain take a fn and validates it using generative testing. The conformed value is always the fn itself. + See 'fdef' for a single operation that creates an fspec and + registers it, as well as a full description of :args, :ret and :fn + Optionally takes :gen generator-fn, which must be a fn of no args that returns a test.check generator." + [& {:keys [args ret fn gen]}] `(fspec-impl (spec ~args) '~(res args) (spec ~ret) '~(res ret) @@ -504,15 +524,6 @@ `(tuple-impl '~(mapv res preds) ~(vec preds))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- ns-qualify - "Qualify symbol s by resolving it or using the current *ns*." - [s] - (if-let [resolved (resolve s)] - (->sym resolved) - (if (namespace s) - s - (symbol (str (.name *ns*)) (str s))))) - (defn- expect "Returns nil if v conforms to spec, else throws ex-info with explain-data." [spec v] @@ -523,11 +534,6 @@ [m] (c/or (:args m) (:ret m))) -(defn fn-spec - "Returns fspec of specs for var or symbol v, or nil." - [v] - (get (registry) (->sym v))) - (defmacro with-instrument-disabled "Disables instrument's checking of calls, within a scope." [& body] @@ -549,7 +555,7 @@ [& args] (if *instrument-enabled* (with-instrument-disabled - (let [specs (fn-spec v)] + (let [specs (get-spec v)] (when (:args specs) (conform! v :args (:args specs) args args)) (binding [*instrument-enabled* true] (.applyTo ^clojure.lang.IFn f args)))) @@ -557,7 +563,7 @@ (defn- macroexpand-check [v args] - (let [specs (fn-spec v)] + (let [specs (get-spec v)] (when-let [arg-spec (:args specs)] (when (= ::invalid (conform arg-spec args)) (let [ed (assoc (explain-data* arg-spec [:args] @@ -580,8 +586,8 @@ expected to contain predicates that relate those values Qualifies fn-sym with resolve, or using *ns* if no resolution found. - Registers specs in the global registry, where they can be retrieved - by calling fn-spec. + Registers an fspec in the global registry, where it can be retrieved + by calling get-spec with the var or fully-qualified symbol. Once registered, function specs are included in doc, checked by instrument, tested by the runner clojure.spec.test/run-tests, and (if @@ -601,8 +607,7 @@ :sym symbol?) :ret symbol?)" [fn-sym & specs] - (let [qn (ns-qualify fn-sym)] - `(clojure.spec/def '~qn (clojure.spec/fspec ~@specs)))) + `(clojure.spec/def ~fn-sym (clojure.spec/fspec ~@specs))) (defn- no-fn-spec [v specs] @@ -629,7 +634,7 @@ spec, if it exists, throwing an ex-info with explain-data if a check fails. Idempotent." [v] (let [v (->var v) - spec (fn-spec v)] + spec (get-spec v)] (if (fn-spec? spec) (locking instrumented-vars (let [{:keys [raw wrapped]} (get @instrumented-vars v) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index 02ed804925..daef2e5b98 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -74,7 +74,7 @@ passed through to test.check/quick-check: Returns a map as quick-check, with :explain-data added if :result is false." [v & opts] - (let [specs (spec/fn-spec v)] + (let [specs (spec/get-spec v)] (if (:args specs) (apply check-fn @v specs opts) (throw (IllegalArgumentException. (str "No :args spec for " v)))))) @@ -102,7 +102,7 @@ Returns a map as quick-check, with :explain-data added if [& ns-syms] (if (seq ns-syms) (run-var-tests (->> (apply spec/speced-vars ns-syms) - (filter (fn [v] (:args (spec/fn-spec v)))))) + (filter (fn [v] (:args (spec/get-spec v)))))) (run-tests (.name ^clojure.lang.Namespace *ns*)))) (defn run-all-tests From aa9b5677789821de219006ece80836bd5c6c8b9b Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 16 Jun 2016 11:47:24 -0400 Subject: [PATCH 219/854] fspec gen ignores :fn rather than not gen. --- src/clj/clojure/spec.clj | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 8c417240f4..a83dbefb4f 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -507,6 +507,10 @@ See 'fdef' for a single operation that creates an fspec and registers it, as well as a full description of :args, :ret and :fn + fspecs can generate functions that validate the arguments and + fabricate a return value compliant with the :ret spec, ignoring + the :fn spec if present. + Optionally takes :gen generator-fn, which must be a fn of no args that returns a test.check generator." @@ -1440,11 +1444,10 @@ by ns-syms. Idempotent." {path {:pred 'ifn? :val f :via via :in in}})) (gen* [_ _ _ _] (if gfn (gfn) - (when-not fnspec - (gen/return - (fn [& args] - (assert (valid? argspec args) (with-out-str (explain argspec args))) - (gen/generate (gen retspec))))))) + (gen/return + (fn [& args] + (assert (valid? argspec args) (with-out-str (explain argspec args))) + (gen/generate (gen retspec)))))) (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) From 8f118b1f9223e540bfa3d2ee60705a8b35c38f24 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 17 Jun 2016 07:58:21 -0400 Subject: [PATCH 220/854] make explain-out public --- src/clj/clojure/spec.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index a83dbefb4f..27d41c1d4a 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -171,8 +171,8 @@ [spec x] (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) [] x)) -(defn- explain-out - "prints an explanation to *out*." +(defn explain-out + "prints explanation data (per 'explain-data') to *out*." [ed] (if ed (do From 6244247fca92c06c41bf186787f2205f1ee2269a Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 17 Jun 2016 10:36:34 -0400 Subject: [PATCH 221/854] specs are not ifns --- src/clj/clojure/spec.clj | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 27d41c1d4a..75a2af88ea 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -757,8 +757,6 @@ by ns-syms. Idempotent." keys->specs #(c/or (k->s %) %) id (java.util.UUID/randomUUID)] (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) Spec (conform* [_ m] (if (keys-pred m) @@ -841,8 +839,6 @@ by ns-syms. Idempotent." (named? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) :else (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) Spec (conform* [_ x] (dt pred x form cpred?)) (unform* [_ x] (if cpred? @@ -873,8 +869,6 @@ by ns-syms. Idempotent." #(assoc %1 retag %2) retag)] (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) Spec (conform* [_ x] (if-let [pred (predx x)] (dt pred x form) @@ -913,8 +907,6 @@ by ns-syms. Idempotent." ([forms preds] (tuple-impl forms preds nil)) ([forms preds gfn] (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) Spec (conform* [_ x] (if-not (c/and (vector? x) @@ -983,8 +975,6 @@ by ns-syms. Idempotent." (tagged-ret (keys i) ret)))) ::invalid)))] (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) Spec (conform* [_ x] (cform x)) (unform* [_ [k x]] (unform (kps k) x)) @@ -1036,8 +1026,6 @@ by ns-syms. Idempotent." "Do not call this directly, use 'and'" [forms preds gfn] (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) Spec (conform* [_ x] (and-preds x preds forms)) (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) @@ -1366,8 +1354,6 @@ by ns-syms. Idempotent." "Do not call this directly, use 'spec' with a regex op argument" [re gfn] (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) Spec (conform* [_ x] (if (c/or (nil? x) (coll? x)) @@ -1413,9 +1399,6 @@ by ns-syms. Idempotent." [argspec aform retspec rform fnspec fform gfn] (let [specs {:args argspec :ret retspec :fn fnspec}] (reify - clojure.lang.IFn - (invoke [this x] (valid? this x)) - clojure.lang.ILookup (valAt [this k] (get specs k)) (valAt [_ k not-found] (get specs k not-found)) From 85a90b2eb46468b4a53da8ccf7f31a48508f5284 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 17 Jun 2016 16:57:47 -0400 Subject: [PATCH 222/854] added bounded-count --- src/clj/clojure/core.clj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 67f3aa339b..2bc08e7883 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7229,6 +7229,18 @@ (cons x (keepi (inc idx) (rest s)))))))))] (keepi 0 coll)))) +(defn bounded-count + "If coll is counted? returns its count, else will count at most the first n + elements of coll using its seq" + {:added "1.9"} + [n coll] + (if (counted? coll) + (count coll) + (loop [i 0 s (seq coll)] + (if (and s (< i n)) + (recur (inc i) (next s)) + i)))) + (defn every-pred "Takes a set of predicates and returns a function f that returns true if all of its composing predicates return a logical true value against all of its arguments, else it returns From 03496c03735cea634b5f448e966ed01d82f8f7ea Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 17 Jun 2016 16:59:30 -0400 Subject: [PATCH 223/854] first cut at every and every-kv --- src/clj/clojure/spec.clj | 140 +++++++++++++++++++++++++++++++++-- src/clj/clojure/spec/gen.clj | 2 +- 2 files changed, 134 insertions(+), 8 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 75a2af88ea..fc70c7bdf7 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -27,8 +27,12 @@ 21) (def ^:dynamic *coll-check-limit* - "The number of items validated in a collection spec'ed with 'coll'" - 100) + "The number of elements validated in a collection spec'ed with 'every'" + 101) + +(def ^:dynamic *coll-error-limit* + "The number of errors reported by explain in a collection spec'ed with 'every'" + 20) (def ^:private ^:dynamic *instrument-enabled* "if false, instrumented fns call straight through" @@ -179,25 +183,25 @@ ;;(prn {:ed ed}) (doseq [[path {:keys [pred val reason via in] :as prob}] (::problems ed)] (when-not (empty? in) - (print "In:" in "")) + (print "In:" (pr-str in) "")) (print "val: ") (pr val) (print " fails") (when-not (empty? via) - (print " spec:" (last via))) + (print " spec:" (pr-str (last via)))) (when-not (empty? path) - (print " at:" path)) + (print " at:" (pr-str path))) (print " predicate: ") (pr pred) (when reason (print ", " reason)) (doseq [[k v] prob] (when-not (#{:pred :val :reason :via :in} k) - (print "\n\t" k " ") + (print "\n\t" (pr-str k) " ") (pr v))) (newline)) (doseq [[k v] ed] (when-not (#{::problems} k) - (print k " ") + (print (pr-str k) " ") (pr v) (newline)))) (println "Success!"))) @@ -432,6 +436,40 @@ [& pred-forms] `(and-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) +(defmacro every + "takes a pred and validates collection elements against that pred. + + Note that 'every' does not do exhaustive checking, rather it samples + *coll-check-limit* elements. Nor (as a result) does it do any + conforming of elements. 'explain' will report at most *coll-error-limit* + problems. Thus 'every' should be suitable for potentially large + collections. + + Takes several kwargs options that further constrain the collection: + + :count - specifies coll has exactly this count (default nil) + :min-count, :max-count - coll has count (<= min count max) (default nil) + :distinct - all the elements are distinct (default nil) + + And additional args that control gen + + :gen-max - the maximum coll size to generate (default 20) + :gen-into - the default colection to generate into (will be emptied) (default []) + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator +" + [pred & {:keys [count max-count min-count distinct gen-max gen-into gen] :as opts}] + `(every-impl '~pred ~pred ~(dissoc opts :gen) ~gen)) + +(defmacro every-kv + "like 'every' but takes separate key and val preds and works on associative collections. + + Same options as 'every'" + + [kpred vpred & opts] + `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (key v#)) :gen-into {} ~@opts)) + (defmacro * "Returns a regex op that matches zero or more values matching pred. Produces a vector of matches iff there is at least one match" @@ -1034,6 +1072,94 @@ by ns-syms. Idempotent." (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) (describe* [_] `(and ~@forms)))) +(defn ^:skip-wiki every-impl + "Do not call this directly, use 'every'" + ([form pred opts] (every-impl form pred opts nil)) + ([form pred {:keys [count max-count min-count distinct gen-max gen-into ::kfn] + :or {gen-max 20, gen-into []} + :as opts} + gfn] + (let [check? #(valid? pred %) + kfn (c/or kfn (fn [i v] i))] + (reify + Spec + (conform* [_ x] + (cond + (c/or (not (seqable? x)) + (c/and distinct (not (empty? x)) (not (apply distinct? x))) + (c/and count (not= count (bounded-count (inc count) x))) + (c/and (c/or min-count max-count) + (not (<= (c/or min-count 0) + (bounded-count (if max-count (inc max-count) min-count) x) + (c/or max-count Integer/MAX_VALUE))))) + :invalid + + :else + (if (indexed? x) + (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] + (loop [i 0] + (if (>= i (c/count x)) + x + (if (check? (nth x i)) + (recur (c/+ i step)) + ::invalid)))) + (c/or (c/and (every? check? (take *coll-check-limit* x)) x) + ::invalid)))) + (unform* [_ x] x) + (explain* [_ path via in x] + (cond + (not (seqable? x)) + {path {:pred 'seqable? :val x :via via :in in}} + + (c/and distinct (not (empty? x)) (not (apply distinct? x))) + {path {:pred 'distinct? :val x :via via :in in}} + + (c/and count (not= count (bounded-count count x))) + {path {:pred `(= ~count (c/count %)) :val x :via via :in in}} + + (c/and (c/or min-count max-count) + (not (<= (c/or min-count 0) + (bounded-count (if max-count (inc max-count) min-count) x) + (c/or max-count Integer/MAX_VALUE)))) + {path {:pred `(<= ~(c/or min-count 0) (c/count %) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}} + + :else + (apply merge + (take *coll-error-limit* + (keep identity + (map (fn [i v] + (let [k (kfn i v)] + (when-not (check? v) + (let [prob (explain-1 form pred (conj path k) via (conj in k) v)] + prob)))) + (range) x)))))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [init (empty gen-into) + pgen (gensub pred overrides path rmap form)] + (gen/fmap + #(if (vector? init) % (into init %)) + (cond + distinct + (if count + (gen/vector-distinct pgen {:num-elements count :max-tries 100}) + (gen/vector-distinct pgen {:min-elements (c/or min-count 0) + :max-elements (c/or max-count (max gen-max (c/* 2 (c/or min-count 0)))) + :max-tries 100})) + + count + (gen/vector pgen count) + + (c/or min-count max-count) + (gen/vector pgen (c/or min-count 0) (c/or max-count (max gen-max (c/* 2 (c/or min-count 0))))) + + :else + (gen/vector pgen 0 gen-max)))))) + + (with-gen* [_ gfn] (every-impl form pred opts gfn)) + (describe* [_] `(every ~form ~@(mapcat identity opts))))))) + ;;;;;;;;;;;;;;;;;;;;;;; regex ;;;;;;;;;;;;;;;;;;; ;;See: ;; http://matt.might.net/articles/implementation-of-regular-expression-matching-in-scheme-with-derivatives/ diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index 04ba20364b..194721d9c7 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -89,7 +89,7 @@ (fn [s] (c/list 'lazy-combinator s)) syms))) -(lazy-combinators hash-map list map not-empty set vector fmap elements +(lazy-combinators hash-map list map not-empty set vector vector-distinct fmap elements bind choose fmap one-of such-that tuple sample return large-integer* double*) From 31b804223bc8f9387f5625dae870ffa9cf0281fc Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Sat, 18 Jun 2016 12:43:07 -0400 Subject: [PATCH 224/854] fix :invalid -> ::invalid --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index fc70c7bdf7..aa98a62913 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1092,7 +1092,7 @@ by ns-syms. Idempotent." (not (<= (c/or min-count 0) (bounded-count (if max-count (inc max-count) min-count) x) (c/or max-count Integer/MAX_VALUE))))) - :invalid + ::invalid :else (if (indexed? x) From b0c945447a09137eacaa95287ce2f484f9cfdaab Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 20 Jun 2016 11:02:33 -0400 Subject: [PATCH 225/854] support gen overrides by name in addition to path --- src/clj/clojure/spec.clj | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index aa98a62913..72ddefbdfa 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -221,7 +221,8 @@ (defn- gensub [spec overrides path rmap form] ;;(prn {:spec spec :over overrides :path path :form form}) - (let [spec (specize spec)] + (let [spec (c/or (get overrides spec) spec) + spec (specize spec)] (if-let [g (c/or (get overrides path) (gen* spec overrides path rmap))] (gen/such-that #(valid? spec %) g 100) (throw (IllegalStateException. (str "Unable to construct gen at: " path " for: " (abbrev form))))))) @@ -229,12 +230,13 @@ (defn gen "Given a spec, returns the generator for it, or throws if none can be constructed. Optionally an overrides map can be provided which - should map paths (vectors of keywords) to generators. These will be - used instead of the generators at those paths. Note that parent - generator (in the spec or overrides map) will supersede those of any - subtrees. A generator for a regex op must always return a - sequential collection (i.e. a generator for s/? should return either - an empty sequence/vector or a sequence/vector with one item in it)" + should map spec names or paths (vectors of keywords) to + generators. These will be used instead of the generators at those + names/paths. Note that parent generator (in the spec or overrides + map) will supersede those of any subtrees. A generator for a regex + op must always return a sequential collection (i.e. a generator for + s/? should return either an empty sequence/vector or a + sequence/vector with one item in it)" ([spec] (gen spec nil)) ([spec overrides] (gensub spec overrides [] {::recursion-limit *recursion-limit*} spec))) From 22289b285e56b08154892a7ad317ba85d6e89d42 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 20 Jun 2016 10:50:24 -0500 Subject: [PATCH 226/854] Add conditional Inst support for java.time.Instant on Java 1.8+ Signed-off-by: Rich Hickey --- src/clj/clojure/core.clj | 6 ++++++ src/clj/clojure/core_instant18.clj | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/clj/clojure/core_instant18.clj diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 2bc08e7883..fa91ba157e 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6639,6 +6639,12 @@ java.util.Date (inst-ms* [inst] (.getTime ^java.util.Date inst))) +;; conditionally extend to Instant on Java 8+ +(try + (Class/forName "java.time.Instant") + (load "core_instant18") + (catch ClassNotFoundException cnfe)) + (defn inst-ms "Return the number of milliseconds since January 1, 1970, 00:00:00 GMT" {:added "1.9"} diff --git a/src/clj/clojure/core_instant18.clj b/src/clj/clojure/core_instant18.clj new file mode 100644 index 0000000000..1feb325ed7 --- /dev/null +++ b/src/clj/clojure/core_instant18.clj @@ -0,0 +1,17 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(in-ns 'clojure.core) + +(import 'java.time.Instant) + +(set! *warn-on-reflection* true) + +(extend-protocol Inst + java.time.Instant + (inst-ms* [inst] (.toEpochMilli ^java.time.Instant inst))) From 574ea97ae94acf83fdfd89f95f5518abd2bbd57e Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Thu, 23 Jun 2016 13:22:16 -0400 Subject: [PATCH 227/854] instrument and test enhancements 5 Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 249 ++++++++++++++++++--------- src/clj/clojure/spec/test.clj | 314 +++++++++++++++++++++------------- 2 files changed, 357 insertions(+), 206 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 72ddefbdfa..e1c3b2bc98 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -225,7 +225,9 @@ spec (specize spec)] (if-let [g (c/or (get overrides path) (gen* spec overrides path rmap))] (gen/such-that #(valid? spec %) g 100) - (throw (IllegalStateException. (str "Unable to construct gen at: " path " for: " (abbrev form))))))) + (let [abbr (abbrev form)] + (throw (ex-info (str "Unable to construct gen at: " path " for: " abbr) + {::path path ::no-gen-for form})))))) (defn gen "Given a spec, returns the generator for it, or throws if none can @@ -277,10 +279,9 @@ (defn ns-qualify "Qualify symbol s by resolving it or using the current *ns*." [s] - (if (namespace s) - (let [v (resolve s)] - (assert v (str "Unable to resolve: " s)) - (->sym v)) + (if-let [ns-sym (some-> s namespace symbol)] + (c/or (some-> (get (ns-aliases *ns*) ns-sym) str (symbol (name s))) + s) (symbol (str (.name *ns*)) (str s)))) (defmacro def @@ -585,8 +586,9 @@ ~@body)) (defn- spec-checking-fn - [v f] - (let [conform! (fn [v role spec data args] + [v f fn-spec] + (let [fn-spec (maybe-spec fn-spec) + conform! (fn [v role spec data args] (let [conformed (conform spec data)] (if (= ::invalid conformed) (let [ed (assoc (explain-data* spec [role] [] [] data) @@ -599,16 +601,15 @@ [& args] (if *instrument-enabled* (with-instrument-disabled - (let [specs (get-spec v)] - (when (:args specs) (conform! v :args (:args specs) args args)) - (binding [*instrument-enabled* true] - (.applyTo ^clojure.lang.IFn f args)))) + (when (:args fn-spec) (conform! v :args (:args fn-spec) args args)) + (binding [*instrument-enabled* true] + (.applyTo ^clojure.lang.IFn f args))) (.applyTo ^clojure.lang.IFn f args))))) (defn- macroexpand-check [v args] - (let [specs (get-spec v)] - (when-let [arg-spec (:args specs)] + (let [fn-spec (get-spec v)] + (when-let [arg-spec (:args fn-spec)] (when (= ::invalid (conform arg-spec args)) (let [ed (assoc (explain-data* arg-spec [:args] (if-let [name (spec-name arg-spec)] [name] []) [] args) @@ -654,13 +655,13 @@ `(clojure.spec/def ~fn-sym (clojure.spec/fspec ~@specs))) (defn- no-fn-spec - [v specs] + [v spec] (ex-info (str "Fn at " v " is not spec'ed.") - {:var v :specs specs})) + {:var v :spec spec})) (def ^:private instrumented-vars "Map for instrumented vars to :raw/:wrapped fns" - (atom {})) + (atom {})) (defn- ->var [s-or-v] @@ -671,87 +672,167 @@ v (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) +(defn- instrument-choose-fn + "Helper for instrument." + [f spec sym {:keys [stub replace]}] + (if (some #{sym} stub) + (-> spec gen gen/generate) + (get replace sym f))) + +(defn- instrument-choose-spec + "Helper for instrument" + [spec sym {overrides :spec}] + (get overrides sym spec)) + +(defn- as-seqable + [x] + (if (seqable? x) x (list x))) + +(defn- instrument-1 + [s opts] + (when-let [v (resolve s)] + (let [spec (get-spec v) + {:keys [raw wrapped]} (get @instrumented-vars v) + current @v + to-wrap (if (= wrapped current) raw current) + ospec (c/or (instrument-choose-spec spec s opts) + (throw (no-fn-spec v spec))) + ofn (instrument-choose-fn to-wrap ospec s opts) + checked (spec-checking-fn v ofn ospec)] + (alter-var-root v (constantly checked)) + (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked})) + (->sym v))) + (defn instrument - "Instruments the var at v, a var or symbol, to check specs -registered with fdef. Wraps the fn at v to check the :args -spec, if it exists, throwing an ex-info with explain-data if a -check fails. Idempotent." - [v] - (let [v (->var v) - spec (get-spec v)] - (if (fn-spec? spec) - (locking instrumented-vars - (let [{:keys [raw wrapped]} (get @instrumented-vars v) - current @v] - (when-not (= wrapped current) - (let [checked (spec-checking-fn v current)] - (alter-var-root v (constantly checked)) - (swap! instrumented-vars assoc v {:raw current :wrapped checked})))) - v) - (throw (no-fn-spec v spec))))) + "Instruments the vars named by sym-or-syms, a symbol or a +collection of symbols. Idempotent. + +If a var has an :args fn-spec, sets the var's root binding to a +fn that checks arg conformance (throwing an exception on failure) +before delegating to the original fn. + +The opts map can be used to override registered specs, and/or to +replace fn implementations entirely: + + :spec a map from fn symbols to spec overrides + :stub a collection of fn symbols to stub + :replace a map from fn symbols to fn overrides + +:spec overrides registered fn-specs with specs your provide. Use +:spec overrides to provide specs for libraries that do not have +them, or to constrain your own use of a fn to a subset of its +spec'ed contract. + +:stub replaces a fn with a stub that checks :args, then uses the +:ret spec to generate a return value. + +:replace replaces a fn with a fn that check :args, then invokes +a fn you provide, enabling arbitrary stubbing and mocking. + +:spec can be used in combination with :stub or :replace. + +Opts for symbols not named by sym-or-syms are ignored. This +facilitates sharing a common options map across many different +calls to instrument. + +Returns a collection of syms naming the vars instrumented." + ([sym-or-syms] (instrument sym-or-syms nil)) + ([sym-or-syms opts] + (locking instrumented-vars + (into + [] + (comp (map #(instrument-1 % opts)) + (remove nil?)) + (as-seqable sym-or-syms))))) + +(defn- unstrument-1 + [s] + (when-let [v (resolve s)] + (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] + (let [current @v] + (when (= wrapped current) + (alter-var-root v (constantly raw)))) + (swap! instrumented-vars dissoc v)) + (->sym v))) (defn unstrument - "Undoes instrument on the var at v, a var or symbol. Idempotent." - [v] - (let [v (->var v)] - (locking instrumented-vars - (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] - (let [current @v] - (when (= wrapped current) - (alter-var-root v (constantly raw)))) - (swap! instrumented-vars dissoc v)) - v))) - -(defn speced-vars - "Returns the set of vars whose namespace is in ns-syms AND -whose vars have been speced with fdef. If no ns-syms are -specified, return speced vars from all namespaces." - [& ns-syms] - (let [ns-match? (if (seq ns-syms) - (set (map str ns-syms)) - (constantly true))] - (reduce-kv - (fn [s k _] - (if (c/and (symbol? k) - (ns-match? (namespace k))) - (if-let [v (resolve k)] - (conj s v) - s) - s)) - #{} - (registry)))) + "Undoes instrument on the vars named by sym-or-syms. Idempotent. +Returns a collection of syms naming the vars unstrumented." + [sym-or-syms] + (locking instrumented-vars + (into + [] + (comp (map #(unstrument-1 %)) + (remove nil?)) + (as-seqable sym-or-syms)))) + +(defn- opt-syms + "Returns set of symbols referenced by 'instrument' opts map" + [opts] + (reduce into #{} [(:stub opts) (c/keys (:replace opts)) (c/keys (:spec opts))])) + +(defn- ns-matcher + [ns-syms] + (let [ns-names (into #{} (map str) ns-syms)] + (fn [s] + (contains? ns-names (namespace s))))) (defn instrument-ns - "Call instrument for all speced-vars in namespaces named -by ns-syms. Idempotent." - [& ns-syms] - (when (seq ns-syms) - (locking instrumented-vars - (doseq [v (apply speced-vars ns-syms)] - (instrument v))))) + "Like instrument, but works on all symbols whose namespace is +in ns-or-nses, specified as a symbol or a seq of symbols." + ([] (instrument-ns (.name ^clojure.lang.Namespace *ns*))) + ([ns-or-nses] (instrument-ns ns-or-nses nil)) + ([ns-or-nses opts] + (let [ns-match? (ns-matcher (as-seqable ns-or-nses))] + (locking instrumented-vars + (into + [] + (comp c/cat + (filter symbol?) + (filter ns-match?) + (distinct) + (map #(instrument-1 % opts)) + (remove nil?)) + [(c/keys (registry)) (opt-syms opts)]))))) (defn unstrument-ns - "Call unstrument for all speced-vars in namespaces named -by ns-syms. Idempotent." - [& ns-syms] - (when (seq ns-syms) + "Like unstrument, but works on all symbols whose namespace is +in ns-or-nses, specified as a symbol or a seq of symbols." + [ns-or-nses] + (let [ns-match? (ns-matcher (as-seqable ns-or-nses))] (locking instrumented-vars - (doseq [v (apply speced-vars ns-syms)] - (unstrument v))))) + (into + [] + (comp (map ->sym) + (filter ns-match?) + (map unstrument-1) + (remove nil?)) + (c/keys @instrumented-vars))))) (defn instrument-all - "Call instrument for all speced-vars. Idempotent." - [] - (locking instrumented-vars - (doseq [v (speced-vars)] - (instrument v)))) + "Like instrument, but works on all vars." + ([] (instrument-all nil)) + ([opts] + (locking instrumented-vars + (into + [] + (comp c/cat + (filter symbol?) + (distinct) + (map #(instrument-1 % opts)) + (remove nil?)) + [(c/keys (registry)) (opt-syms opts)])))) (defn unstrument-all - "Call unstrument for all speced-vars. Idempotent" + "Like unstrument, but works on all vars." [] (locking instrumented-vars - (doseq [v (speced-vars)] - (unstrument v)))) + (into + [] + (comp (map ->sym) + (map unstrument-1) + (remove nil?)) + (c/keys @instrumented-vars)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- recur-limit? [rmap id path k] @@ -1674,4 +1755,4 @@ by ns-syms. Idempotent." ~@(when-not NaN? '[#(not (Double/isNaN %))]) ~@(when max `[#(<= % ~max)]) ~@(when min `[#(<= ~min %)])) - :gen #(gen/double* ~m))) \ No newline at end of file + :gen #(gen/double* ~m))) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index daef2e5b98..c4044d2b16 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -7,140 +7,210 @@ ; You must not remove this notice, or any other, from this software. (ns clojure.spec.test + (:refer-clojure :exclude [test]) (:require - [clojure.spec :as spec] + [clojure.pprint :as pp] + [clojure.spec :as s] [clojure.spec.gen :as gen])) -;; wrap spec/explain-data until specs always return nil for ok data -(defn- explain-data* - [spec v] - (when-not (spec/valid? spec v nil) - (spec/explain-data spec v))) - -;; wrap and unwrap spec failure data in an exception so that -;; quick-check will treat it as a failure. -(defn- wrap-failing - [explain-data step] - (ex-info "Wrapper" {::check-call (assoc explain-data :failed-on step)})) - -(defn- unwrap-failing - [ret] - (let [ret (if-let [explain (-> ret :result ex-data ::check-call)] - (assoc ret :result explain) - ret)] - (if-let [shrunk-explain (-> ret :shrunk :result ex-data ::check-call)] - (assoc-in ret [:shrunk :result] shrunk-explain) - ret))) +(in-ns 'clojure.spec.test.check) +(in-ns 'clojure.spec.test) +(alias 'stc 'clojure.spec.test.check) + +(defn- explain-test + [args spec v role] + (ex-info + "Specification-based test failed" + (when-not (s/valid? spec v nil) + (assoc (s/explain-data* spec [role] [] [] v) + ::args args + ::val v)))) (defn- check-call "Returns true if call passes specs, otherwise *returns* an exception -with explain-data plus a :failed-on key under ::check-call." +with explain-data under ::check-call." [f specs args] - (let [cargs (when (:args specs) (spec/conform (:args specs) args))] - (if (= cargs ::spec/invalid) - (wrap-failing (explain-data* (:args specs) args) :args) + (let [cargs (when (:args specs) (s/conform (:args specs) args))] + (if (= cargs ::s/invalid) + (explain-test args (:args specs) args :args) (let [ret (apply f args) - cret (when (:ret specs) (spec/conform (:ret specs) ret))] - (if (= cret ::spec/invalid) - (wrap-failing (explain-data* (:ret specs) ret) :ret) + cret (when (:ret specs) (s/conform (:ret specs) ret))] + (if (= cret ::s/invalid) + (explain-test args (:ret specs) ret :ret) (if (and (:args specs) (:ret specs) (:fn specs)) - (if (spec/valid? (:fn specs) {:args cargs :ret cret}) + (if (s/valid? (:fn specs) {:args cargs :ret cret}) true - (wrap-failing (explain-data* (:fn specs) {:args cargs :ret cret}) :fn)) + (explain-test args (:fn specs) {:args cargs :ret cret} :fn)) true)))))) -(defn check-fn - "Check a function using provided specs and test.check. -Same options and return as check-var" - [f specs - & {:keys [num-tests seed max-size reporter-fn] - :or {num-tests 100 max-size 200 reporter-fn (constantly nil)}}] - (let [g (spec/gen (:args specs)) - prop (gen/for-all* [g] #(check-call f specs %))] - (let [ret (gen/quick-check num-tests prop :seed seed :max-size max-size :reporter-fn reporter-fn)] - (if-let [[smallest] (-> ret :shrunk :smallest)] - (unwrap-failing ret) - ret)))) - -(defn check-var - "Checks a var's specs using test.check. Optional args are -passed through to test.check/quick-check: - - num-tests number of tests to run, default 100 - seed random seed - max-size how large an input to generate, max 200 - reporter-fn reporting fn - -Returns a map as quick-check, with :explain-data added if -:result is false." - [v & opts] - (let [specs (spec/get-spec v)] - (if (:args specs) - (apply check-fn @v specs opts) - (throw (IllegalArgumentException. (str "No :args spec for " v)))))) - -(defn- run-var-tests - "Helper for run-tests, run-all-tests." - [vs] - (let [reporter-fn println] - (reduce - (fn [totals v] - (let [_ (println "Checking" v) - ret (check-var v :reporter-fn reporter-fn)] - (prn ret) - (cond-> totals - true (update :test inc) - (true? (:result ret)) (update :pass inc) - (::spec/problems (:result ret)) (update :fail inc) - (instance? Throwable (:result ret)) (update :error inc)))) - {:test 0, :pass 0, :fail 0, :error 0} - vs))) - -(defn run-tests - "Like run-all-tests, but scoped to specific namespaces, or to -*ns* if no ns-sym are specified." - [& ns-syms] - (if (seq ns-syms) - (run-var-tests (->> (apply spec/speced-vars ns-syms) - (filter (fn [v] (:args (spec/get-spec v)))))) - (run-tests (.name ^clojure.lang.Namespace *ns*)))) - -(defn run-all-tests - "Like clojure.test/run-all-tests, but runs test.check tests -for all speced vars. Prints per-test results to *out*, and -returns a map with :test,:pass,:fail, and :error counts." - [] - (run-var-tests (spec/speced-vars))) - -(comment - (require '[clojure.pprint :as pp] - '[clojure.spec :as s] - '[clojure.spec.gen :as gen] - '[clojure.test :as ctest]) - - (require :reload '[clojure.spec.test :as test]) - - (load-file "examples/broken_specs.clj") - (load-file "examples/correct_specs.clj") - - ;; discover speced vars for your own test runner - (s/speced-vars) - - ;; check a single var - (test/check-var #'-) - (test/check-var #'+) - (test/check-var #'clojure.spec.broken-specs/throwing-fn) - - ;; old style example tests - (ctest/run-all-tests) - - (s/speced-vars 'clojure.spec.correct-specs) - ;; new style spec tests return same kind of map - (test/check-var #'subs) - (clojure.spec.test/run-tests 'clojure.core) - (test/run-all-tests) - - ) +(defn- throwable? + [x] + (instance? Throwable x)) + +(defn- check-fn + [f specs {gen :gen opts ::stc/opts}] + (let [{:keys [num-tests] :or {num-tests 100}} opts + g (try (s/gen (:args specs) gen) (catch Throwable t t))] + (if (throwable? g) + {:result g} + (let [prop (gen/for-all* [g] #(check-call f specs %))] + (apply gen/quick-check num-tests prop (mapcat identity opts)))))) + +(defn- unwrap-return + "Unwraps exceptions used to flow information through test.check." + [x] + (let [data (ex-data x)] + (if (or (::args data) (::s/args data) (::s/no-gen-for data)) + data + x))) + +(defn- result-type + [result] + (let [ret (::return result)] + (cond + (true? ret) :pass + (::s/args ret) :instrument-fail + (::s/no-gen-for ret) :no-gen + (::args ret) :fail + :default :error))) + +(defn- make-test-result + "Builds spec result map." + [test-sym spec test-check-ret] + (let [result (merge {::sym test-sym + ::spec spec + ::stc/ret test-check-ret} + (when-let [result (-> test-check-ret :result)] + {::return (unwrap-return result)}) + (when-let [shrunk (-> test-check-ret :shrunk)] + {::return (unwrap-return (:result shrunk))}))] + (assoc result ::result-type (result-type result)))) + +(defn- abbrev-result + [x] + (if (true? (::return x)) + (dissoc x ::spec ::stc/ret ::return) + (update (dissoc x ::stc/ret) ::spec s/describe))) + +(defn- default-result-callback + [x] + (pp/pprint (abbrev-result x)) + (flush)) + +(defn- test-1 + [{:keys [s f spec]} + {:keys [result-callback] :as opts + :or {result-callback default-result-callback}}] + (let [result (cond + (nil? f) + {::result-type :no-fn ::sym s ::spec spec} + + (:args spec) + (let [tcret (check-fn f spec opts)] + (make-test-result s spec tcret)) + + :default + {::result-type :no-args ::sym s ::spec spec})] + (result-callback result) + result)) + +;; duped from spec to avoid introducing public API +(defn- as-seqable + [x] + (if (seqable? x) x (list x))) + +;; duped from spec to avoid introducing public API +(defn- ns-matcher + [ns-syms] + (let [ns-names (into #{} (map str) ns-syms)] + (fn [s] + (contains? ns-names (namespace s))))) + +(defn- update-result-map + ([] + {:test 0 :pass 0 :fail 0 :error 0 + :no-fn 0 :no-args 0 :no-gen 0}) + ([m] m) + ([results result] + (-> results + (update :test inc) + (update (::result-type result) inc)))) + +(defn- sym->test-map + [s] + (let [v (resolve s)] + {:s s + :f (when v @v) + :spec (when v (s/get-spec v))})) + +(defn test-fn + "Runs generative tests for fn f using spec and opts. See +'test' for options and return." + ([f spec] (test-fn f spec nil)) + ([f spec opts] + (update-result-map + (update-result-map) + (test-1 {:f f :spec spec} opts)))) + +(defn test + "Checks specs for fns named by sym-or-syms using test.check. + +The opts map includes the following optional keys: + +:clojure.spec.test.check/opts opts to flow through test.check +:result-callback callback fn to handle test results +:gen overrides map for spec/gen + +The c.s.t.c/opts include :num-tests in addition to the keys +documented by test.check. + +The result-callback defaults to default-result-callback. + +Returns a map with the following keys: + +:test # of syms tested +:pass # of passing tests +:fail # of failing tests +:error # of throwing tests +:no-fn # of syms with no fn +:no-args # of syms with no argspec +:no-gen # of syms for which arg data gen failed" + ([sym-or-syms] (test sym-or-syms nil)) + ([sym-or-syms opts] + (transduce + (comp + (map sym->test-map) + (map #(test-1 % opts))) + update-result-map + (as-seqable sym-or-syms)))) + +(defn test-ns + "Like test, but scoped to specific namespaces, or to +*ns* if no arg specified." + ([] (test-ns (.name ^clojure.lang.Namespace *ns*))) + ([ns-or-nses] (test-ns ns-or-nses nil)) + ([ns-or-nses opts] + (let [ns-match? (ns-matcher (as-seqable ns-or-nses))] + (transduce + (comp (filter symbol?) + (filter ns-match?) + (map sym->test-map) + (map #(test-1 % opts))) + update-result-map + (keys (s/registry)))))) + +(defn test-all + "Like test, but tests all vars named by fn-specs in the spec +registry." + ([] (test-all nil)) + ([opts] + (transduce + (comp (filter symbol?) + (map sym->test-map) + (map #(test-1 % opts))) + update-result-map + (keys (s/registry))))) + From 43b029fb061f3b85679f7aa3dc8a94be57bba95c Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 23 Jun 2016 14:30:22 -0400 Subject: [PATCH 228/854] docstring tweaks --- src/clj/clojure/spec.clj | 28 ++++++++++++++-------------- src/clj/clojure/spec/test.clj | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index e1c3b2bc98..dcc7ed8aec 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -712,11 +712,15 @@ fn that checks arg conformance (throwing an exception on failure) before delegating to the original fn. The opts map can be used to override registered specs, and/or to -replace fn implementations entirely: +replace fn implementations entirely. Opts for symbols not named by +sym-or-syms are ignored. This facilitates sharing a common options map +across many different calls to instrument. - :spec a map from fn symbols to spec overrides - :stub a collection of fn symbols to stub - :replace a map from fn symbols to fn overrides +The opts map may have the following keys: + + :spec a map from var-name symbols to override specs + :stub a collection of var-name symbols to be replaced by stubs + :replace a map from var-name symbols to replacement fns :spec overrides registered fn-specs with specs your provide. Use :spec overrides to provide specs for libraries that do not have @@ -726,15 +730,11 @@ spec'ed contract. :stub replaces a fn with a stub that checks :args, then uses the :ret spec to generate a return value. -:replace replaces a fn with a fn that check :args, then invokes -a fn you provide, enabling arbitrary stubbing and mocking. +:replace replaces a fn with a fn that checks args conformance, then +invokes the fn you provide, enabling arbitrary stubbing and mocking. :spec can be used in combination with :stub or :replace. -Opts for symbols not named by sym-or-syms are ignored. This -facilitates sharing a common options map across many different -calls to instrument. - Returns a collection of syms naming the vars instrumented." ([sym-or-syms] (instrument sym-or-syms nil)) ([sym-or-syms opts] @@ -778,8 +778,8 @@ Returns a collection of syms naming the vars unstrumented." (contains? ns-names (namespace s))))) (defn instrument-ns - "Like instrument, but works on all symbols whose namespace is -in ns-or-nses, specified as a symbol or a seq of symbols." + "Like instrument, but works on all symbols whose namespace name is +in ns-or-nses, a symbol or a collection of symbols." ([] (instrument-ns (.name ^clojure.lang.Namespace *ns*))) ([ns-or-nses] (instrument-ns ns-or-nses nil)) ([ns-or-nses opts] @@ -796,8 +796,8 @@ in ns-or-nses, specified as a symbol or a seq of symbols." [(c/keys (registry)) (opt-syms opts)]))))) (defn unstrument-ns - "Like unstrument, but works on all symbols whose namespace is -in ns-or-nses, specified as a symbol or a seq of symbols." + "Like unstrument, but works on all symbols whose namespace name is +in ns-or-nses, a symbol or a collection of symbols." [ns-or-nses] (let [ns-match? (ns-matcher (as-seqable ns-or-nses))] (locking instrumented-vars diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index c4044d2b16..c038557076 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -153,7 +153,7 @@ with explain-data under ::check-call." (test-1 {:f f :spec spec} opts)))) (defn test - "Checks specs for fns named by sym-or-syms using test.check. + "Checks specs for fns named by sym-or-syms (a symbol or collection of symbols) using test.check. The opts map includes the following optional keys: From 6d48ae372a540903173be2974b66b8911371e05d Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 28 Apr 2016 13:24:09 -0500 Subject: [PATCH 229/854] CLJ-1910 Namespaced maps in reader and printer Signed-off-by: Rich Hickey --- src/clj/clojure/core_print.clj | 43 +++++++++-- src/jvm/clojure/lang/EdnReader.java | 55 ++++++++++++++ src/jvm/clojure/lang/LispReader.java | 102 ++++++++++++++++++++++++++ test/clojure/test_clojure/reader.cljc | 37 +++++++++- 4 files changed, 228 insertions(+), 9 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 12d8354b09..7c162348d2 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -205,18 +205,45 @@ (print-meta v w) (print-sequential "[" pr-on " " "]" v w)) +(defn- print-prefix-map [prefix m print-one w] + (print-sequential + (str prefix "{") + (fn [e ^Writer w] + (do (print-one (key e) w) (.append w \space) (print-one (val e) w))) + ", " + "}" + (seq m) w)) + (defn- print-map [m print-one w] - (print-sequential - "{" - (fn [e ^Writer w] - (do (print-one (key e) w) (.append w \space) (print-one (val e) w))) - ", " - "}" - (seq m) w)) + (print-prefix-map nil m print-one w)) + +(defn- strip-ns + [named] + (if (symbol? named) + (symbol nil (name named)) + (keyword nil (name named)))) + +(defn- lift-ns + "Returns [lifted-ns lifted-map] or nil if m can't be lifted." + [m] + (loop [ns nil + [[k v :as entry] & entries] (seq m) + lm (empty m)] + (if entry + (when (or (keyword? k) (symbol? k)) + (if ns + (when (= ns (namespace k)) + (recur ns entries (assoc lm (strip-ns k) v))) + (when-let [new-ns (namespace k)] + (recur new-ns entries (assoc lm (strip-ns k) v))))) + [ns lm]))) (defmethod print-method clojure.lang.IPersistentMap [m, ^Writer w] (print-meta m w) - (print-map m pr-on w)) + (let [[ns lift-map] (lift-ns m)] + (if ns + (print-prefix-map (str "#:" ns) lift-map pr-on w) + (print-map m pr-on w)))) (defmethod print-dup java.util.Map [m, ^Writer w] (print-ctor m #(print-map (seq %1) print-dup %2) w)) diff --git a/src/jvm/clojure/lang/EdnReader.java b/src/jvm/clojure/lang/EdnReader.java index 08cfe20feb..5c3bd104b0 100644 --- a/src/jvm/clojure/lang/EdnReader.java +++ b/src/jvm/clojure/lang/EdnReader.java @@ -16,6 +16,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -53,6 +54,7 @@ public class EdnReader{ dispatchMacros['{'] = new SetReader(); dispatchMacros['<'] = new UnreadableReader(); dispatchMacros['_'] = new DiscardReader(); + dispatchMacros[':'] = new NamespaceMapReader(); } static boolean nonConstituent(int ch){ @@ -482,6 +484,59 @@ public Object invoke(Object reader, Object underscore, Object opts) { } } +public static class NamespaceMapReader extends AFn{ + public Object invoke(Object reader, Object colon, Object opts) { + PushbackReader r = (PushbackReader) reader; + + // Read ns symbol + Object sym = read(r, true, null, false, opts); + if (!(sym instanceof Symbol) || ((Symbol)sym).getNamespace() != null) + throw new RuntimeException("Namespaced map must specify a valid namespace: " + sym); + String ns = ((Symbol)sym).getName(); + + // Read map + int nextChar = read1(r); + while(isWhitespace(nextChar)) + nextChar = read1(r); + if('{' != nextChar) + throw new RuntimeException("Namespaced map must specify a map"); + List kvs = readDelimitedList('}', r, true, opts); + if((kvs.size() & 1) == 1) + throw Util.runtimeException("Namespaced map literal must contain an even number of forms"); + + // Construct output map + IPersistentMap m = RT.map(); + Iterator iter = kvs.iterator(); + while(iter.hasNext()) { + Object key = iter.next(); + Object val = iter.next(); + + if(key instanceof Keyword) { + Keyword kw = (Keyword) key; + if (kw.getNamespace() == null) { + m = m.assoc(Keyword.intern(ns, kw.getName()), val); + } else if (kw.getNamespace().equals("_")) { + m = m.assoc(Keyword.intern(null, kw.getName()), val); + } else { + m = m.assoc(kw, val); + } + } else if(key instanceof Symbol) { + Symbol s = (Symbol) key; + if (s.getNamespace() == null) { + m = m.assoc(Symbol.intern(ns, s.getName()), val); + } else if (s.getNamespace().equals("_")) { + m = m.assoc(Symbol.intern(null, s.getName()), val); + } else { + m = m.assoc(s, val); + } + } else { + m = m.assoc(key, val); + } + } + return m; + } +} + public static class DispatchReader extends AFn{ public Object invoke(Object reader, Object hash, Object opts) { int ch = read1((Reader) reader); diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index 6441d45213..a4afb847d2 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -31,6 +31,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -115,6 +116,7 @@ public class LispReader{ dispatchMacros['<'] = new UnreadableReader(); dispatchMacros['_'] = new DiscardReader(); dispatchMacros['?'] = new ConditionalReader(); + dispatchMacros[':'] = new NamespaceMapReader(); } static boolean isWhitespace(int ch){ @@ -597,6 +599,106 @@ public Object invoke(Object reader, Object underscore, Object opts, Object pendi } } +// :a.b{:c 1} => {:a.b/c 1} +// ::{:c 1} => {:a.b/c 1} (where *ns* = a.b) +// ::a{:c 1} => {:a.b/c 1} (where a is aliased to a.b) +public static class NamespaceMapReader extends AFn{ + public Object invoke(Object reader, Object colon, Object opts, Object pendingForms) { + PushbackReader r = (PushbackReader) reader; + + boolean auto = false; + int autoChar = read1(r); + if(autoChar == ':') + auto = true; + else + unread(r, autoChar); + + Object sym = null; + int nextChar = read1(r); + if(isWhitespace(nextChar)) { // the #:: { } case or an error + if(auto) { + while (isWhitespace(nextChar)) + nextChar = read1(r); + if(nextChar != '{') { + unread(r, nextChar); + throw Util.runtimeException("Namespaced map must specify a namespace"); + } + } else { + unread(r, nextChar); + throw Util.runtimeException("Namespaced map must specify a namespace"); + } + } else if(nextChar != '{') { // #:foo { } or #::foo { } + unread(r, nextChar); + sym = read(r, true, null, false, opts, pendingForms); + nextChar = read1(r); + while(isWhitespace(nextChar)) + nextChar = read1(r); + } + if(nextChar != '{') + throw Util.runtimeException("Namespaced map must specify a map"); + + // Resolve autoresolved ns + String ns; + if (auto) { + if (sym == null) { + ns = Compiler.currentNS().getName().getName(); + } else if (!(sym instanceof Symbol) || ((Symbol)sym).getNamespace() != null) { + throw Util.runtimeException("Namespaced map must specify a valid namespace: " + sym); + } else { + Namespace resolvedNS = Compiler.currentNS().lookupAlias((Symbol)sym); + if(resolvedNS == null) + resolvedNS = Namespace.find((Symbol)sym); + + if(resolvedNS == null) { + throw Util.runtimeException("Unknown auto-resolved namespace alias: " + sym); + } else { + ns = resolvedNS.getName().getName(); + } + } + } else if (!(sym instanceof Symbol) || ((Symbol)sym).getNamespace() != null) { + throw Util.runtimeException("Namespaced map must specify a valid namespace: " + sym); + } else { + ns = ((Symbol)sym).getName(); + } + + // Read map + List kvs = readDelimitedList('}', r, true, opts, ensurePending(pendingForms)); + if((kvs.size() & 1) == 1) + throw Util.runtimeException("Namespaced map literal must contain an even number of forms"); + + // Construct output map + IPersistentMap m = RT.map(); + Iterator iter = kvs.iterator(); + while(iter.hasNext()) { + Object key = iter.next(); + Object val = iter.next(); + + if(key instanceof Keyword) { + Keyword kw = (Keyword) key; + if (kw.getNamespace() == null) { + m = m.assoc(Keyword.intern(ns, kw.getName()), val); + } else if (kw.getNamespace().equals("_")) { + m = m.assoc(Keyword.intern(null, kw.getName()), val); + } else { + m = m.assoc(kw, val); + } + } else if(key instanceof Symbol) { + Symbol s = (Symbol) key; + if (s.getNamespace() == null) { + m = m.assoc(Symbol.intern(ns, s.getName()), val); + } else if (s.getNamespace().equals("_")) { + m = m.assoc(Symbol.intern(null, s.getName()), val); + } else { + m = m.assoc(s, val); + } + } else { + m = m.assoc(key, val); + } + } + return m; + } +} + public static class WrappingReader extends AFn{ final Symbol sym; diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc index 6d03590dcc..91ce25ec22 100644 --- a/test/clojure/test_clojure/reader.cljc +++ b/test/clojure/test_clojure/reader.cljc @@ -22,8 +22,10 @@ read-instant-calendar read-instant-timestamp]]) (:require clojure.walk + [clojure.edn :as edn] [clojure.test.generative :refer (defspec)] - [clojure.test-clojure.generators :as cgen]) + [clojure.test-clojure.generators :as cgen] + [clojure.edn :as edn]) (:import [clojure.lang BigInt Ratio] java.io.File java.util.TimeZone)) @@ -713,3 +715,36 @@ (is (= 23 (read-string {:eof 23} ""))) (is (= 23 (read {:eof 23} (clojure.lang.LineNumberingPushbackReader. (java.io.StringReader. "")))))) + +(require '[clojure.string :as s]) +(deftest namespaced-maps + (is (= #:a{1 nil, :b nil, :b/c nil, :_/d nil} + #:a {1 nil, :b nil, :b/c nil, :_/d nil} + {1 nil, :a/b nil, :b/c nil, :d nil})) + (is (= #::{1 nil, :a nil, :a/b nil, :_/d nil} + #:: {1 nil, :a nil, :a/b nil, :_/d nil} + {1 nil, :clojure.test-clojure.reader/a nil, :a/b nil, :d nil} )) + (is (= #::s{1 nil, :a nil, :a/b nil, :_/d nil} + #::s {1 nil, :a nil, :a/b nil, :_/d nil} + {1 nil, :clojure.string/a nil, :a/b nil, :d nil})) + (is (= #::clojure.core{1 nil, :a nil, :a/b nil, :_/d nil} {1 nil, :clojure.core/a nil, :a/b nil, :d nil})) + (is (= (read-string "#:a{b 1 b/c 2}") {'a/b 1, 'b/c 2})) + (is (= (binding [*ns* (the-ns 'clojure.test-clojure.reader)] (read-string "#::{b 1, b/c 2, _/d 3}")) {'clojure.test-clojure.reader/b 1, 'b/c 2, 'd 3})) + (is (= (binding [*ns* (the-ns 'clojure.test-clojure.reader)] (read-string "#::s{b 1, b/c 2, _/d 3}")) {'clojure.string/b 1, 'b/c 2, 'd 3})) + (is (= (read-string "#::clojure.core{b 1, b/c 2, _/d 3}") {'clojure.core/b 1, 'b/c 2, 'd 3}))) + +(deftest namespaced-map-errors + (are [err msg form] (thrown-with-msg? err msg (read-string form)) + Exception #"Invalid token" "#:::" + Exception #"Namespaced map literal must contain an even number of forms" "#:s{1}" + Exception #"Namespaced map must specify a valid namespace" "#:s/t{1 2}" + Exception #"Namespaced map literal must contain an even number of forms" "#::clojure.core{1}" + Exception #"Namespaced map must specify a valid namespace" "#::clojure.core/t{1 2}" + Exception #"Unknown auto-resolved namespace alias" "#::BOGUS{1 2}" + Exception #"Namespaced map must specify a namespace" "#:: clojure.core{:a 1}" + Exception #"Namespaced map must specify a namespace" "#: clojure.core{:a 1}")) + +(deftest namespaced-map-edn + (is (= {1 1, :a/b 2, :b/c 3, :d 4} + (edn/read-string "#:a{1 1, :b 2, :b/c 3, :_/d 4}") + (edn/read-string "#:a {1 1, :b 2, :b/c 3, :_/d 4}")))) \ No newline at end of file From d274b2b96588b100c70be065f949e1fdc9e7e14d Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 14 Jun 2016 09:33:50 -0500 Subject: [PATCH 230/854] CLJ-1919 Destructuring namespaced keys and symbols Signed-off-by: Rich Hickey --- src/clj/clojure/core.clj | 40 +++++++++++++++-------- test/clojure/test_clojure/special.clj | 46 ++++++++++++++++++--------- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index fa91ba157e..cd070d9126 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -4381,24 +4381,36 @@ (if (:as b) (conj ret (:as b) gmap) ret)))) - bes (reduce1 - (fn [bes entry] - (reduce1 #(assoc %1 %2 ((val entry) %2)) - (dissoc bes (key entry)) - ((key entry) bes))) - (dissoc b :as :or) - {:keys #(if (keyword? %) % (keyword (str %))), - :strs str, :syms #(list `quote %)})] + bes (let [transforms + (reduce1 + (fn [transforms mk] + (if (keyword? mk) + (let [mkns (namespace mk) + mkn (name mk)] + (cond (= mkn "keys") (assoc transforms mk #(keyword (or mkns (namespace %)) (name %))) + (= mkn "syms") (assoc transforms mk #(list `quote (symbol (or mkns (namespace %)) (name %)))) + (= mkn "strs") (assoc transforms mk str) + :else transforms)) + transforms)) + {} + (keys b))] + (reduce1 + (fn [bes entry] + (reduce1 #(assoc %1 %2 ((val entry) %2)) + (dissoc bes (key entry)) + ((key entry) bes))) + (dissoc b :as :or) + transforms))] (if (seq bes) (let [bb (key (first bes)) bk (val (first bes)) - bv (if (contains? defaults bb) - (list `get gmap bk (defaults bb)) + local (if (instance? clojure.lang.Named bb) (symbol nil (name bb)) bb) + bv (if (contains? defaults local) + (list `get gmap bk (defaults local)) (list `get gmap bk))] - (recur (cond - (symbol? bb) (-> ret (conj (if (namespace bb) (symbol (name bb)) bb)) (conj bv)) - (keyword? bb) (-> ret (conj (symbol (name bb)) bv)) - :else (pb ret bb bv)) + (recur (if (ident? bb) + (-> ret (conj local bv)) + (pb ret bb bv)) (next bes))) ret))))] (cond diff --git a/test/clojure/test_clojure/special.clj b/test/clojure/test_clojure/special.clj index 9432c45683..87f0e3ffe6 100644 --- a/test/clojure/test_clojure/special.clj +++ b/test/clojure/test_clojure/special.clj @@ -33,24 +33,37 @@ (is (= {} x)))) (deftest keywords-in-destructuring - (let [{:keys [:a :b]} {:a 1 :b 2}] - (is (= 1 a)) - (is (= 2 b)))) + (let [m {:a 1 :b 2}] + (let [{:keys [:a :b]} m] + (is (= [1 2] [a b]))) + (let [{:keys [:a :b :c] :or {c 3}} m] + (is (= [1 2 3] [a b c]))))) (deftest namespaced-keywords-in-destructuring - (let [{:keys [:a/b :c/d]} {:a/b 1 :c/d 2}] - (is (= 1 b)) - (is (= 2 d)))) + (let [m {:a/b 1 :c/d 2}] + (let [{:keys [:a/b :c/d]} m] + (is (= [1 2] [b d]))) + (let [{:keys [:a/b :c/d :e/f] :or {f 3}} m] + (is (= [1 2 3] [b d f]))))) (deftest namespaced-keys-in-destructuring - (let [{:keys [a/b c/d]} {:a/b 1 :c/d 2}] - (is (= 1 b)) - (is (= 2 d)))) + (let [m {:a/b 1 :c/d 2}] + (let [{:keys [a/b c/d]} m] + (is (= [1 2] [b d]))) + (let [{:keys [a/b c/d e/f] :or {f 3}} m] + (is (= [1 2 3] [b d f]))))) (deftest namespaced-syms-in-destructuring - (let [{:syms [a/b c/d]} {'a/b 1 'c/d 2}] - (is (= 1 b)) - (is (= 2 d)))) + (let [{:syms [a/b c/d e/f] :or {f 3}} {'a/b 1 'c/d 2}] + (is (= [1 2 3] [b d f])))) + +(deftest namespaced-keys-syntax + (let [{:a/keys [b c d] :or {d 3}} {:a/b 1 :a/c 2}] + (is (= [1 2 3] [b c d])))) + +(deftest namespaced-syms-syntax + (let [{:a/syms [b c d] :or {d 3}} {'a/b 1 'a/c 2}] + (is (= [1 2 3] [b c d])))) (deftest keywords-not-allowed-in-let-bindings (is (thrown-with-msg? Exception #"Unsupported binding form: :a" @@ -68,11 +81,14 @@ (is (thrown-with-msg? Exception #"Can't let qualified name: a/x" (eval '(let [[a/x] [1]] x))))) +(deftest or-doesnt-create-bindings + (is (thrown-with-msg? Exception #"Unable to resolve symbol: b" + (eval '(let [{:keys [a] :or {b 2}} {:a 1}] [a b]))))) + (require '[clojure.string :as s]) (deftest resolve-keyword-ns-alias-in-destructuring - (let [{:keys [::s/x ::s/y]} {:clojure.string/x 1 :clojure.string/y 2}] - (is (= x 1)) - (is (= y 2)))) + (let [{:keys [::s/x ::s/y ::s/z] :or {z 3}} {:clojure.string/x 1 :clojure.string/y 2}] + (is (= [1 2 3] [x y z])))) (deftest quote-with-multiple-args (let [ex (is (thrown? clojure.lang.Compiler$CompilerException From daf0811dcc304a73e3169f7034edc44a465352a1 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 23 Jun 2016 17:54:29 -0400 Subject: [PATCH 231/854] fix gen override by name, use in fspec ret gen --- src/clj/clojure/spec.clj | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index dcc7ed8aec..ceea0f4df9 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -221,9 +221,10 @@ (defn- gensub [spec overrides path rmap form] ;;(prn {:spec spec :over overrides :path path :form form}) - (let [spec (c/or (get overrides spec) spec) - spec (specize spec)] - (if-let [g (c/or (get overrides path) (gen* spec overrides path rmap))] + (let [spec (specize spec)] + (if-let [g (c/or (get overrides (c/or (spec-name spec) spec)) + (get overrides path) + (gen* spec overrides path rmap))] (gen/such-that #(valid? spec %) g 100) (let [abbr (abbrev form)] (throw (ex-info (str "Unable to construct gen at: " path " for: " abbr) @@ -1634,12 +1635,12 @@ in ns-or-nses, a symbol or a collection of symbols." (let [cargs (conform argspec args)] (explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret}))))))))) {path {:pred 'ifn? :val f :via via :in in}})) - (gen* [_ _ _ _] (if gfn + (gen* [_ overrides _ _] (if gfn (gfn) (gen/return (fn [& args] (assert (valid? argspec args) (with-out-str (explain argspec args))) - (gen/generate (gen retspec)))))) + (gen/generate (gen retspec overrides)))))) (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) From dc8903d294337638f63af8c86fe335f9c29ab526 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 24 Jun 2016 10:31:35 -0400 Subject: [PATCH 232/854] added exercise-fn per dchelimsky --- src/clj/clojure/spec.clj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index ceea0f4df9..5f18fe5161 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1679,6 +1679,18 @@ in ns-or-nses, a symbol or a collection of symbols." ([spec n overrides] (map #(vector % (conform spec %)) (gen/sample (gen spec overrides) n)))) +(defn exercise-fn + "exercises the fn named by sym (a symbol) by applying it to + n (default 10) generated samples of its args spec. When fspec is + supplied its arg spec is used, and sym-or-f can be a fn. Returns a + sequence of tuples of [args ret]. " + ([sym] (exercise-fn sym 10)) + ([sym n] (exercise-fn sym n (get-spec sym))) + ([sym-or-f n fspec] + (let [f (if (symbol? sym-or-f) (resolve sym-or-f) sym-or-f)] + (for [args (gen/sample (gen (:args fspec)) n)] + [args (apply f args)])))) + (defn coll-checker "returns a predicate function that checks *coll-check-limit* items in a collection with pred" [pred] From e8c72929f9648f99e7914e939602c7bdd7928022 Mon Sep 17 00:00:00 2001 From: puredanger Date: Thu, 23 Jun 2016 16:00:45 -0500 Subject: [PATCH 233/854] preparation for using clojure.core specs Signed-off-by: Rich Hickey --- build.xml | 7 ++++++- src/clj/clojure/core/specs.clj | 4 ++++ src/jvm/clojure/lang/Compiler.java | 30 +++++++++++++++++------------- src/jvm/clojure/lang/RT.java | 1 + 4 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 src/clj/clojure/core/specs.clj diff --git a/build.xml b/build.xml index a2e867c1a1..535ac0177c 100644 --- a/build.xml +++ b/build.xml @@ -59,6 +59,7 @@ + @@ -81,9 +82,13 @@ - + + + diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj new file mode 100644 index 0000000000..0850386140 --- /dev/null +++ b/src/clj/clojure/core/specs.clj @@ -0,0 +1,4 @@ +(ns ^{:skip-wiki true} clojure.core.specs + (:require [clojure.spec :as s])) + +(alias 'cc 'clojure.core) \ No newline at end of file diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 8d5ce3abd8..c455d32c26 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6771,20 +6771,24 @@ public static Object macroexpand1(Object x) { Var v = isMacro(op); if(v != null) { - try - { - final Namespace checkns = Namespace.find(Symbol.intern("clojure.spec")); - if (checkns != null) - { - final Var check = Var.find(Symbol.intern("clojure.spec/macroexpand-check")); - if ((check != null) && (check.isBound())) - check.applyTo(RT.cons(v, RT.list(form.next()))); - } - Symbol.intern("clojure.spec"); - } - catch(IllegalArgumentException e) + // Do not check specs while inside clojure.spec + if(! "clojure/spec.clj".equals(SOURCE_PATH.deref())) { - throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e); + try + { + final Namespace checkns = Namespace.find(Symbol.intern("clojure.spec")); + if (checkns != null) + { + final Var check = Var.find(Symbol.intern("clojure.spec/macroexpand-check")); + if ((check != null) && (check.isBound())) + check.applyTo(RT.cons(v, RT.list(form.next()))); + } + Symbol.intern("clojure.spec"); + } + catch(IllegalArgumentException e) + { + throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e); + } } try { diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 95e9a944ba..0682c80531 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -460,6 +460,7 @@ else if(!loaded && failIfNotFound) static void doInit() throws ClassNotFoundException, IOException{ load("clojure/core"); load("clojure/spec"); + load("clojure/core/specs"); Var.pushThreadBindings( RT.mapUniqueKeys(CURRENT_NS, CURRENT_NS.deref(), From 758d009c37f75e85e94a65d227d77ced56f16118 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Fri, 24 Jun 2016 04:08:01 -0400 Subject: [PATCH 234/854] test results as seqs, flow gen Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 22 ++-- src/clj/clojure/spec/test.clj | 186 ++++++++++++++++++---------------- 2 files changed, 115 insertions(+), 93 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 5f18fe5161..5624a669de 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -675,9 +675,9 @@ (defn- instrument-choose-fn "Helper for instrument." - [f spec sym {:keys [stub replace]}] + [f spec sym {over :gen :keys [stub replace]}] (if (some #{sym} stub) - (-> spec gen gen/generate) + (-> spec (gen over) gen/generate) (get replace sym f))) (defn- instrument-choose-spec @@ -685,9 +685,11 @@ [spec sym {overrides :spec}] (get overrides sym spec)) -(defn- as-seqable +(defn- collectionize [x] - (if (seqable? x) x (list x))) + (if (symbol? x) + (list x) + x)) (defn- instrument-1 [s opts] @@ -721,6 +723,7 @@ The opts map may have the following keys: :spec a map from var-name symbols to override specs :stub a collection of var-name symbols to be replaced by stubs + :gen a map from spec names to generator overrides :replace a map from var-name symbols to replacement fns :spec overrides registered fn-specs with specs your provide. Use @@ -731,6 +734,8 @@ spec'ed contract. :stub replaces a fn with a stub that checks :args, then uses the :ret spec to generate a return value. +:gen overrides are used only for :stub generation. + :replace replaces a fn with a fn that checks args conformance, then invokes the fn you provide, enabling arbitrary stubbing and mocking. @@ -739,12 +744,13 @@ invokes the fn you provide, enabling arbitrary stubbing and mocking. Returns a collection of syms naming the vars instrumented." ([sym-or-syms] (instrument sym-or-syms nil)) ([sym-or-syms opts] + (assert (every? ident? (c/keys (:gen opts))) "instrument :gen expects ident keys") (locking instrumented-vars (into [] (comp (map #(instrument-1 % opts)) (remove nil?)) - (as-seqable sym-or-syms))))) + (collectionize sym-or-syms))))) (defn- unstrument-1 [s] @@ -765,7 +771,7 @@ Returns a collection of syms naming the vars unstrumented." [] (comp (map #(unstrument-1 %)) (remove nil?)) - (as-seqable sym-or-syms)))) + (collectionize sym-or-syms)))) (defn- opt-syms "Returns set of symbols referenced by 'instrument' opts map" @@ -784,7 +790,7 @@ in ns-or-nses, a symbol or a collection of symbols." ([] (instrument-ns (.name ^clojure.lang.Namespace *ns*))) ([ns-or-nses] (instrument-ns ns-or-nses nil)) ([ns-or-nses opts] - (let [ns-match? (ns-matcher (as-seqable ns-or-nses))] + (let [ns-match? (ns-matcher (collectionize ns-or-nses))] (locking instrumented-vars (into [] @@ -800,7 +806,7 @@ in ns-or-nses, a symbol or a collection of symbols." "Like unstrument, but works on all symbols whose namespace name is in ns-or-nses, a symbol or a collection of symbols." [ns-or-nses] - (let [ns-match? (ns-matcher (as-seqable ns-or-nses))] + (let [ns-match? (ns-matcher (collectionize ns-or-nses))] (locking instrumented-vars (into [] diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index c038557076..d2f7a00a63 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -66,10 +66,10 @@ with explain-data under ::check-call." (defn- result-type [result] - (let [ret (::return result)] + (let [ret (:result result)] (cond (true? ret) :pass - (::s/args ret) :instrument-fail + (::s/args ret) :no-argspec (::s/no-gen-for ret) :no-gen (::args ret) :fail :default :error))) @@ -77,47 +77,35 @@ with explain-data under ::check-call." (defn- make-test-result "Builds spec result map." [test-sym spec test-check-ret] - (let [result (merge {::sym test-sym - ::spec spec + (let [result (merge {:spec spec ::stc/ret test-check-ret} + (when test-sym + {:sym test-sym}) (when-let [result (-> test-check-ret :result)] - {::return (unwrap-return result)}) + {:result (unwrap-return result)}) (when-let [shrunk (-> test-check-ret :shrunk)] - {::return (unwrap-return (:result shrunk))}))] - (assoc result ::result-type (result-type result)))) - -(defn- abbrev-result - [x] - (if (true? (::return x)) - (dissoc x ::spec ::stc/ret ::return) - (update (dissoc x ::stc/ret) ::spec s/describe))) - -(defn- default-result-callback - [x] - (pp/pprint (abbrev-result x)) - (flush)) + {:result (unwrap-return (:result shrunk))}))] + (assoc result :type (result-type result)))) (defn- test-1 - [{:keys [s f spec]} - {:keys [result-callback] :as opts - :or {result-callback default-result-callback}}] - (let [result (cond - (nil? f) - {::result-type :no-fn ::sym s ::spec spec} + [{:keys [s f spec]} {:keys [result-callback] :as opts}] + (cond + (nil? f) + {:type :no-fn :sym s :spec spec} - (:args spec) - (let [tcret (check-fn f spec opts)] - (make-test-result s spec tcret)) + (:args spec) + (let [tcret (check-fn f spec opts)] + (make-test-result s spec tcret)) - :default - {::result-type :no-args ::sym s ::spec spec})] - (result-callback result) - result)) + :default + {:type :no-argspec :sym s :spec spec})) ;; duped from spec to avoid introducing public API -(defn- as-seqable +(defn- collectionize [x] - (if (seqable? x) x (list x))) + (if (symbol? x) + (list x) + x)) ;; duped from spec to avoid introducing public API (defn- ns-matcher @@ -126,16 +114,6 @@ with explain-data under ::check-call." (fn [s] (contains? ns-names (namespace s))))) -(defn- update-result-map - ([] - {:test 0 :pass 0 :fail 0 :error 0 - :no-fn 0 :no-args 0 :no-gen 0}) - ([m] m) - ([results result] - (-> results - (update :test inc) - (update (::result-type result) inc)))) - (defn- sym->test-map [s] (let [v (resolve s)] @@ -143,46 +121,63 @@ with explain-data under ::check-call." :f (when v @v) :spec (when v (s/get-spec v))})) +(defn- validate-opts + [opts] + (assert (every? ident? (keys (:gen opts))) "test :gen expects ident keys")) + (defn test-fn "Runs generative tests for fn f using spec and opts. See 'test' for options and return." ([f spec] (test-fn f spec nil)) ([f spec opts] - (update-result-map - (update-result-map) - (test-1 {:f f :spec spec} opts)))) + (validate-opts opts) + (test-1 {:f f :spec spec} opts))) (defn test - "Checks specs for fns named by sym-or-syms (a symbol or collection of symbols) using test.check. + "Checks specs for fns named by sym-or-syms (a symbol or +collection of symbols) using test.check. -The opts map includes the following optional keys: +The opts map includes the following optional keys, where stc +aliases clojure.spec.test.check: -:clojure.spec.test.check/opts opts to flow through test.check -:result-callback callback fn to handle test results -:gen overrides map for spec/gen +::stc/opts opts to flow through test.check/quick-check +:gen map from spec names to generator overrides -The c.s.t.c/opts include :num-tests in addition to the keys -documented by test.check. +The ::stc/opts include :num-tests in addition to the keys +documented by test.check. Generator overrides are passed to +spec/gen when generating function args. -The result-callback defaults to default-result-callback. +Returns a lazy sequence of test result maps with the following +keys -Returns a map with the following keys: +:spec the spec tested +:type the type of the test result +:sym optional symbol naming the var tested +:result optional test result +::stc/ret optional value returned by test.check/quick-check -:test # of syms tested -:pass # of passing tests -:fail # of failing tests -:error # of throwing tests -:no-fn # of syms with no fn -:no-args # of syms with no argspec -:no-gen # of syms for which arg data gen failed" +Values for the :result key can be one of + +true passing test +exception code under test threw +map with explain-data under :clojure.spec/problems + +Values for the :type key can be one of + +:pass test passed +:fail test failed +:error test threw +:no-argspec no :args in fn-spec +:no-gen unable to generate :args +:no-fn unable to resolve fn to test +" ([sym-or-syms] (test sym-or-syms nil)) ([sym-or-syms opts] - (transduce - (comp - (map sym->test-map) - (map #(test-1 % opts))) - update-result-map - (as-seqable sym-or-syms)))) + (validate-opts opts) + (->> (eduction + (map sym->test-map) + (collectionize sym-or-syms)) + (pmap #(test-1 % opts))))) (defn test-ns "Like test, but scoped to specific namespaces, or to @@ -190,29 +185,50 @@ Returns a map with the following keys: ([] (test-ns (.name ^clojure.lang.Namespace *ns*))) ([ns-or-nses] (test-ns ns-or-nses nil)) ([ns-or-nses opts] - (let [ns-match? (ns-matcher (as-seqable ns-or-nses))] - (transduce - (comp (filter symbol?) - (filter ns-match?) - (map sym->test-map) - (map #(test-1 % opts))) - update-result-map - (keys (s/registry)))))) + (validate-opts opts) + (let [ns-match? (ns-matcher (collectionize ns-or-nses))] + (->> (eduction + (filter symbol?) + (filter ns-match?) + (map sym->test-map) + (keys (s/registry))) + (pmap #(test-1 % opts)))))) (defn test-all "Like test, but tests all vars named by fn-specs in the spec registry." ([] (test-all nil)) ([opts] - (transduce - (comp (filter symbol?) - (map sym->test-map) - (map #(test-1 % opts))) - update-result-map - (keys (s/registry))))) - - + (validate-opts opts) + (->> (eduction + (filter symbol?) + (map sym->test-map) + (keys (s/registry))) + (pmap #(test-1 % opts))))) + +(defn abbrev-result + "Given a test result, returns an abbreviated version +suitable for summary use." + [x] + (if (true? (:result x)) + (dissoc x :spec ::stc/ret :result) + (update (dissoc x ::stc/ret) :spec s/describe))) + +(defn summarize-results + "Given a collection of test-results, e.g. from 'test', +pretty prints the abbrev-result of each. + +Returns a map with :total, the total number of results, plus a +key with a count for each different :type of result." + [test-results] + (reduce + (fn [summary result] + (pp/pprint (abbrev-result result)) + (-> summary + (update :total inc) + (update (:type result) (fnil inc 0)))) + {:total 0} + test-results)) - From 7ff29181ee3fdbebba1e5a888960871807573995 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Fri, 24 Jun 2016 11:01:17 -0400 Subject: [PATCH 235/854] fold *strument and *strument-ns down into single fns Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 127 ++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 68 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 5624a669de..301d986983 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -706,18 +706,50 @@ (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked})) (->sym v))) +(defn- unstrument-1 + [s] + (when-let [v (resolve s)] + (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] + (let [current @v] + (when (= wrapped current) + (alter-var-root v (constantly raw)))) + (swap! instrumented-vars dissoc v)) + (->sym v))) + +(defn- opt-syms + "Returns set of symbols referenced by 'instrument' opts map" + [opts] + (reduce into #{} [(:stub opts) (c/keys (:replace opts)) (c/keys (:spec opts))])) + +(defn- sym-matcher + "Returns a fn that matches symbols that are either in syms, +or whose namespace is in syms." + [syms] + (let [names (into #{} (map str) syms)] + (fn [s] + (c/or (contains? names (namespace s)) + (contains? names (str s)))))) + +(defn- validate-opts + [opts] + (assert (every? ident? (c/keys (:gen opts))) "instrument :gen expects ident keys")) + (defn instrument - "Instruments the vars named by sym-or-syms, a symbol or a -collection of symbols. Idempotent. + "Instruments the vars matched by ns-or-names, a symbol or a +collection of symbols. Instruments the current namespace if +ns-or-names not specified. Idempotent. + +A var matches ns-or-names if ns-or-names includes either the var's +fully qualified name or the var's namespace. If a var has an :args fn-spec, sets the var's root binding to a fn that checks arg conformance (throwing an exception on failure) before delegating to the original fn. The opts map can be used to override registered specs, and/or to -replace fn implementations entirely. Opts for symbols not named by -sym-or-syms are ignored. This facilitates sharing a common options map -across many different calls to instrument. +replace fn implementations entirely. Opts for symbols not matched +by ns-or-names are ignored. This facilitates sharing a common +options map across many different calls to instrument. The opts map may have the following keys: @@ -742,84 +774,43 @@ invokes the fn you provide, enabling arbitrary stubbing and mocking. :spec can be used in combination with :stub or :replace. Returns a collection of syms naming the vars instrumented." - ([sym-or-syms] (instrument sym-or-syms nil)) - ([sym-or-syms opts] - (assert (every? ident? (c/keys (:gen opts))) "instrument :gen expects ident keys") - (locking instrumented-vars - (into - [] - (comp (map #(instrument-1 % opts)) - (remove nil?)) - (collectionize sym-or-syms))))) - -(defn- unstrument-1 - [s] - (when-let [v (resolve s)] - (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] - (let [current @v] - (when (= wrapped current) - (alter-var-root v (constantly raw)))) - (swap! instrumented-vars dissoc v)) - (->sym v))) - -(defn unstrument - "Undoes instrument on the vars named by sym-or-syms. Idempotent. -Returns a collection of syms naming the vars unstrumented." - [sym-or-syms] - (locking instrumented-vars - (into - [] - (comp (map #(unstrument-1 %)) - (remove nil?)) - (collectionize sym-or-syms)))) - -(defn- opt-syms - "Returns set of symbols referenced by 'instrument' opts map" - [opts] - (reduce into #{} [(:stub opts) (c/keys (:replace opts)) (c/keys (:spec opts))])) - -(defn- ns-matcher - [ns-syms] - (let [ns-names (into #{} (map str) ns-syms)] - (fn [s] - (contains? ns-names (namespace s))))) - -(defn instrument-ns - "Like instrument, but works on all symbols whose namespace name is -in ns-or-nses, a symbol or a collection of symbols." - ([] (instrument-ns (.name ^clojure.lang.Namespace *ns*))) - ([ns-or-nses] (instrument-ns ns-or-nses nil)) - ([ns-or-nses opts] - (let [ns-match? (ns-matcher (collectionize ns-or-nses))] + ([] (instrument (.name ^clojure.lang.Namespace *ns*))) + ([ns-or-names] (instrument ns-or-names nil)) + ([ns-or-names opts] + (validate-opts opts) + (let [match? (sym-matcher (collectionize ns-or-names))] (locking instrumented-vars (into [] (comp c/cat (filter symbol?) - (filter ns-match?) + (filter match?) (distinct) (map #(instrument-1 % opts)) (remove nil?)) [(c/keys (registry)) (opt-syms opts)]))))) -(defn unstrument-ns - "Like unstrument, but works on all symbols whose namespace name is -in ns-or-nses, a symbol or a collection of symbols." - [ns-or-nses] - (let [ns-match? (ns-matcher (collectionize ns-or-nses))] - (locking instrumented-vars - (into - [] - (comp (map ->sym) - (filter ns-match?) - (map unstrument-1) - (remove nil?)) - (c/keys @instrumented-vars))))) +(defn unstrument + "Undoes instrument on the vars matched by ns-or-names, specified +as in instrument. Returns a collection of syms naming the vars +unstrumented." + ([] (unstrument (.name ^clojure.lang.Namespace *ns*))) + ([ns-or-names] + (let [match? (sym-matcher (collectionize ns-or-names))] + (locking instrumented-vars + (into + [] + (comp (map ->sym) + (filter match?) + (map unstrument-1) + (remove nil?)) + (c/keys @instrumented-vars)))))) (defn instrument-all "Like instrument, but works on all vars." ([] (instrument-all nil)) ([opts] + (validate-opts opts) (locking instrumented-vars (into [] From e8557891642a313bbdb97e2d3a61022ec816d132 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 24 Jun 2016 19:10:23 -0400 Subject: [PATCH 236/854] first cut of conforming coll-of and map-of with count constraints --- src/clj/clojure/spec.clj | 155 +++++++++++++++++------------ test/clojure/test_clojure/spec.clj | 1 - 2 files changed, 93 insertions(+), 63 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 301d986983..c6fd93ef61 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -462,6 +462,8 @@ Optionally takes :gen generator-fn, which must be a fn of no args that returns a test.check generator + + See also - coll-of, every-kv " [pred & {:keys [count max-count min-count distinct gen-max gen-into gen] :as opts}] `(every-impl '~pred ~pred ~(dissoc opts :gen) ~gen)) @@ -469,10 +471,37 @@ (defmacro every-kv "like 'every' but takes separate key and val preds and works on associative collections. - Same options as 'every'" + Same options as 'every' + + See also - map-of" + + [kpred vpred & opts] + `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :gen-into {} ~@opts)) + +(defmacro coll-of + "Returns a spec for a collection of items satisfying pred. The + generator will fill an empty init-coll. Unlike 'every', coll-of + will exhaustively conform every value. + + Same options as 'every'. + + See also - every, map-of" + [pred init-coll & opts] + `(every ~pred ::conform-all true :gen-into ~init-coll ~@opts)) + +(defmacro map-of + "Returns a spec for a map whose keys satisfy kpred and vals satisfy + vpred. Unlike 'every-kv', map-of will exhaustively conform every + value. + + Same options as 'every', with the addition of: + + :conform-keys - conform keys as well as values (default false) + See also - every-kv" [kpred vpred & opts] - `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (key v#)) :gen-into {} ~@opts)) + `(and (every-kv ~kpred ~vpred ::conform-all true ~@opts) map?)) + (defmacro * "Returns a regex op that matches zero or more values matching @@ -1153,28 +1182,76 @@ unstrumented." (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) (describe* [_] `(and ~@forms)))) +(defn- coll-prob [x distinct count min-count max-count + path via in] + (cond + (not (seqable? x)) + {path {:pred 'seqable? :val x :via via :in in}} + + (c/and distinct (not (empty? x)) (not (apply distinct? x))) + {path {:pred 'distinct? :val x :via via :in in}} + + (c/and count (not= count (bounded-count count x))) + {path {:pred `(= ~count (c/count %)) :val x :via via :in in}} + + (c/and (c/or min-count max-count) + (not (<= (c/or min-count 0) + (bounded-count (if max-count (inc max-count) min-count) x) + (c/or max-count Integer/MAX_VALUE)))) + {path {:pred `(<= ~(c/or min-count 0) (c/count %) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}})) + (defn ^:skip-wiki every-impl - "Do not call this directly, use 'every'" + "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" ([form pred opts] (every-impl form pred opts nil)) - ([form pred {:keys [count max-count min-count distinct gen-max gen-into ::kfn] + ([form pred {:keys [count max-count min-count distinct gen-max gen-into ::kfn + conform-keys ::conform-all] :or {gen-max 20, gen-into []} :as opts} gfn] (let [check? #(valid? pred %) - kfn (c/or kfn (fn [i v] i))] + kfn (c/or kfn (fn [i v] i)) + addcv (fn [ret i v cv] (conj ret cv)) + cfns (fn [x] + ;;returns a tuple of [init add complete] fns + (cond + (vector? x) + [identity + (fn [ret i v cv] + (if (identical? v cv) + ret + (assoc ret i cv))) + identity] + + (map? x) + [(if conform-keys empty identity) + (fn [ret i v cv] + (if (c/and (identical? v cv) (not conform-keys)) + ret + (assoc ret (nth (if conform-keys cv v) 0) (nth cv 1)))) + identity] + + (list? x) [empty addcv reverse] + + :else [empty addcv identity]))] (reify Spec (conform* [_ x] (cond - (c/or (not (seqable? x)) - (c/and distinct (not (empty? x)) (not (apply distinct? x))) - (c/and count (not= count (bounded-count (inc count) x))) - (c/and (c/or min-count max-count) - (not (<= (c/or min-count 0) - (bounded-count (if max-count (inc max-count) min-count) x) - (c/or max-count Integer/MAX_VALUE))))) + (coll-prob x distinct count min-count max-count + nil nil nil) ::invalid + conform-all + (let [[init add complete] (cfns x)] + (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] + (if vseq + (let [cv (dt pred v nil)] + (if (= ::invalid cv) + ::invalid + (recur (add ret i v cv) (inc i) vs))) + (complete ret)))) + + :else (if (indexed? x) (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] @@ -1188,25 +1265,10 @@ unstrumented." ::invalid)))) (unform* [_ x] x) (explain* [_ path via in x] - (cond - (not (seqable? x)) - {path {:pred 'seqable? :val x :via via :in in}} - - (c/and distinct (not (empty? x)) (not (apply distinct? x))) - {path {:pred 'distinct? :val x :via via :in in}} - - (c/and count (not= count (bounded-count count x))) - {path {:pred `(= ~count (c/count %)) :val x :via via :in in}} - - (c/and (c/or min-count max-count) - (not (<= (c/or min-count 0) - (bounded-count (if max-count (inc max-count) min-count) x) - (c/or max-count Integer/MAX_VALUE)))) - {path {:pred `(<= ~(c/or min-count 0) (c/count %) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}} - - :else - (apply merge - (take *coll-error-limit* + (c/or (coll-prob x distinct count min-count max-count + path via in) + (apply merge + ((if conform-all identity (partial take *coll-error-limit*)) (keep identity (map (fn [i v] (let [k (kfn i v)] @@ -1688,37 +1750,6 @@ unstrumented." (for [args (gen/sample (gen (:args fspec)) n)] [args (apply f args)])))) -(defn coll-checker - "returns a predicate function that checks *coll-check-limit* items in a collection with pred" - [pred] - (let [check? #(valid? pred %)] - (fn [coll] - (c/or (nil? coll) - (c/and - (coll? coll) - (every? check? (take *coll-check-limit* coll))))))) - -(defn coll-gen - "returns a function of no args that returns a generator of - collections of items conforming to pred, with the same shape as - init-coll" - [pred init-coll] - (let [init (empty init-coll)] - (fn [] - (gen/fmap - #(if (vector? init) % (into init %)) - (gen/vector (gen pred)))))) - -(defmacro coll-of - "Returns a spec for a collection of items satisfying pred. The generator will fill an empty init-coll." - [pred init-coll] - `(spec (coll-checker ~pred) :gen (coll-gen ~pred ~init-coll))) - -(defmacro map-of - "Returns a spec for a map whose keys satisfy kpred and vals satisfy vpred." - [kpred vpred] - `(and (coll-of (tuple ~kpred ~vpred) {}) map?)) - (defn inst-in-range? "Return true if inst at or after start and before end" [start end inst] diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 76a15150e6..82e5aacd92 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -120,7 +120,6 @@ m nil ::s/invalid '{[] {:pred map?, :val nil, :via []}} m {} {} nil m {:a "b"} {:a "b"} nil - m {:a :b} ::s/invalid '{[] {:pred (coll-checker (tuple keyword? string?)), :val {:a :b}, :via []}} coll nil nil nil coll [] [] nil From c86375c585c1ad3f7635075f57793dfc2e31593e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 27 Jun 2016 12:00:09 -0400 Subject: [PATCH 237/854] use gen-into targets for vec/map opts --- src/clj/clojure/spec.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index c6fd93ef61..c74fbf41b7 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -277,7 +277,7 @@ (swap! registry-ref assoc k spec) k)) -(defn ns-qualify +(defn- ns-qualify "Qualify symbol s by resolving it or using the current *ns*." [s] (if-let [ns-sym (some-> s namespace symbol)] @@ -1214,7 +1214,7 @@ unstrumented." cfns (fn [x] ;;returns a tuple of [init add complete] fns (cond - (vector? x) + (c/and (vector? x) (vector? gen-into)) [identity (fn [ret i v cv] (if (identical? v cv) @@ -1222,7 +1222,7 @@ unstrumented." (assoc ret i cv))) identity] - (map? x) + (c/and (map? x) (map? gen-into)) [(if conform-keys empty identity) (fn [ret i v cv] (if (c/and (identical? v cv) (not conform-keys)) From 3528b32ed43d98d3d1231ca8bcb59a7f4ebef953 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 27 Jun 2016 12:05:34 -0400 Subject: [PATCH 238/854] use gen-into targets only for map opts --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index c74fbf41b7..43b440cc80 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1214,7 +1214,7 @@ unstrumented." cfns (fn [x] ;;returns a tuple of [init add complete] fns (cond - (c/and (vector? x) (vector? gen-into)) + (vector? x) [identity (fn [ret i v cv] (if (identical? v cv) From 40d875a6af1bf1c7415265a347a0a77de9c3cd94 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 27 Jun 2016 12:44:33 -0400 Subject: [PATCH 239/854] typos --- src/clj/clojure/spec.clj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 43b440cc80..6ccd5bb4eb 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -452,7 +452,7 @@ Takes several kwargs options that further constrain the collection: :count - specifies coll has exactly this count (default nil) - :min-count, :max-count - coll has count (<= min count max) (default nil) + :min-count, :max-count - coll has count (<= min-count count max-count) (defaults nil) :distinct - all the elements are distinct (default nil) And additional args that control gen @@ -1173,11 +1173,11 @@ unstrumented." (defn ^:skip-wiki and-spec-impl "Do not call this directly, use 'and'" [forms preds gfn] - (reify - Spec - (conform* [_ x] (and-preds x preds forms)) - (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) - (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) + (reify + Spec + (conform* [_ x] (and-preds x preds forms)) + (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) + (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) (describe* [_] `(and ~@forms)))) @@ -1192,13 +1192,13 @@ unstrumented." {path {:pred 'distinct? :val x :via via :in in}} (c/and count (not= count (bounded-count count x))) - {path {:pred `(= ~count (c/count %)) :val x :via via :in in}} + {path {:pred `(= ~count ~(c/count x)) :val x :via via :in in}} (c/and (c/or min-count max-count) (not (<= (c/or min-count 0) (bounded-count (if max-count (inc max-count) min-count) x) (c/or max-count Integer/MAX_VALUE)))) - {path {:pred `(<= ~(c/or min-count 0) (c/count %) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}})) + {path {:pred `(<= ~(c/or min-count 0) ~(c/count x) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}})) (defn ^:skip-wiki every-impl "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" From 23e3ec3f8bceeedee70beed7a17846c25eba05a6 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 27 Jun 2016 18:39:49 -0400 Subject: [PATCH 240/854] added merge, merges keys specs new explain-data format - probs collection of prob-maps, :path in maps :into and :kind for every and coll-of no more init-coll for coll-of, use :into or :kind (or not) --- src/clj/clojure/spec.clj | 184 ++++++++++++++++++----------- test/clojure/test_clojure/spec.clj | 70 +++++------ 2 files changed, 152 insertions(+), 102 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 6ccd5bb4eb..9f7d78391a 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -7,7 +7,7 @@ ; You must not remove this notice, or any other, from this software. (ns clojure.spec - (:refer-clojure :exclude [+ * and or cat def keys]) + (:refer-clojure :exclude [+ * and or cat def keys merge]) (:require [clojure.walk :as walk] [clojure.spec.gen :as gen] [clojure.string :as str])) @@ -169,7 +169,7 @@ (defn explain-data "Given a spec and a value x which ought to conform, returns nil if x conforms, else a map with at least the key ::problems whose value is - a path->problem-map, where problem-map has at least :pred and :val + a collection of problem-maps, where problem-map has at least :path :pred and :val keys describing the predicate and the value that failed at that path." [spec x] @@ -181,7 +181,7 @@ (if ed (do ;;(prn {:ed ed}) - (doseq [[path {:keys [pred val reason via in] :as prob}] (::problems ed)] + (doseq [{:keys [path pred val reason via in] :as prob} (::problems ed)] (when-not (empty? in) (print "In:" (pr-str in) "")) (print "val: ") @@ -195,7 +195,7 @@ (pr pred) (when reason (print ", " reason)) (doseq [[k v] prob] - (when-not (#{:pred :val :reason :via :in} k) + (when-not (#{:path :pred :val :reason :via :in} k) (print "\n\t" (pr-str k) " ") (pr v))) (newline)) @@ -440,6 +440,15 @@ [& pred-forms] `(and-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) +(defmacro merge + "Takes map-validating specs (e.g. 'keys' specs) and + returns a spec that returns a conformed map satisfying all of the + specs. Successive conformed values propagate through rest of + predicates. Unlike 'and', merge can generate maps satisfying the + union of the predicates." + [& pred-forms] + `(merge-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) + (defmacro every "takes a pred and validates collection elements against that pred. @@ -451,6 +460,7 @@ Takes several kwargs options that further constrain the collection: + :kind - one of [], (), {}, #{} - must be this kind of collection - (default nil) :count - specifies coll has exactly this count (default nil) :min-count, :max-count - coll has count (<= min-count count max-count) (defaults nil) :distinct - all the elements are distinct (default nil) @@ -458,49 +468,50 @@ And additional args that control gen :gen-max - the maximum coll size to generate (default 20) - :gen-into - the default colection to generate into (will be emptied) (default []) + :into - one of [], (), {}, #{} - the default collection to generate into (default same as :kind if supplied, else []) Optionally takes :gen generator-fn, which must be a fn of no args that returns a test.check generator See also - coll-of, every-kv " - [pred & {:keys [count max-count min-count distinct gen-max gen-into gen] :as opts}] + [pred & {:keys [into kind count max-count min-count distinct gen-max gen] :as opts}] `(every-impl '~pred ~pred ~(dissoc opts :gen) ~gen)) (defmacro every-kv "like 'every' but takes separate key and val preds and works on associative collections. - Same options as 'every' + Same options as 'every', :into defaults to {} See also - map-of" [kpred vpred & opts] - `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :gen-into {} ~@opts)) + `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :into {} ~@opts)) (defmacro coll-of - "Returns a spec for a collection of items satisfying pred. The - generator will fill an empty init-coll. Unlike 'every', coll-of - will exhaustively conform every value. + "Returns a spec for a collection of items satisfying pred. Unlike + 'every', coll-of will exhaustively conform every value. - Same options as 'every'. + Same options as 'every'. conform will produce a collection + corresponding to :into if supplied, else will match the input collection, + avoiding rebuilding when possible. See also - every, map-of" - [pred init-coll & opts] - `(every ~pred ::conform-all true :gen-into ~init-coll ~@opts)) + [pred & opts] + `(every ~pred ::conform-all true ~@opts)) (defmacro map-of "Returns a spec for a map whose keys satisfy kpred and vals satisfy vpred. Unlike 'every-kv', map-of will exhaustively conform every value. - Same options as 'every', with the addition of: + Same options as 'every', :kind set to {}, with the addition of: :conform-keys - conform keys as well as values (default false) See also - every-kv" [kpred vpred & opts] - `(and (every-kv ~kpred ~vpred ::conform-all true ~@opts) map?)) + `(every-kv ~kpred ~vpred ::conform-all true ~@opts :kind {})) (defmacro * @@ -894,7 +905,7 @@ unstrumented." (let [pred (maybe-spec pred)] (if (spec? pred) (explain* pred path (if-let [name (spec-name pred)] (conj via name) via) in v) - {path {:pred (abbrev form) :val v :via via :in in}}))) + [{:path path :pred (abbrev form) :val v :via via :in in}]))) (defn ^:skip-wiki map-spec-impl "Do not call this directly, use 'spec' with a map argument" @@ -934,14 +945,14 @@ unstrumented." ret)))) (explain* [_ path via in x] (if-not (map? x) - {path {:pred 'map? :val x :via via :in in}} + [{:path path :pred 'map? :val x :via via :in in}] (let [reg (registry)] - (apply merge + (apply concat (when-let [probs (->> (map (fn [pred form] (when-not (pred x) (abbrev form))) pred-exprs pred-forms) (keep identity) seq)] - {path {:pred (vec probs) :val x :via via :in in}}) + [{:path path :pred (vec probs) :val x :via via :in in}]) (map (fn [[k v]] (when-not (c/or (not (contains? reg (keys->specs k))) (valid? (keys->specs k) v k)) @@ -996,7 +1007,7 @@ unstrumented." x)) (explain* [_ path via in x] (when (= ::invalid (dt pred x form cpred?)) - {path {:pred (abbrev form) :val x :via via :in in}})) + [{:path path :pred (abbrev form) :val x :via via :in in}])) (gen* [_ _ _ _] (if gfn (gfn) (gen/gen-for-pred pred))) @@ -1029,7 +1040,7 @@ unstrumented." path (conj path dv)] (if-let [pred (predx x)] (explain-1 form pred path via in x) - {path {:pred form :val x :reason "no method" :via via :in in}}))) + [{:path path :pred form :val x :reason "no method" :via via :in in}]))) (gen* [_ overrides path rmap] (if gfn (gfn) @@ -1082,13 +1093,13 @@ unstrumented." (explain* [_ path via in x] (cond (not (vector? x)) - {path {:pred 'vector? :val x :via via :in in}} + [{:path path :pred 'vector? :val x :via via :in in}] (not= (count x) (count preds)) - {path {:pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}} + [{:path path :pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}] :else - (apply merge + (apply concat (map (fn [i form pred] (let [v (x i)] (when-not (valid? pred v) @@ -1128,7 +1139,7 @@ unstrumented." (unform* [_ [k x]] (unform (kps k) x)) (explain* [this path via in x] (when-not (valid? this x) - (apply merge + (apply concat (map (fn [k form pred] (when-not (valid? pred x) (explain-1 form pred (conj path k) via in x))) @@ -1182,39 +1193,70 @@ unstrumented." (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) (describe* [_] `(and ~@forms)))) -(defn- coll-prob [x distinct count min-count max-count +(defn ^:skip-wiki merge-spec-impl + "Do not call this directly, use 'merge'" + [forms preds gfn] + (reify + Spec + (conform* [_ x] (and-preds x preds forms)) + (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) + (explain* [_ path via in x] + (apply concat + (map #(explain-1 %1 %2 path via in x) + forms preds))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (gen/fmap + #(apply c/merge %) + (apply gen/tuple (map #(gensub %1 overrides path rmap %2) + preds forms))))) + (with-gen* [_ gfn] (merge-spec-impl forms preds gfn)) + (describe* [_] `(merge ~@forms)))) + +(defn- coll-prob [x kfn kform distinct count min-count max-count path via in] - (cond - (not (seqable? x)) - {path {:pred 'seqable? :val x :via via :in in}} + (let [] + (cond + (not (kfn x)) + [{:path path :pred kform :val x :via via :in in}] - (c/and distinct (not (empty? x)) (not (apply distinct? x))) - {path {:pred 'distinct? :val x :via via :in in}} + (c/and distinct (not (empty? x)) (not (apply distinct? x))) + [{:path path :pred 'distinct? :val x :via via :in in}] - (c/and count (not= count (bounded-count count x))) - {path {:pred `(= ~count ~(c/count x)) :val x :via via :in in}} + (c/and count (not= count (bounded-count count x))) + [{:path path :pred `(= ~count (c/count ~'%)) :val x :via via :in in}] - (c/and (c/or min-count max-count) - (not (<= (c/or min-count 0) - (bounded-count (if max-count (inc max-count) min-count) x) - (c/or max-count Integer/MAX_VALUE)))) - {path {:pred `(<= ~(c/or min-count 0) ~(c/count x) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}})) + (c/and (c/or min-count max-count) + (not (<= (c/or min-count 0) + (bounded-count (if max-count (inc max-count) min-count) x) + (c/or max-count Integer/MAX_VALUE)))) + [{:path path :pred `(<= ~(c/or min-count 0) (c/count ~'%) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}]))) (defn ^:skip-wiki every-impl "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" ([form pred opts] (every-impl form pred opts nil)) - ([form pred {:keys [count max-count min-count distinct gen-max gen-into ::kfn + ([form pred {gen-into :into + :keys [kind count max-count min-count distinct gen-max ::kfn conform-keys ::conform-all] - :or {gen-max 20, gen-into []} + :or {gen-max 20} :as opts} gfn] - (let [check? #(valid? pred %) + (let [conform-into (c/or gen-into kind) + gen-into (c/or gen-into kind []) + check? #(valid? pred %) kfn (c/or kfn (fn [i v] i)) addcv (fn [ret i v cv] (conj ret cv)) + [kindfn kindform] (cond + (map? kind) [map? `map?] + (vector? kind) [vector? `vector?] + (list? kind) [list? `list?] + (set? kind) [set? `set?] + :else [seqable? `seqable?]) cfns (fn [x] ;;returns a tuple of [init add complete] fns (cond - (vector? x) + (c/and (vector? x) (c/or (not conform-into) (vector? conform-into))) [identity (fn [ret i v cv] (if (identical? v cv) @@ -1222,7 +1264,7 @@ unstrumented." (assoc ret i cv))) identity] - (c/and (map? x) (map? gen-into)) + (c/and (map? x) (map? conform-into)) [(if conform-keys empty identity) (fn [ret i v cv] (if (c/and (identical? v cv) (not conform-keys)) @@ -1230,14 +1272,15 @@ unstrumented." (assoc ret (nth (if conform-keys cv v) 0) (nth cv 1)))) identity] - (list? x) [empty addcv reverse] + (c/or (list? conform-into) (c/and (not conform-into) (list? x))) + [(constantly ()) addcv reverse] - :else [empty addcv identity]))] + :else [#(empty (c/or conform-into %)) addcv identity]))] (reify Spec (conform* [_ x] (cond - (coll-prob x distinct count min-count max-count + (coll-prob x kindfn kindform distinct count min-count max-count nil nil nil) ::invalid @@ -1265,15 +1308,15 @@ unstrumented." ::invalid)))) (unform* [_ x] x) (explain* [_ path via in x] - (c/or (coll-prob x distinct count min-count max-count + (c/or (coll-prob x kindfn kindform distinct count min-count max-count path via in) - (apply merge + (apply concat ((if conform-all identity (partial take *coll-error-limit*)) (keep identity (map (fn [i v] (let [k (kfn i v)] (when-not (check? v) - (let [prob (explain-1 form pred (conj path k) via (conj in k) v)] + (let [prob (explain-1 form pred path via (conj in k) v)] prob)))) (range) x)))))) (gen* [_ overrides path rmap] @@ -1498,11 +1541,12 @@ unstrumented." {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve! p) via (if-let [name (spec-name p)] (conj via name) via) insufficient (fn [path form] - {path {:reason "Insufficient input" - :pred (abbrev form) - :val () - :via via - :in in}})] + [{:path path + :reason "Insufficient input" + :pred (abbrev form) + :val () + :via via + :in in}])] (when p (case op ::accept nil @@ -1530,7 +1574,7 @@ unstrumented." (op-explain form pred path via in input))) ::alt (if (empty? input) (insufficient path (op-describe p)) - (apply merge + (apply concat (map (fn [k form pred] (op-explain (c/or form (op-describe pred)) pred @@ -1607,17 +1651,19 @@ unstrumented." (if (accept? p) (if (= (::op p) ::pcat) (op-explain (op-describe p) p path via (conj in i) (seq data)) - {path {:reason "Extra input" - :pred (abbrev (op-describe re)) - :val data - :via via - :in (conj in i)}}) + [{:path path + :reason "Extra input" + :pred (abbrev (op-describe re)) + :val data + :via via + :in (conj in i)}]) (c/or (op-explain (op-describe p) p path via (conj in i) (seq data)) - {path {:reason "Extra input" - :pred (abbrev (op-describe p)) - :val data - :via via - :in (conj in i)}})))))) + [{:path path + :reason "Extra input" + :pred (abbrev (op-describe p)) + :val data + :via via + :in (conj in i)}])))))) (defn ^:skip-wiki regex-spec-impl "Do not call this directly, use 'spec' with a regex op argument" @@ -1632,7 +1678,7 @@ unstrumented." (explain* [_ path via in x] (if (c/or (nil? x) (coll? x)) (re-explain path via in re (seq x)) - {path {:pred (abbrev (op-describe re)) :val x :via via :in in}})) + [{:path path :pred (abbrev (op-describe re)) :val x :via via :in in}])) (gen* [_ overrides path rmap] (if gfn (gfn) @@ -1685,7 +1731,7 @@ unstrumented." (let [ret (try (apply f args) (catch Throwable t t))] (if (instance? Throwable ret) ;;TODO add exception data - {path {:pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via :in in}} + [{:path path :pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via :in in}] (let [cret (dt retspec ret rform)] (if (= ::invalid cret) @@ -1693,7 +1739,7 @@ unstrumented." (when fnspec (let [cargs (conform argspec args)] (explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret}))))))))) - {path {:pred 'ifn? :val f :via via :in in}})) + [{:path path :pred 'ifn? :val f :via via :in in}])) (gen* [_ overrides _ _] (if gfn (gfn) (gen/return diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 82e5aacd92..6c68bcba77 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -23,7 +23,11 @@ m1) (= m1 m2))) -(deftest conform-explain +(defn- ne [probs] + (let [[path prob] (first probs)] + [(assoc prob :path path)])) + +#_(deftest conform-explain (let [a (s/and #(> % 5) #(< % 10)) o (s/or :s string? :k keyword?) c (s/cat :a string? :b keyword?) @@ -47,77 +51,77 @@ lrange 7 7 nil lrange 8 8 nil - lrange 42 ::s/invalid {[] {:pred '(int-in-range? 7 42 %), :val 42, :via [], :in []}} + lrange 42 ::s/invalid [{:path [] :pred '(int-in-range? 7 42 %), :val 42, :via [], :in []}] - irange #inst "1938" ::s/invalid {[] {:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1938", :via [], :in []}} + irange #inst "1938" ::s/invalid [{:path [] :pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1938", :via [], :in []}] irange #inst "1942" #inst "1942" nil - irange #inst "1946" ::s/invalid {[] {:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1946", :via [], :in []}} + irange #inst "1946" ::s/invalid [{:path [] :pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1946", :via [], :in []}] - drange 3.0 ::s/invalid {[] {:pred '(<= 3.1 %), :val 3.0, :via [], :in []}} + drange 3.0 ::s/invalid [{:path [] :pred '(<= 3.1 %), :val 3.0, :via [], :in []}] drange 3.1 3.1 nil drange 3.2 3.2 nil - drange Double/POSITIVE_INFINITY ::s/invalid {[] {:pred '(not (isInfinite %)), :val Double/POSITIVE_INFINITY, :via [], :in []}} + drange Double/POSITIVE_INFINITY ::s/invalid [ {:path [] :pred '(not (isInfinite %)), :val Double/POSITIVE_INFINITY, :via [], :in []}] ;; can't use equality-based test for Double/NaN ;; drange Double/NaN ::s/invalid {[] {:pred '(not (isNaN %)), :val Double/NaN, :via [], :in []}} keyword? :k :k nil - keyword? nil ::s/invalid {[] {:pred ::s/unknown :val nil :via []}} - keyword? "abc" ::s/invalid {[] {:pred ::s/unknown :val "abc" :via []}} + keyword? nil ::s/invalid (ne {[] {:pred ::s/unknown :val nil :via []}}) + keyword? "abc" ::s/invalid (ne {[] {:pred ::s/unknown :val "abc" :via []}}) a 6 6 nil - a 3 ::s/invalid '{[] {:pred (> % 5), :val 3 :via []}} - a 20 ::s/invalid '{[] {:pred (< % 10), :val 20 :via []}} + a 3 ::s/invalid (ne '{[] {:pred (> % 5), :val 3 :via []}}) + a 20 ::s/invalid (ne '{[] {:pred (< % 10), :val 20 :via []}}) a nil "java.lang.NullPointerException" "java.lang.NullPointerException" a :k "java.lang.ClassCastException" "java.lang.ClassCastException" o "a" [:s "a"] nil o :a [:k :a] nil - o 'a ::s/invalid '{[:s] {:pred string?, :val a :via []}, [:k] {:pred keyword?, :val a :via []}} + o 'a ::s/invalid (ne '{[:s] {:pred string?, :val a :via []}, [:k] {:pred keyword?, :val a :via []}}) - c nil ::s/invalid '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}} - c [] ::s/invalid '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}} - c [:a] ::s/invalid '{[:a] {:pred string?, :val :a, :via []}} - c ["a"] ::s/invalid '{[:b] {:reason "Insufficient input", :pred keyword?, :val (), :via []}} + c nil ::s/invalid (ne '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}}) + c [] ::s/invalid (ne '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}}) + c [:a] ::s/invalid (ne '{[:a] {:pred string?, :val :a, :via []}}) + c ["a"] ::s/invalid (ne '{[:b] {:reason "Insufficient input", :pred keyword?, :val (), :via []}}) c ["s" :k] '{:a "s" :b :k} nil - c ["s" :k 5] ::s/invalid '{[] {:reason "Extra input", :pred (cat :a string? :b keyword?), :val (5), :via []}} + c ["s" :k 5] ::s/invalid (ne '{[] {:reason "Extra input", :pred (cat :a string? :b keyword?), :val (5), :via []}}) (s/cat) nil {} nil - (s/cat) [5] ::s/invalid '{[] {:reason "Extra input", :pred (cat), :val (5), :via [], :in [0]}} + (s/cat) [5] ::s/invalid (ne '{[] {:reason "Extra input", :pred (cat), :val (5), :via [], :in [0]}}) - either nil ::s/invalid '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}} - either [] ::s/invalid '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}} + either nil ::s/invalid (ne '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}}) + either [] ::s/invalid (ne '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}}) either [:k] [:b :k] nil either ["s"] [:a "s"] nil - either [:b "s"] ::s/invalid '{[] {:reason "Extra input", :pred (alt :a string? :b keyword?), :val ("s") :via []}} + either [:b "s"] ::s/invalid (ne '{[] {:reason "Extra input", :pred (alt :a string? :b keyword?), :val ("s") :via []}}) star nil [] nil star [] [] nil star [:k] [:k] nil star [:k1 :k2] [:k1 :k2] nil - star [:k1 :k2 "x"] ::s/invalid '{[] {:pred keyword?, :val "x" :via []}} - star ["a"] ::s/invalid {[] '{:pred keyword?, :val "a" :via []}} + star [:k1 :k2 "x"] ::s/invalid (ne '{[] {:pred keyword?, :val "x" :via []}}) + star ["a"] ::s/invalid (ne {[] '{:pred keyword?, :val "a" :via []}}) - plus nil ::s/invalid '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}} - plus [] ::s/invalid '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}} + plus nil ::s/invalid (ne '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}}) + plus [] ::s/invalid (ne '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}}) plus [:k] [:k] nil plus [:k1 :k2] [:k1 :k2] nil - plus [:k1 :k2 "x"] ::s/invalid '{[] {:pred keyword?, :val "x", :via [], :in [2]}} - plus ["a"] ::s/invalid '{[] {:pred keyword?, :val "a" :via []}} + plus [:k1 :k2 "x"] ::s/invalid (ne '{[] {:pred keyword?, :val "x", :via [], :in [2]}}) + plus ["a"] ::s/invalid (ne '{[] {:pred keyword?, :val "a" :via []}}) opt nil nil nil opt [] nil nil - opt :k ::s/invalid '{[] {:pred (? keyword?), :val :k, :via []}} + opt :k ::s/invalid (ne '{[] {:pred (? keyword?), :val :k, :via []}}) opt [:k] :k nil - opt [:k1 :k2] ::s/invalid '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2), :via []}} - opt [:k1 :k2 "x"] ::s/invalid '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2 "x"), :via []}} - opt ["a"] ::s/invalid '{[] {:pred keyword?, :val "a", :via []}} + opt [:k1 :k2] ::s/invalid (ne '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2), :via []}}) + opt [:k1 :k2 "x"] ::s/invalid (ne '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2 "x"), :via []}}) + opt ["a"] ::s/invalid (ne '{[] {:pred keyword?, :val "a", :via []}}) andre nil nil nil andre [] nil nil - andre :k :clojure.spec/invalid '{[] {:pred (& (* keyword?) even-count?), :val :k, :via []}} - andre [:k] ::s/invalid '{[] {:pred even-count?, :val [:k], :via []}} + andre :k :clojure.spec/invalid (ne '{[] {:pred (& (* keyword?) even-count?), :val :k, :via []}}) + andre [:k] ::s/invalid (ne '{[] {:pred even-count?, :val [:k], :via []}}) andre [:j :k] [:j :k] nil - m nil ::s/invalid '{[] {:pred map?, :val nil, :via []}} + m nil ::s/invalid (ne '{[] {:pred map?, :val nil, :via []}}) m {} {} nil m {:a "b"} {:a "b"} nil From bacc9a1bdfc425fc9e2eedf5dd442befb05812d2 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Fri, 24 Jun 2016 15:52:59 -0400 Subject: [PATCH 241/854] separate what-to-test from test Signed-off-by: Stuart Halloway Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 69 +++++++++++++++-------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index d2f7a00a63..f7864392ab 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -107,12 +107,14 @@ with explain-data under ::check-call." (list x) x)) -;; duped from spec to avoid introducing public API -(defn- ns-matcher - [ns-syms] - (let [ns-names (into #{} (map str) ns-syms)] +(defn- sym-matcher + "Returns a fn that matches symbols that are either in syms, +or whose namespace is in syms." + [syms] + (let [names (into #{} (map str) syms)] (fn [s] - (contains? ns-names (namespace s))))) + (or (contains? names (namespace s)) + (contains? names (str s)))))) (defn- sym->test-map [s] @@ -125,6 +127,24 @@ with explain-data under ::check-call." [opts] (assert (every? ident? (keys (:gen opts))) "test :gen expects ident keys")) +(defn syms-to-test + "Returns a coll of registered syms matching ns-or-names (a symbol or +collection of symbols). + +A symbol matches ns-or-names if ns-or-names includes either the symbol +itself or the symbol's namespace symbol. + +If no ns-or-names specified, returns all registered syms." + ([] (sequence + (filter symbol?) + (keys (s/registry)))) + ([ns-or-names] + (let [match? (sym-matcher (collectionize ns-or-names))] + (sequence + (comp (filter symbol?) + (filter match?)) + (keys (s/registry)))))) + (defn test-fn "Runs generative tests for fn f using spec and opts. See 'test' for options and return." @@ -134,8 +154,7 @@ with explain-data under ::check-call." (test-1 {:f f :spec spec} opts))) (defn test - "Checks specs for fns named by sym-or-syms (a symbol or -collection of symbols) using test.check. + "Checks specs for vars named by syms using test.check. The opts map includes the following optional keys, where stc aliases clojure.spec.test.check: @@ -171,40 +190,10 @@ Values for the :type key can be one of :no-gen unable to generate :args :no-fn unable to resolve fn to test " - ([sym-or-syms] (test sym-or-syms nil)) - ([sym-or-syms opts] - (validate-opts opts) - (->> (eduction - (map sym->test-map) - (collectionize sym-or-syms)) - (pmap #(test-1 % opts))))) - -(defn test-ns - "Like test, but scoped to specific namespaces, or to -*ns* if no arg specified." - ([] (test-ns (.name ^clojure.lang.Namespace *ns*))) - ([ns-or-nses] (test-ns ns-or-nses nil)) - ([ns-or-nses opts] - (validate-opts opts) - (let [ns-match? (ns-matcher (collectionize ns-or-nses))] - (->> (eduction - (filter symbol?) - (filter ns-match?) - (map sym->test-map) - (keys (s/registry))) - (pmap #(test-1 % opts)))))) - -(defn test-all - "Like test, but tests all vars named by fn-specs in the spec -registry." - ([] (test-all nil)) - ([opts] + ([syms] (test syms nil)) + ([syms opts] (validate-opts opts) - (->> (eduction - (filter symbol?) - (map sym->test-map) - (keys (s/registry))) - (pmap #(test-1 % opts))))) + (pmap #(test-1 (sym->test-map %) opts) syms))) (defn abbrev-result "Given a test result, returns an abbreviated version From 20ade931fc115c6384e906f9fd4fce112db42363 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Mon, 27 Jun 2016 10:55:02 -0400 Subject: [PATCH 242/854] move instrument from spec to spec.test Signed-off-by: Stuart Halloway Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 219 ---------------------------------- src/clj/clojure/spec/test.clj | 217 +++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 219 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 9f7d78391a..720da41519 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -34,10 +34,6 @@ "The number of errors reported by explain in a collection spec'ed with 'every'" 20) -(def ^:private ^:dynamic *instrument-enabled* - "if false, instrumented fns call straight through" - true) - (defprotocol Spec (conform* [spec x]) (unform* [spec y]) @@ -608,44 +604,6 @@ [& preds] (assert (not (empty? preds))) `(tuple-impl '~(mapv res preds) ~(vec preds))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- expect - "Returns nil if v conforms to spec, else throws ex-info with explain-data." - [spec v] - ) - -(defn- fn-spec? - "Fn-spec must include at least :args or :ret specs." - [m] - (c/or (:args m) (:ret m))) - -(defmacro with-instrument-disabled - "Disables instrument's checking of calls, within a scope." - [& body] - `(binding [*instrument-enabled* nil] - ~@body)) - -(defn- spec-checking-fn - [v f fn-spec] - (let [fn-spec (maybe-spec fn-spec) - conform! (fn [v role spec data args] - (let [conformed (conform spec data)] - (if (= ::invalid conformed) - (let [ed (assoc (explain-data* spec [role] [] [] data) - ::args args)] - (throw (ex-info - (str "Call to " v " did not conform to spec:\n" (with-out-str (explain-out ed))) - ed))) - conformed)))] - (c/fn - [& args] - (if *instrument-enabled* - (with-instrument-disabled - (when (:args fn-spec) (conform! v :args (:args fn-spec) args args)) - (binding [*instrument-enabled* true] - (.applyTo ^clojure.lang.IFn f args))) - (.applyTo ^clojure.lang.IFn f args))))) (defn- macroexpand-check [v args] @@ -695,183 +653,6 @@ [fn-sym & specs] `(clojure.spec/def ~fn-sym (clojure.spec/fspec ~@specs))) -(defn- no-fn-spec - [v spec] - (ex-info (str "Fn at " v " is not spec'ed.") - {:var v :spec spec})) - -(def ^:private instrumented-vars - "Map for instrumented vars to :raw/:wrapped fns" - (atom {})) - -(defn- ->var - [s-or-v] - (if (var? s-or-v) - s-or-v - (let [v (c/and (symbol? s-or-v) (resolve s-or-v))] - (if (var? v) - v - (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) - -(defn- instrument-choose-fn - "Helper for instrument." - [f spec sym {over :gen :keys [stub replace]}] - (if (some #{sym} stub) - (-> spec (gen over) gen/generate) - (get replace sym f))) - -(defn- instrument-choose-spec - "Helper for instrument" - [spec sym {overrides :spec}] - (get overrides sym spec)) - -(defn- collectionize - [x] - (if (symbol? x) - (list x) - x)) - -(defn- instrument-1 - [s opts] - (when-let [v (resolve s)] - (let [spec (get-spec v) - {:keys [raw wrapped]} (get @instrumented-vars v) - current @v - to-wrap (if (= wrapped current) raw current) - ospec (c/or (instrument-choose-spec spec s opts) - (throw (no-fn-spec v spec))) - ofn (instrument-choose-fn to-wrap ospec s opts) - checked (spec-checking-fn v ofn ospec)] - (alter-var-root v (constantly checked)) - (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked})) - (->sym v))) - -(defn- unstrument-1 - [s] - (when-let [v (resolve s)] - (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] - (let [current @v] - (when (= wrapped current) - (alter-var-root v (constantly raw)))) - (swap! instrumented-vars dissoc v)) - (->sym v))) - -(defn- opt-syms - "Returns set of symbols referenced by 'instrument' opts map" - [opts] - (reduce into #{} [(:stub opts) (c/keys (:replace opts)) (c/keys (:spec opts))])) - -(defn- sym-matcher - "Returns a fn that matches symbols that are either in syms, -or whose namespace is in syms." - [syms] - (let [names (into #{} (map str) syms)] - (fn [s] - (c/or (contains? names (namespace s)) - (contains? names (str s)))))) - -(defn- validate-opts - [opts] - (assert (every? ident? (c/keys (:gen opts))) "instrument :gen expects ident keys")) - -(defn instrument - "Instruments the vars matched by ns-or-names, a symbol or a -collection of symbols. Instruments the current namespace if -ns-or-names not specified. Idempotent. - -A var matches ns-or-names if ns-or-names includes either the var's -fully qualified name or the var's namespace. - -If a var has an :args fn-spec, sets the var's root binding to a -fn that checks arg conformance (throwing an exception on failure) -before delegating to the original fn. - -The opts map can be used to override registered specs, and/or to -replace fn implementations entirely. Opts for symbols not matched -by ns-or-names are ignored. This facilitates sharing a common -options map across many different calls to instrument. - -The opts map may have the following keys: - - :spec a map from var-name symbols to override specs - :stub a collection of var-name symbols to be replaced by stubs - :gen a map from spec names to generator overrides - :replace a map from var-name symbols to replacement fns - -:spec overrides registered fn-specs with specs your provide. Use -:spec overrides to provide specs for libraries that do not have -them, or to constrain your own use of a fn to a subset of its -spec'ed contract. - -:stub replaces a fn with a stub that checks :args, then uses the -:ret spec to generate a return value. - -:gen overrides are used only for :stub generation. - -:replace replaces a fn with a fn that checks args conformance, then -invokes the fn you provide, enabling arbitrary stubbing and mocking. - -:spec can be used in combination with :stub or :replace. - -Returns a collection of syms naming the vars instrumented." - ([] (instrument (.name ^clojure.lang.Namespace *ns*))) - ([ns-or-names] (instrument ns-or-names nil)) - ([ns-or-names opts] - (validate-opts opts) - (let [match? (sym-matcher (collectionize ns-or-names))] - (locking instrumented-vars - (into - [] - (comp c/cat - (filter symbol?) - (filter match?) - (distinct) - (map #(instrument-1 % opts)) - (remove nil?)) - [(c/keys (registry)) (opt-syms opts)]))))) - -(defn unstrument - "Undoes instrument on the vars matched by ns-or-names, specified -as in instrument. Returns a collection of syms naming the vars -unstrumented." - ([] (unstrument (.name ^clojure.lang.Namespace *ns*))) - ([ns-or-names] - (let [match? (sym-matcher (collectionize ns-or-names))] - (locking instrumented-vars - (into - [] - (comp (map ->sym) - (filter match?) - (map unstrument-1) - (remove nil?)) - (c/keys @instrumented-vars)))))) - -(defn instrument-all - "Like instrument, but works on all vars." - ([] (instrument-all nil)) - ([opts] - (validate-opts opts) - (locking instrumented-vars - (into - [] - (comp c/cat - (filter symbol?) - (distinct) - (map #(instrument-1 % opts)) - (remove nil?)) - [(c/keys (registry)) (opt-syms opts)])))) - -(defn unstrument-all - "Like unstrument, but works on all vars." - [] - (locking instrumented-vars - (into - [] - (comp (map ->sym) - (map unstrument-1) - (remove nil?)) - (c/keys @instrumented-vars)))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- recur-limit? [rmap id path k] (c/and (> (get rmap id) (::recursion-limit rmap)) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index f7864392ab..8777252714 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -17,6 +17,223 @@ (in-ns 'clojure.spec.test) (alias 'stc 'clojure.spec.test.check) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def ^:private ^:dynamic *instrument-enabled* + "if false, instrumented fns call straight through" + true) + +(defn- fn-spec? + "Fn-spec must include at least :args or :ret specs." + [m] + (or (:args m) (:ret m))) + +(defmacro with-instrument-disabled + "Disables instrument's checking of calls, within a scope." + [& body] + `(binding [*instrument-enabled* nil] + ~@body)) + +(defn- spec-checking-fn + [v f fn-spec] + (let [fn-spec (@#'s/maybe-spec fn-spec) + conform! (fn [v role spec data args] + (let [conformed (s/conform spec data)] + (if (= ::s/invalid conformed) + (let [ed (assoc (s/explain-data* spec [role] [] [] data) + ::s/args args)] + (throw (ex-info + (str "Call to " v " did not conform to spec:\n" (with-out-str (s/explain-out ed))) + ed))) + conformed)))] + (fn + [& args] + (if *instrument-enabled* + (with-instrument-disabled + (when (:args fn-spec) (conform! v :args (:args fn-spec) args args)) + (binding [*instrument-enabled* true] + (.applyTo ^clojure.lang.IFn f args))) + (.applyTo ^clojure.lang.IFn f args))))) + + +(defn- no-fn-spec + [v spec] + (ex-info (str "Fn at " v " is not spec'ed.") + {:var v :spec spec})) + +(def ^:private instrumented-vars + "Map for instrumented vars to :raw/:wrapped fns" + (atom {})) + +(defn- ->var + [s-or-v] + (if (var? s-or-v) + s-or-v + (let [v (and (symbol? s-or-v) (resolve s-or-v))] + (if (var? v) + v + (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) + +(defn- instrument-choose-fn + "Helper for instrument." + [f spec sym {over :gen :keys [stub replace]}] + (if (some #{sym} stub) + (-> spec (s/gen over) gen/generate) + (get replace sym f))) + +(defn- instrument-choose-spec + "Helper for instrument" + [spec sym {overrides :spec}] + (get overrides sym spec)) + +(defn- collectionize + [x] + (if (symbol? x) + (list x) + x)) + +(def ->sym @#'s/->sym) + +(defn- instrument-1 + [s opts] + (when-let [v (resolve s)] + (let [spec (s/get-spec v) + {:keys [raw wrapped]} (get @instrumented-vars v) + current @v + to-wrap (if (= wrapped current) raw current) + ospec (or (instrument-choose-spec spec s opts) + (throw (no-fn-spec v spec))) + ofn (instrument-choose-fn to-wrap ospec s opts) + checked (spec-checking-fn v ofn ospec)] + (alter-var-root v (constantly checked)) + (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked})) + (->sym v))) + +(defn- unstrument-1 + [s] + (when-let [v (resolve s)] + (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] + (let [current @v] + (when (= wrapped current) + (alter-var-root v (constantly raw)))) + (swap! instrumented-vars dissoc v)) + (->sym v))) + +(defn- opt-syms + "Returns set of symbols referenced by 'instrument' opts map" + [opts] + (reduce into #{} [(:stub opts) (keys (:replace opts)) (keys (:spec opts))])) + +(defn- sym-matcher + "Returns a fn that matches symbols that are either in syms, +or whose namespace is in syms." + [syms] + (let [names (into #{} (map str) syms)] + (fn [s] + (or (contains? names (namespace s)) + (contains? names (str s)))))) + +(defn- validate-opts + [opts] + (assert (every? ident? (keys (:gen opts))) "instrument :gen expects ident keys")) + +(defn instrument + "Instruments the vars matched by ns-or-names, a symbol or a +collection of symbols. Instruments the current namespace if +ns-or-names not specified. Idempotent. + +A var matches ns-or-names if ns-or-names includes either the var's +fully qualified name or the var's namespace. + +If a var has an :args fn-spec, sets the var's root binding to a +fn that checks arg conformance (throwing an exception on failure) +before delegating to the original fn. + +The opts map can be used to override registered specs, and/or to +replace fn implementations entirely. Opts for symbols not matched +by ns-or-names are ignored. This facilitates sharing a common +options map across many different calls to instrument. + +The opts map may have the following keys: + + :spec a map from var-name symbols to override specs + :stub a collection of var-name symbols to be replaced by stubs + :gen a map from spec names to generator overrides + :replace a map from var-name symbols to replacement fns + +:spec overrides registered fn-specs with specs your provide. Use +:spec overrides to provide specs for libraries that do not have +them, or to constrain your own use of a fn to a subset of its +spec'ed contract. + +:stub replaces a fn with a stub that checks :args, then uses the +:ret spec to generate a return value. + +:gen overrides are used only for :stub generation. + +:replace replaces a fn with a fn that checks args conformance, then +invokes the fn you provide, enabling arbitrary stubbing and mocking. + +:spec can be used in combination with :stub or :replace. + +Returns a collection of syms naming the vars instrumented." + ([] (instrument (.name ^clojure.lang.Namespace *ns*))) + ([ns-or-names] (instrument ns-or-names nil)) + ([ns-or-names opts] + (validate-opts opts) + (let [match? (sym-matcher (collectionize ns-or-names))] + (locking instrumented-vars + (into + [] + (comp cat + (filter symbol?) + (filter match?) + (distinct) + (map #(instrument-1 % opts)) + (remove nil?)) + [(keys (s/registry)) (opt-syms opts)]))))) + +(defn unstrument + "Undoes instrument on the vars matched by ns-or-names, specified +as in instrument. Returns a collection of syms naming the vars +unstrumented." + ([] (unstrument (.name ^clojure.lang.Namespace *ns*))) + ([ns-or-names] + (let [match? (sym-matcher (collectionize ns-or-names))] + (locking instrumented-vars + (into + [] + (comp (map ->sym) + (filter match?) + (map unstrument-1) + (remove nil?)) + (keys @instrumented-vars)))))) + +(defn instrument-all + "Like instrument, but works on all vars." + ([] (instrument-all nil)) + ([opts] + (validate-opts opts) + (locking instrumented-vars + (into + [] + (comp cat + (filter symbol?) + (distinct) + (map #(instrument-1 % opts)) + (remove nil?)) + [(keys (s/registry)) (opt-syms opts)])))) + +(defn unstrument-all + "Like unstrument, but works on all vars." + [] + (locking instrumented-vars + (into + [] + (comp (map ->sym) + (map unstrument-1) + (remove nil?)) + (keys @instrumented-vars)))) + (defn- explain-test [args spec v role] (ex-info From 5ff3cb6658511672acab1dce24994b732c28a6b8 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Mon, 27 Jun 2016 11:36:39 -0400 Subject: [PATCH 243/854] unstrument var under test for duration of test Signed-off-by: Stuart Halloway Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index 8777252714..b211401f3f 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -305,17 +305,21 @@ with explain-data under ::check-call." (assoc result :type (result-type result)))) (defn- test-1 - [{:keys [s f spec]} {:keys [result-callback] :as opts}] - (cond - (nil? f) - {:type :no-fn :sym s :spec spec} - - (:args spec) - (let [tcret (check-fn f spec opts)] - (make-test-result s spec tcret)) - - :default - {:type :no-argspec :sym s :spec spec})) + [{:keys [s f v spec]} {:keys [result-callback] :as opts}] + (when v (unstrument s)) + (try + (cond + (nil? f) + {:type :no-fn :sym s :spec spec} + + (:args spec) + (let [tcret (check-fn f spec opts)] + (make-test-result s spec tcret)) + + :default + {:type :no-argspec :sym s :spec spec}) + (finally + (when v (instrument s))))) ;; duped from spec to avoid introducing public API (defn- collectionize @@ -337,7 +341,7 @@ or whose namespace is in syms." [s] (let [v (resolve s)] {:s s - :f (when v @v) + :v v :spec (when v (s/get-spec v))})) (defn- validate-opts From a4477453db5b195dd6d1041f1da31c75af21c939 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Mon, 27 Jun 2016 15:56:25 -0400 Subject: [PATCH 244/854] separate enumeration from instrument and test Signed-off-by: Stuart Halloway Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 274 +++++++++++++++------------------- 1 file changed, 121 insertions(+), 153 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index b211401f3f..584d7d6a74 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -17,7 +17,44 @@ (in-ns 'clojure.spec.test) (alias 'stc 'clojure.spec.test.check) +(defn- throwable? + [x] + (instance? Throwable x)) + +(defn ->sym + [x] + (@#'s/->sym x)) + +(defn- ->var + [s-or-v] + (if (var? s-or-v) + s-or-v + (let [v (and (symbol? s-or-v) (resolve s-or-v))] + (if (var? v) + v + (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) + +(defn- collectionize + [x] + (if (symbol? x) + (list x) + x)) + +(defn enumerate-namespace + "Given a symbol naming an ns, or a collection of such symbols, +returns the set of all symbols naming vars in those nses." + [ns-sym-or-syms] + (into + #{} + (mapcat (fn [ns-sym] + (map + (fn [name-sym] + (symbol (name ns-sym) (name name-sym))) + (keys (ns-interns ns-sym))))) + (collectionize ns-sym-or-syms))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (def ^:private ^:dynamic *instrument-enabled* "if false, instrumented fns call straight through" true) @@ -54,7 +91,6 @@ (.applyTo ^clojure.lang.IFn f args))) (.applyTo ^clojure.lang.IFn f args))))) - (defn- no-fn-spec [v spec] (ex-info (str "Fn at " v " is not spec'ed.") @@ -64,15 +100,6 @@ "Map for instrumented vars to :raw/:wrapped fns" (atom {})) -(defn- ->var - [s-or-v] - (if (var? s-or-v) - s-or-v - (let [v (and (symbol? s-or-v) (resolve s-or-v))] - (if (var? v) - v - (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) - (defn- instrument-choose-fn "Helper for instrument." [f spec sym {over :gen :keys [stub replace]}] @@ -85,14 +112,6 @@ [spec sym {overrides :spec}] (get overrides sym spec)) -(defn- collectionize - [x] - (if (symbol? x) - (list x) - x)) - -(def ->sym @#'s/->sym) - (defn- instrument-1 [s opts] (when-let [v (resolve s)] @@ -123,40 +142,39 @@ [opts] (reduce into #{} [(:stub opts) (keys (:replace opts)) (keys (:spec opts))])) -(defn- sym-matcher - "Returns a fn that matches symbols that are either in syms, -or whose namespace is in syms." - [syms] - (let [names (into #{} (map str) syms)] - (fn [s] - (or (contains? names (namespace s)) - (contains? names (str s)))))) +(defn- fn-spec-name? + [s] + (symbol? s)) -(defn- validate-opts - [opts] - (assert (every? ident? (keys (:gen opts))) "instrument :gen expects ident keys")) +(defn instrumentable-syms + "Given an opts map as per instrument, returns the set of syms +that can be instrumented." + ([] (instrumentable-syms nil)) + ([opts] + (assert (every? ident? (keys (:gen opts))) "instrument :gen expects ident keys") + (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) + (keys (:spec opts)) + (:stub opts) + (keys (:replace opts))]))) (defn instrument - "Instruments the vars matched by ns-or-names, a symbol or a -collection of symbols. Instruments the current namespace if -ns-or-names not specified. Idempotent. - -A var matches ns-or-names if ns-or-names includes either the var's -fully qualified name or the var's namespace. + "Instruments the vars named by sym-or-syms, a symbol or collection +of symbols, or all instrumentable vars if sym-or-syms is not +specified. If a var has an :args fn-spec, sets the var's root binding to a fn that checks arg conformance (throwing an exception on failure) before delegating to the original fn. The opts map can be used to override registered specs, and/or to -replace fn implementations entirely. Opts for symbols not matched -by ns-or-names are ignored. This facilitates sharing a common +replace fn implementations entirely. Opts for symbols not included +in sym-or-syms are ignored. This facilitates sharing a common options map across many different calls to instrument. The opts map may have the following keys: :spec a map from var-name symbols to override specs - :stub a collection of var-name symbols to be replaced by stubs + :stub a set of var-name symbols to be replaced by stubs :gen a map from spec names to generator overrides :replace a map from var-name symbols to replacement fns @@ -176,63 +194,33 @@ invokes the fn you provide, enabling arbitrary stubbing and mocking. :spec can be used in combination with :stub or :replace. Returns a collection of syms naming the vars instrumented." - ([] (instrument (.name ^clojure.lang.Namespace *ns*))) - ([ns-or-names] (instrument ns-or-names nil)) - ([ns-or-names opts] - (validate-opts opts) - (let [match? (sym-matcher (collectionize ns-or-names))] - (locking instrumented-vars - (into - [] - (comp cat - (filter symbol?) - (filter match?) - (distinct) - (map #(instrument-1 % opts)) - (remove nil?)) - [(keys (s/registry)) (opt-syms opts)]))))) - -(defn unstrument - "Undoes instrument on the vars matched by ns-or-names, specified -as in instrument. Returns a collection of syms naming the vars -unstrumented." - ([] (unstrument (.name ^clojure.lang.Namespace *ns*))) - ([ns-or-names] - (let [match? (sym-matcher (collectionize ns-or-names))] - (locking instrumented-vars - (into - [] - (comp (map ->sym) - (filter match?) - (map unstrument-1) - (remove nil?)) - (keys @instrumented-vars)))))) - -(defn instrument-all - "Like instrument, but works on all vars." - ([] (instrument-all nil)) - ([opts] - (validate-opts opts) + ([] (instrument (instrumentable-syms))) + ([sym-or-syms] (instrument sym-or-syms nil)) + ([sym-or-syms opts] (locking instrumented-vars (into [] - (comp cat - (filter symbol?) + (comp (filter (instrumentable-syms opts)) (distinct) (map #(instrument-1 % opts)) (remove nil?)) - [(keys (s/registry)) (opt-syms opts)])))) - -(defn unstrument-all - "Like unstrument, but works on all vars." - [] - (locking instrumented-vars - (into - [] - (comp (map ->sym) - (map unstrument-1) - (remove nil?)) - (keys @instrumented-vars)))) + (collectionize sym-or-syms))))) + +(defn unstrument + "Undoes instrument on the vars named by sym-or-syms, specified +as in instrument. With no args, unstruments all instrumented vars. +Returns a collection of syms naming the vars unstrumented." + ([] (unstrument (map ->sym (keys @instrumented-vars)))) + ([sym-or-syms] + (locking instrumented-vars + (into + [] + (comp (filter symbol?) + (map unstrument-1) + (remove nil?)) + (collectionize sym-or-syms))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; testing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- explain-test [args spec v role] @@ -260,10 +248,6 @@ with explain-data under ::check-call." (explain-test args (:fn specs) {:args cargs :ret cret} :fn)) true)))))) -(defn- throwable? - [x] - (instance? Throwable x)) - (defn- check-fn [f specs {gen :gen opts ::stc/opts}] (let [{:keys [num-tests] :or {num-tests 100}} opts @@ -308,35 +292,20 @@ with explain-data under ::check-call." [{:keys [s f v spec]} {:keys [result-callback] :as opts}] (when v (unstrument s)) (try - (cond - (nil? f) - {:type :no-fn :sym s :spec spec} + (let [f (or f (when v @v))] + (cond + (nil? f) + {:type :no-fn :sym s :spec spec} - (:args spec) - (let [tcret (check-fn f spec opts)] - (make-test-result s spec tcret)) + (:args spec) + (let [tcret (check-fn f spec opts)] + (make-test-result s spec tcret)) - :default - {:type :no-argspec :sym s :spec spec}) + :default + {:type :no-argspec :sym s :spec spec})) (finally (when v (instrument s))))) -;; duped from spec to avoid introducing public API -(defn- collectionize - [x] - (if (symbol? x) - (list x) - x)) - -(defn- sym-matcher - "Returns a fn that matches symbols that are either in syms, -or whose namespace is in syms." - [syms] - (let [names (into #{} (map str) syms)] - (fn [s] - (or (contains? names (namespace s)) - (contains? names (str s)))))) - (defn- sym->test-map [s] (let [v (resolve s)] @@ -344,38 +313,31 @@ or whose namespace is in syms." :v v :spec (when v (s/get-spec v))})) -(defn- validate-opts +(defn- validate-test-opts [opts] (assert (every? ident? (keys (:gen opts))) "test :gen expects ident keys")) -(defn syms-to-test - "Returns a coll of registered syms matching ns-or-names (a symbol or -collection of symbols). - -A symbol matches ns-or-names if ns-or-names includes either the symbol -itself or the symbol's namespace symbol. - -If no ns-or-names specified, returns all registered syms." - ([] (sequence - (filter symbol?) - (keys (s/registry)))) - ([ns-or-names] - (let [match? (sym-matcher (collectionize ns-or-names))] - (sequence - (comp (filter symbol?) - (filter match?)) - (keys (s/registry)))))) - (defn test-fn "Runs generative tests for fn f using spec and opts. See 'test' for options and return." ([f spec] (test-fn f spec nil)) ([f spec opts] - (validate-opts opts) + (validate-test-opts opts) (test-1 {:f f :spec spec} opts))) +(defn testable-syms + "Given an opts map as per test, returns the set of syms that +can be tested." + ([] (testable-syms nil)) + ([opts] + (validate-test-opts opts) + (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) + (keys (:spec opts))]))) + (defn test - "Checks specs for vars named by syms using test.check. + "Run generative tests for spec conformance on vars named by +sym-or-syms, a symbol or collection of symbols. If sym-or-syms +is not specified, test all testable vars. The opts map includes the following optional keys, where stc aliases clojure.spec.test.check: @@ -411,10 +373,15 @@ Values for the :type key can be one of :no-gen unable to generate :args :no-fn unable to resolve fn to test " - ([syms] (test syms nil)) - ([syms opts] - (validate-opts opts) - (pmap #(test-1 (sym->test-map %) opts) syms))) + ([] (test (testable-syms))) + ([sym-or-syms] (test sym-or-syms nil)) + ([sym-or-syms opts] + (->> (collectionize sym-or-syms) + (filter (testable-syms opts)) + (pmap + #(test-1 (sym->test-map %) opts))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; test reporting ;;;;;;;;;;;;;;;;;;;;;;;; (defn abbrev-result "Given a test result, returns an abbreviated version @@ -425,20 +392,21 @@ suitable for summary use." (update (dissoc x ::stc/ret) :spec s/describe))) (defn summarize-results - "Given a collection of test-results, e.g. from 'test', -pretty prints the abbrev-result of each. + "Given a collection of test-results, e.g. from 'test', pretty +prints the summary-result (default abbrev-result) of each. Returns a map with :total, the total number of results, plus a key with a count for each different :type of result." - [test-results] - (reduce - (fn [summary result] - (pp/pprint (abbrev-result result)) - (-> summary - (update :total inc) - (update (:type result) (fnil inc 0)))) - {:total 0} - test-results)) + ([test-results] (summarize-results test-results abbrev-result)) + ([test-results summary-result] + (reduce + (fn [summary result] + (pp/pprint (summary-result result)) + (-> summary + (update :total inc) + (update (:type result) (fnil inc 0)))) + {:total 0} + test-results))) From 75da0465f300519a74f6fa13582ea8eb8877b148 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Mon, 27 Jun 2016 16:02:43 -0400 Subject: [PATCH 245/854] dump training wheel tests Signed-off-by: Rich Hickey --- test/clojure/test_clojure/spec.clj | 73 ------------------------------ 1 file changed, 73 deletions(-) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 6c68bcba77..cabed04ea2 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -132,82 +132,9 @@ ;;coll [:a "b"] ::s/invalid '{[] {:pred (coll-checker keyword?), :val [:a b], :via []}} ))) -(s/fdef flip-nums - :args (s/cat :arg1 integer? :arg2 integer?) - :ret vector? - :fn (fn [{:keys [args ret]}] - (= ret [(:arg2 args) (:arg1 args)]))) - -(def ^:dynamic *break-flip-nums* false) -(defn flip-nums - "Set *break-flip-nums* to break this fns compatibility with -its spec for test purposes." - [a b] - (if *break-flip-nums* - (when-not (= a b) - (vec (sort [a b]))) - [b a])) - -(defmacro get-ex-data - [x] - `(try - ~x - nil - (catch Throwable t# - (ex-data t#)))) - -;; Note the the complicated equality comparisons below are exactly the -;; kind of thing that spec helps you avoid, used here only because we -;; are near the bottom, testing spec itself. -(deftest test-instrument-flip-nums - (when-not (= "true" (System/getProperty "clojure.compiler.direct-linking")) - (binding [*break-flip-nums* true] - (try - (= [1 2] (flip-nums 2 1)) - (= [:a :b] (flip-nums :a :b)) - (= [1 2] (flip-nums 1 2)) - (is (nil? (flip-nums 1 1))) - (s/instrument `flip-nums) - (is (= [1 2] (flip-nums 2 1))) - (is (submap? '{:clojure.spec/problems {[:args :arg1] {:pred integer?, :val :a, :via []}}, :clojure.spec/args (:a :b)} - (get-ex-data (flip-nums :a :b)))) - (is (submap? '{:clojure.spec/problems {[:fn] {:pred (fn [{:keys [args ret]}] (= ret [(:arg2 args) (:arg1 args)])), :val {:args {:arg1 1, :arg2 2}, :ret [1 2]}, :via []}}, :clojure.spec/args (1 2)} - (get-ex-data (flip-nums 1 2)))) - (is (submap? '{:clojure.spec/problems {[:ret] {:pred vector?, :val nil, :via []}}, :clojure.spec/args (1 1)} - (get-ex-data (flip-nums 1 1)))) - (s/unstrument `flip-nums) - (= [1 2] (flip-nums 2 1)) - (= [:a :b] (flip-nums :a :b)) - (= [1 2] (flip-nums 1 2)) - (is (nil? (flip-nums 1 1))) - (s/unstrument `flip-nums))))) - -(def core-pred-syms - (into #{} - (comp (map first) (filter (fn [s] (.endsWith (name s) "?")))) - (ns-publics 'clojure.core))) - -(def generatable-core-pred-syms - (into #{} - (filter #(gen/gen-for-pred @ (resolve %))) - core-pred-syms)) - -(s/fdef generate-from-core-pred - :args (s/cat :s generatable-core-pred-syms) - :ret ::s/any - :fn (fn [{:keys [args ret]}] - (@(resolve (:s args)) ret))) - -(defn generate-from-core-pred - [s] - (gen/generate (gen/gen-for-pred @(resolve s)))) - (comment (require '[clojure.test :refer (run-tests)]) (in-ns 'clojure.test-clojure.spec) (run-tests) - (stest/run-all-tests) - (stest/check-var #'generate-from-core-pred :num-tests 10000) - ) From 20b877f0e7c0a40497c1ca8e1d9988bdf2a84587 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 28 Jun 2016 12:33:54 -0500 Subject: [PATCH 246/854] update test for explain-data Signed-off-by: Rich Hickey --- test/clojure/test_clojure/spec.clj | 98 +++++++++++++++++------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index cabed04ea2..b1de85d1ce 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -23,11 +23,7 @@ m1) (= m1 m2))) -(defn- ne [probs] - (let [[path prob] (first probs)] - [(assoc prob :path path)])) - -#_(deftest conform-explain +(deftest conform-explain (let [a (s/and #(> % 5) #(< % 10)) o (s/or :s string? :k keyword?) c (s/cat :a string? :b keyword?) @@ -37,7 +33,11 @@ opt (s/? keyword?) andre (s/& (s/* keyword?) even-count?) m (s/map-of keyword? string?) - coll (s/coll-of keyword? []) + mkeys (s/map-of (s/and keyword? (s/conformer name)) ::s/any) + mkeys2 (s/map-of (s/and keyword? (s/conformer name)) ::s/any :conform-keys true) + s (s/coll-of (s/spec (s/cat :tag keyword? :val ::s/any)) :kind ()) + v (s/coll-of keyword? :kind []) + coll (s/coll-of keyword?) lrange (s/int-in 7 42) drange (s/double-in :infinite? false :NaN? false :min 3.1 :max 3.2) irange (s/inst-in #inst "1939" #inst "1946") @@ -46,90 +46,104 @@ (let [co (result-or-ex (s/conform spec x)) e (result-or-ex (::s/problems (s/explain-data spec x)))] (when (not= conformed co) (println "conform fail\n\texpect=" conformed "\n\tactual=" co)) - (when (not (submap? ed e)) (println "explain fail\n\texpect=" ed "\n\tactual=" e)) - (and (= conformed co) (submap? ed e))) + (when (not (every? true? (map submap? ed e))) + (println "explain failures\n\texpect=" ed "\n\tactual failures=" e "\n\tsubmap?=" (map submap? ed e))) + (and (= conformed co) (every? true? (map submap? ed e)))) lrange 7 7 nil lrange 8 8 nil - lrange 42 ::s/invalid [{:path [] :pred '(int-in-range? 7 42 %), :val 42, :via [], :in []}] + lrange 42 ::s/invalid [{:pred '(int-in-range? 7 42 %), :val 42}] - irange #inst "1938" ::s/invalid [{:path [] :pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1938", :via [], :in []}] + irange #inst "1938" ::s/invalid [{:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1938"}] irange #inst "1942" #inst "1942" nil - irange #inst "1946" ::s/invalid [{:path [] :pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1946", :via [], :in []}] + irange #inst "1946" ::s/invalid [{:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1946"}] - drange 3.0 ::s/invalid [{:path [] :pred '(<= 3.1 %), :val 3.0, :via [], :in []}] + drange 3.0 ::s/invalid [{:pred '(<= 3.1 %), :val 3.0}] drange 3.1 3.1 nil drange 3.2 3.2 nil - drange Double/POSITIVE_INFINITY ::s/invalid [ {:path [] :pred '(not (isInfinite %)), :val Double/POSITIVE_INFINITY, :via [], :in []}] + drange Double/POSITIVE_INFINITY ::s/invalid [{:pred '(not (isInfinite %)), :val Double/POSITIVE_INFINITY}] ;; can't use equality-based test for Double/NaN - ;; drange Double/NaN ::s/invalid {[] {:pred '(not (isNaN %)), :val Double/NaN, :via [], :in []}} + ;; drange Double/NaN ::s/invalid {[] {:pred '(not (isNaN %)), :val Double/NaN}} keyword? :k :k nil - keyword? nil ::s/invalid (ne {[] {:pred ::s/unknown :val nil :via []}}) - keyword? "abc" ::s/invalid (ne {[] {:pred ::s/unknown :val "abc" :via []}}) + keyword? nil ::s/invalid [{:pred ::s/unknown :val nil}] + keyword? "abc" ::s/invalid [{:pred ::s/unknown :val "abc"}] a 6 6 nil - a 3 ::s/invalid (ne '{[] {:pred (> % 5), :val 3 :via []}}) - a 20 ::s/invalid (ne '{[] {:pred (< % 10), :val 20 :via []}}) + a 3 ::s/invalid '[{:pred (> % 5), :val 3}] + a 20 ::s/invalid '[{:pred (< % 10), :val 20}] a nil "java.lang.NullPointerException" "java.lang.NullPointerException" a :k "java.lang.ClassCastException" "java.lang.ClassCastException" o "a" [:s "a"] nil o :a [:k :a] nil - o 'a ::s/invalid (ne '{[:s] {:pred string?, :val a :via []}, [:k] {:pred keyword?, :val a :via []}}) + o 'a ::s/invalid '[{:pred string?, :val a, :path [:s]} {:pred keyword?, :val a :path [:k]}] - c nil ::s/invalid (ne '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}}) - c [] ::s/invalid (ne '{[:a] {:reason "Insufficient input", :pred string?, :val (), :via []}}) - c [:a] ::s/invalid (ne '{[:a] {:pred string?, :val :a, :via []}}) - c ["a"] ::s/invalid (ne '{[:b] {:reason "Insufficient input", :pred keyword?, :val (), :via []}}) + c nil ::s/invalid '[{:reason "Insufficient input", :pred string?, :val (), :path [:a]}] + c [] ::s/invalid '[{:reason "Insufficient input", :pred string?, :val (), :path [:a]}] + c [:a] ::s/invalid '[{:pred string?, :val :a, :path [:a], :in [0]}] + c ["a"] ::s/invalid '[{:reason "Insufficient input", :pred keyword?, :val (), :path [:b]}] c ["s" :k] '{:a "s" :b :k} nil - c ["s" :k 5] ::s/invalid (ne '{[] {:reason "Extra input", :pred (cat :a string? :b keyword?), :val (5), :via []}}) + c ["s" :k 5] ::s/invalid '[{:reason "Extra input", :pred (cat :a string? :b keyword?), :val (5)}] (s/cat) nil {} nil - (s/cat) [5] ::s/invalid (ne '{[] {:reason "Extra input", :pred (cat), :val (5), :via [], :in [0]}}) + (s/cat) [5] ::s/invalid '[{:reason "Extra input", :pred (cat), :val (5), :in [0]}] - either nil ::s/invalid (ne '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}}) - either [] ::s/invalid (ne '{[] {:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}}) + either nil ::s/invalid '[{:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}] + either [] ::s/invalid '[{:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}] either [:k] [:b :k] nil either ["s"] [:a "s"] nil - either [:b "s"] ::s/invalid (ne '{[] {:reason "Extra input", :pred (alt :a string? :b keyword?), :val ("s") :via []}}) + either [:b "s"] ::s/invalid '[{:reason "Extra input", :pred (alt :a string? :b keyword?), :val ("s") :via []}] star nil [] nil star [] [] nil star [:k] [:k] nil star [:k1 :k2] [:k1 :k2] nil - star [:k1 :k2 "x"] ::s/invalid (ne '{[] {:pred keyword?, :val "x" :via []}}) - star ["a"] ::s/invalid (ne {[] '{:pred keyword?, :val "a" :via []}}) + star [:k1 :k2 "x"] ::s/invalid '[{:pred keyword?, :val "x" :via []}] + star ["a"] ::s/invalid '[{:pred keyword?, :val "a" :via []}] - plus nil ::s/invalid (ne '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}}) - plus [] ::s/invalid (ne '{[] {:reason "Insufficient input", :pred keyword?, :val () :via []}}) + plus nil ::s/invalid '[{:reason "Insufficient input", :pred keyword?, :val () :via []}] + plus [] ::s/invalid '[{:reason "Insufficient input", :pred keyword?, :val () :via []}] plus [:k] [:k] nil plus [:k1 :k2] [:k1 :k2] nil - plus [:k1 :k2 "x"] ::s/invalid (ne '{[] {:pred keyword?, :val "x", :via [], :in [2]}}) - plus ["a"] ::s/invalid (ne '{[] {:pred keyword?, :val "a" :via []}}) + plus [:k1 :k2 "x"] ::s/invalid '[{:pred keyword?, :val "x", :in [2]}] + plus ["a"] ::s/invalid '[{:pred keyword?, :val "a" :via []}] opt nil nil nil opt [] nil nil - opt :k ::s/invalid (ne '{[] {:pred (? keyword?), :val :k, :via []}}) + opt :k ::s/invalid '[{:pred (? keyword?), :val :k}] opt [:k] :k nil - opt [:k1 :k2] ::s/invalid (ne '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2), :via []}}) - opt [:k1 :k2 "x"] ::s/invalid (ne '{[] {:reason "Extra input", :pred (? keyword?), :val (:k2 "x"), :via []}}) - opt ["a"] ::s/invalid (ne '{[] {:pred keyword?, :val "a", :via []}}) + opt [:k1 :k2] ::s/invalid '[{:reason "Extra input", :pred (? keyword?), :val (:k2)}] + opt [:k1 :k2 "x"] ::s/invalid '[{:reason "Extra input", :pred (? keyword?), :val (:k2 "x")}] + opt ["a"] ::s/invalid '[{:pred keyword?, :val "a"}] andre nil nil nil andre [] nil nil - andre :k :clojure.spec/invalid (ne '{[] {:pred (& (* keyword?) even-count?), :val :k, :via []}}) - andre [:k] ::s/invalid (ne '{[] {:pred even-count?, :val [:k], :via []}}) + andre :k :clojure.spec/invalid '[{:pred (& (* keyword?) even-count?), :val :k}] + andre [:k] ::s/invalid '[{:pred even-count?, :val [:k]}] andre [:j :k] [:j :k] nil - m nil ::s/invalid (ne '{[] {:pred map?, :val nil, :via []}}) + m nil ::s/invalid '[{:pred clojure.core/map?, :val nil}] m {} {} nil m {:a "b"} {:a "b"} nil + mkeys nil ::s/invalid '[{:pred clojure.core/map?, :val nil}] + mkeys {} {} nil + mkeys {:a 1 :b 2} {:a 1 :b 2} nil + + mkeys2 nil ::s/invalid '[{:pred clojure.core/map?, :val nil}] + mkeys2 {} {} nil + mkeys2 {:a 1 :b 2} {"a" 1 "b" 2} nil + + s '([:a 1] [:b "2"]) '({:tag :a :val 1} {:tag :b :val "2"}) nil + + v [:a :b] [:a :b] nil + v '(:a :b) ::s/invalid '[{:pred clojure.core/vector? :val (:a :b)}] + coll nil nil nil coll [] [] nil coll [:a] [:a] nil coll [:a :b] [:a :b] nil - ;;coll [:a "b"] ::s/invalid '{[] {:pred (coll-checker keyword?), :val [:a b], :via []}} + ;;coll [:a "b"] ::s/invalid '[{:pred (coll-checker keyword?), :val [:a b]}] ))) (comment From baa6c45b103dcdc8f8e551ace12943886b59f397 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Tue, 28 Jun 2016 13:28:54 -0400 Subject: [PATCH 247/854] common key for all kinds of failures in ex-data, interpret ex-data only in test summaries Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 2 +- src/clj/clojure/spec/test.clj | 67 ++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 720da41519..619700461c 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -224,7 +224,7 @@ (gen/such-that #(valid? spec %) g 100) (let [abbr (abbrev form)] (throw (ex-info (str "Unable to construct gen at: " path " for: " abbr) - {::path path ::no-gen-for form})))))) + {::path path ::form form ::failure :no-gen})))))) (defn gen "Given a spec, returns the generator for it, or throws if none can diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index 584d7d6a74..c6eea49a63 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -77,7 +77,8 @@ returns the set of all symbols naming vars in those nses." (let [conformed (s/conform spec data)] (if (= ::s/invalid conformed) (let [ed (assoc (s/explain-data* spec [role] [] [] data) - ::s/args args)] + ::s/args args + ::s/failure :instrument-check-failed)] (throw (ex-info (str "Call to " v " did not conform to spec:\n" (with-out-str (s/explain-out ed))) ed))) @@ -94,7 +95,7 @@ returns the set of all symbols naming vars in those nses." (defn- no-fn-spec [v spec] (ex-info (str "Fn at " v " is not spec'ed.") - {:var v :spec spec})) + {:var v :spec spec ::s/failure :no-fn-spec})) (def ^:private instrumented-vars "Map for instrumented vars to :raw/:wrapped fns" @@ -229,7 +230,8 @@ Returns a collection of syms naming the vars unstrumented." (when-not (s/valid? spec v nil) (assoc (s/explain-data* spec [role] [] [] v) ::args args - ::val v)))) + ::val v + ::s/failure :test-failed)))) (defn- check-call "Returns true if call passes specs, otherwise *returns* an exception @@ -257,36 +259,21 @@ with explain-data under ::check-call." (let [prop (gen/for-all* [g] #(check-call f specs %))] (apply gen/quick-check num-tests prop (mapcat identity opts)))))) -(defn- unwrap-return - "Unwraps exceptions used to flow information through test.check." +(defn- failure-type [x] - (let [data (ex-data x)] - (if (or (::args data) (::s/args data) (::s/no-gen-for data)) - data - x))) - -(defn- result-type - [result] - (let [ret (:result result)] - (cond - (true? ret) :pass - (::s/args ret) :no-argspec - (::s/no-gen-for ret) :no-gen - (::args ret) :fail - :default :error))) + (::s/failure (ex-data x))) (defn- make-test-result "Builds spec result map." [test-sym spec test-check-ret] - (let [result (merge {:spec spec - ::stc/ret test-check-ret} - (when test-sym - {:sym test-sym}) - (when-let [result (-> test-check-ret :result)] - {:result (unwrap-return result)}) - (when-let [shrunk (-> test-check-ret :shrunk)] - {:result (unwrap-return (:result shrunk))}))] - (assoc result :type (result-type result)))) + (merge {:spec spec + ::stc/ret test-check-ret} + (when test-sym + {:sym test-sym}) + (when-let [result (-> test-check-ret :result)] + {:result result}) + (when-let [shrunk (-> test-check-ret :shrunk)] + {:result (:result shrunk)}))) (defn- test-1 [{:keys [s f v spec]} {:keys [result-callback] :as opts}] @@ -295,14 +282,14 @@ with explain-data under ::check-call." (let [f (or f (when v @v))] (cond (nil? f) - {:type :no-fn :sym s :spec spec} + {::s/failure :no-fn :sym s :spec spec} (:args spec) (let [tcret (check-fn f spec opts)] (make-test-result s spec tcret)) :default - {:type :no-argspec :sym s :spec spec})) + {::s/failure :no-args-spec :sym s :spec spec})) (finally (when v (instrument s))))) @@ -383,13 +370,29 @@ Values for the :type key can be one of ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; test reporting ;;;;;;;;;;;;;;;;;;;;;;;; +(defn- unwrap-failure + [x] + (if (failure-type x) + (ex-data x) + x)) + +(defn- result-type + [ret] + (let [result (:result ret)] + (cond + (true? result) :test-passed + (failure-type result) (failure-type result) + :default :test-threw))) + (defn abbrev-result "Given a test result, returns an abbreviated version suitable for summary use." [x] (if (true? (:result x)) (dissoc x :spec ::stc/ret :result) - (update (dissoc x ::stc/ret) :spec s/describe))) + (-> (dissoc x ::stc/ret) + (update :spec s/describe) + (update :result unwrap-failure)))) (defn summarize-results "Given a collection of test-results, e.g. from 'test', pretty @@ -404,7 +407,7 @@ key with a count for each different :type of result." (pp/pprint (summary-result result)) (-> summary (update :total inc) - (update (:type result) (fnil inc 0)))) + (update (result-type result) (fnil inc 0)))) {:total 0} test-results))) From 386e7e63485bcb7bed050df2c2b54a6ceca05e5f Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 28 Jun 2016 16:05:16 -0400 Subject: [PATCH 248/854] every/coll :kind is pred/spec --- src/clj/clojure/spec.clj | 95 ++++++++++++++++-------------- test/clojure/test_clojure/spec.clj | 14 ++--- 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 619700461c..32bcd3dbb2 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -159,8 +159,9 @@ (with-gen* (specize spec) gen-fn)) (defn explain-data* [spec path via in x] - (when-let [probs (explain* (specize spec) path via in x)] - {::problems probs})) + (let [probs (explain* (specize spec) path via in x)] + (when-not (empty? probs) + {::problems probs}))) (defn explain-data "Given a spec and a value x which ought to conform, returns nil if x @@ -188,7 +189,7 @@ (when-not (empty? path) (print " at:" (pr-str path))) (print " predicate: ") - (pr pred) + (pr (abbrev pred)) (when reason (print ", " reason)) (doseq [[k v] prob] (when-not (#{:path :pred :val :reason :via :in} k) @@ -456,7 +457,9 @@ Takes several kwargs options that further constrain the collection: - :kind - one of [], (), {}, #{} - must be this kind of collection - (default nil) + :kind - a pred/spec that the collection type must satisfy, e.g. vector? + (default nil) Note that if :kind is specified and :into is + not, this pred must generate in order for every to generate. :count - specifies coll has exactly this count (default nil) :min-count, :max-count - coll has count (<= min-count count max-count) (defaults nil) :distinct - all the elements are distinct (default nil) @@ -464,7 +467,8 @@ And additional args that control gen :gen-max - the maximum coll size to generate (default 20) - :into - one of [], (), {}, #{} - the default collection to generate into (default same as :kind if supplied, else []) + :into - one of [], (), {}, #{} - the default collection to generate into + (default: empty coll as generated by :kind pred if supplied, else []) Optionally takes :gen generator-fn, which must be a fn of no args that returns a test.check generator @@ -472,7 +476,8 @@ See also - coll-of, every-kv " [pred & {:keys [into kind count max-count min-count distinct gen-max gen] :as opts}] - `(every-impl '~pred ~pred ~(dissoc opts :gen) ~gen)) + (let [nopts (-> opts (dissoc :gen) (assoc ::kind-form `'~(res (:kind opts))))] + `(every-impl '~pred ~pred ~nopts ~gen))) (defmacro every-kv "like 'every' but takes separate key and val preds and works on associative collections. @@ -501,13 +506,13 @@ vpred. Unlike 'every-kv', map-of will exhaustively conform every value. - Same options as 'every', :kind set to {}, with the addition of: + Same options as 'every', :kind defaults to map?, with the addition of: :conform-keys - conform keys as well as values (default false) See also - every-kv" [kpred vpred & opts] - `(every-kv ~kpred ~vpred ::conform-all true ~@opts :kind {})) + `(every-kv ~kpred ~vpred ::conform-all true :kind map? ~@opts)) (defmacro * @@ -733,7 +738,9 @@ pred-exprs pred-forms) (keep identity) seq)] - [{:path path :pred (vec probs) :val x :via via :in in}]) + (map + #(identity {:path path :pred % :val x :via via :in in}) + probs)) (map (fn [[k v]] (when-not (c/or (not (contains? reg (keys->specs k))) (valid? (keys->specs k) v k)) @@ -821,7 +828,7 @@ path (conj path dv)] (if-let [pred (predx x)] (explain-1 form pred path via in x) - [{:path path :pred form :val x :reason "no method" :via via :in in}]))) + [{:path path :pred (abbrev form) :val x :reason "no method" :via via :in in}]))) (gen* [_ overrides path rmap] (if gfn (gfn) @@ -997,10 +1004,11 @@ (defn- coll-prob [x kfn kform distinct count min-count max-count path via in] - (let [] + (let [pred (c/or kfn coll?) + kform (c/or kform `coll?)] (cond - (not (kfn x)) - [{:path path :pred kform :val x :via via :in in}] + (not (valid? pred x)) + (explain-1 kform pred path via in x) (c/and distinct (not (empty? x)) (not (apply distinct? x))) [{:path path :pred 'distinct? :val x :via via :in in}] @@ -1018,22 +1026,15 @@ "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" ([form pred opts] (every-impl form pred opts nil)) ([form pred {gen-into :into - :keys [kind count max-count min-count distinct gen-max ::kfn + :keys [kind ::kind-form count max-count min-count distinct gen-max ::kfn conform-keys ::conform-all] :or {gen-max 20} :as opts} gfn] - (let [conform-into (c/or gen-into kind) - gen-into (c/or gen-into kind []) + (let [conform-into gen-into check? #(valid? pred %) kfn (c/or kfn (fn [i v] i)) addcv (fn [ret i v cv] (conj ret cv)) - [kindfn kindform] (cond - (map? kind) [map? `map?] - (vector? kind) [vector? `vector?] - (list? kind) [list? `list?] - (set? kind) [set? `set?] - :else [seqable? `seqable?]) cfns (fn [x] ;;returns a tuple of [init add complete] fns (cond @@ -1045,7 +1046,7 @@ (assoc ret i cv))) identity] - (c/and (map? x) (map? conform-into)) + (c/and (map? x) (c/or (c/and kind (not conform-into)) (map? conform-into))) [(if conform-keys empty identity) (fn [ret i v cv] (if (c/and (identical? v cv) (not conform-keys)) @@ -1061,7 +1062,7 @@ Spec (conform* [_ x] (cond - (coll-prob x kindfn kindform distinct count min-count max-count + (coll-prob x kind kind-form distinct count min-count max-count nil nil nil) ::invalid @@ -1089,7 +1090,7 @@ ::invalid)))) (unform* [_ x] x) (explain* [_ path via in x] - (c/or (coll-prob x kindfn kindform distinct count min-count max-count + (c/or (coll-prob x kind kind-form distinct count min-count max-count path via in) (apply concat ((if conform-all identity (partial take *coll-error-limit*)) @@ -1103,26 +1104,32 @@ (gen* [_ overrides path rmap] (if gfn (gfn) - (let [init (empty gen-into) - pgen (gensub pred overrides path rmap form)] - (gen/fmap - #(if (vector? init) % (into init %)) + (let [pgen (gensub pred overrides path rmap form)] + (gen/bind (cond - distinct - (if count - (gen/vector-distinct pgen {:num-elements count :max-tries 100}) - (gen/vector-distinct pgen {:min-elements (c/or min-count 0) - :max-elements (c/or max-count (max gen-max (c/* 2 (c/or min-count 0)))) - :max-tries 100})) - - count - (gen/vector pgen count) - - (c/or min-count max-count) - (gen/vector pgen (c/or min-count 0) (c/or max-count (max gen-max (c/* 2 (c/or min-count 0))))) - - :else - (gen/vector pgen 0 gen-max)))))) + gen-into (gen/return (empty gen-into)) + kind (gen/fmap #(if (empty? %) % (empty %)) + (gensub kind overrides path rmap form)) + :else (gen/return [])) + (fn [init] + (gen/fmap + #(if (vector? init) % (into init %)) + (cond + distinct + (if count + (gen/vector-distinct pgen {:num-elements count :max-tries 100}) + (gen/vector-distinct pgen {:min-elements (c/or min-count 0) + :max-elements (c/or max-count (max gen-max (c/* 2 (c/or min-count 0)))) + :max-tries 100})) + + count + (gen/vector pgen count) + + (c/or min-count max-count) + (gen/vector pgen (c/or min-count 0) (c/or max-count (max gen-max (c/* 2 (c/or min-count 0))))) + + :else + (gen/vector pgen 0 gen-max)))))))) (with-gen* [_ gfn] (every-impl form pred opts gfn)) (describe* [_] `(every ~form ~@(mapcat identity opts))))))) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index b1de85d1ce..1ad805f18e 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -35,8 +35,8 @@ m (s/map-of keyword? string?) mkeys (s/map-of (s/and keyword? (s/conformer name)) ::s/any) mkeys2 (s/map-of (s/and keyword? (s/conformer name)) ::s/any :conform-keys true) - s (s/coll-of (s/spec (s/cat :tag keyword? :val ::s/any)) :kind ()) - v (s/coll-of keyword? :kind []) + s (s/coll-of (s/spec (s/cat :tag keyword? :val ::s/any)) :kind list?) + v (s/coll-of keyword? :kind vector?) coll (s/coll-of keyword?) lrange (s/int-in 7 42) drange (s/double-in :infinite? false :NaN? false :min 3.1 :max 3.2) @@ -122,24 +122,24 @@ andre [:k] ::s/invalid '[{:pred even-count?, :val [:k]}] andre [:j :k] [:j :k] nil - m nil ::s/invalid '[{:pred clojure.core/map?, :val nil}] + m nil ::s/invalid '[{:pred map?, :val nil}] m {} {} nil m {:a "b"} {:a "b"} nil - mkeys nil ::s/invalid '[{:pred clojure.core/map?, :val nil}] + mkeys nil ::s/invalid '[{:pred map?, :val nil}] mkeys {} {} nil mkeys {:a 1 :b 2} {:a 1 :b 2} nil - mkeys2 nil ::s/invalid '[{:pred clojure.core/map?, :val nil}] + mkeys2 nil ::s/invalid '[{:pred map?, :val nil}] mkeys2 {} {} nil mkeys2 {:a 1 :b 2} {"a" 1 "b" 2} nil s '([:a 1] [:b "2"]) '({:tag :a :val 1} {:tag :b :val "2"}) nil v [:a :b] [:a :b] nil - v '(:a :b) ::s/invalid '[{:pred clojure.core/vector? :val (:a :b)}] + v '(:a :b) ::s/invalid '[{:pred vector? :val (:a :b)}] - coll nil nil nil + coll nil ::s/invalid '[{:path [], :pred coll?, :val nil, :via [], :in []}] coll [] [] nil coll [:a] [:a] nil coll [:a :b] [:a :b] nil From c31a8eaf43ad84827f50b8e33cf519ee9f5f19ea Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 28 Jun 2016 16:07:52 -0500 Subject: [PATCH 249/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..b019f2dfa6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha8 http://clojure.org/ Clojure core environment and runtime library. From 36bebcd2ecd2a9a89e60c24c7cab533a8d4d6be2 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 28 Jun 2016 16:07:53 -0500 Subject: [PATCH 250/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b019f2dfa6..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha8 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From cd64b71ddb297c10e459537f818cf2cba9a743ec Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Thu, 30 Jun 2016 16:58:48 -0400 Subject: [PATCH 251/854] make StackTraceElement into data Signed-off-by: Rich Hickey --- src/clj/clojure/core_print.clj | 13 ++++++++++--- test/clojure/test_clojure/printer.clj | 22 +++------------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 7c162348d2..bcbc77cbce 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -440,14 +440,20 @@ (defmethod print-method StackTraceElement [^StackTraceElement o ^Writer w] (print-method [(symbol (.getClassName o)) (symbol (.getMethodName o)) (.getFileName o) (.getLineNumber o)] w)) +(defn StackTraceElement->vec + "Constructs a data representation for a StackTraceElement" + {:added "1.9"} + [^StackTraceElement o] + [(symbol (.getClassName o)) (symbol (.getMethodName o)) (.getFileName o) (.getLineNumber o)]) + (defn Throwable->map "Constructs a data representation for a Throwable." {:added "1.7"} [^Throwable o] (let [base (fn [^Throwable t] - (let [m {:type (class t) + (let [m {:type (symbol (.getName (class t))) :message (.getLocalizedMessage t) - :at (get (.getStackTrace t) 0)} + :at (StackTraceElement->vec (get (.getStackTrace t) 0))} data (ex-data t)] (if data (assoc m :data data) @@ -459,7 +465,8 @@ ^Throwable root (peek via) m {:cause (.getLocalizedMessage root) :via (vec (map base via)) - :trace (vec (.getStackTrace ^Throwable (or root o)))} + :trace (vec (map StackTraceElement->vec + (.getStackTrace ^Throwable (or root o))))} data (ex-data root)] (if data (assoc m :data data) diff --git a/test/clojure/test_clojure/printer.clj b/test/clojure/test_clojure/printer.clj index 30fd22153d..aa75d1058d 100644 --- a/test/clojure/test_clojure/printer.clj +++ b/test/clojure/test_clojure/printer.clj @@ -119,27 +119,9 @@ #'var-with-meta "#'clojure.test-clojure.printer/var-with-meta" #'var-with-type "#'clojure.test-clojure.printer/var-with-type")) -(defn ^:private ednize-stack-trace-element - [^StackTraceElement ste] - [(symbol (.getClassName ste)) - (symbol (.getMethodName ste)) - (.getFileName ste) - (.getLineNumber ste)]) - -(defn ^:private ednize-throwable-data - [throwable-data] - (-> throwable-data - (update :via (fn [vias] - (map (fn [via] - (-> via - (update :type #(symbol (.getName %))) - (update :at ednize-stack-trace-element))) - vias))) - (update :trace #(map ednize-stack-trace-element %)))) - (deftest print-throwable (binding [*data-readers* {'error identity}] - (are [e] (= (-> e Throwable->map ednize-throwable-data) + (are [e] (= (-> e Throwable->map) (-> e pr-str read-string)) (Exception. "heyo") (Throwable. "I can a throwable" @@ -151,3 +133,5 @@ (Error. "less outer" (ex-info "the root" {:with "even" :more 'data}))))))) + + From 99d10ceb26a3078698a5b970a912ed88f95e688a Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Thu, 30 Jun 2016 16:59:06 -0400 Subject: [PATCH 252/854] report ::caller that caused instrument failre Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 59 ++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index c6eea49a63..e4312af30f 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -11,7 +11,8 @@ (:require [clojure.pprint :as pp] [clojure.spec :as s] - [clojure.spec.gen :as gen])) + [clojure.spec.gen :as gen] + [clojure.string :as str])) (in-ns 'clojure.spec.test.check) (in-ns 'clojure.spec.test) @@ -70,15 +71,63 @@ returns the set of all symbols naming vars in those nses." `(binding [*instrument-enabled* nil] ~@body)) +(defn- interpret-stack-trace-element + "Given the vector-of-syms form of a stacktrace element produced +by e.g. Throwable->map, returns a map form that adds some keys +guessing the original Clojure names. Returns a map with + + :class class name symbol from stack trace + :method method symbol from stack trace + :file filename from stack trace + :line line number from stack trace + :var-scope optional Clojure var symbol scoping fn def + :local-fn optional local Clojure symbol scoping fn def + +For non-Clojure fns, :scope and :local-fn will be absent." + [[cls method file line]] + (let [clojure? (contains? '#{invoke invokeStatic} method) + demunge #(clojure.lang.Compiler/demunge %) + degensym #(str/replace % #"--.*" "") + [ns-sym name-sym local] (when clojure? + (->> (str/split (str cls) #"\$" 3) + (map demunge)))] + (merge {:file file + :line line + :method method + :class cls} + (when (and ns-sym name-sym) + {:var-scope (symbol ns-sym name-sym)}) + (when local + {:local-fn (symbol (degensym local))})))) + +(defn- stacktrace-relevant-to-instrument + "Takes a coll of stack trace elements (as returned by +StackTraceElement->vec) and returns a coll of maps as per +interpret-stack-trace-element that are relevant to a +failure in instrument." + [elems] + (let [plumbing? (fn [{:keys [var-scope]}] + (contains? '#{clojure.spec.test/spec-checking-fn} var-scope))] + (sequence (comp (map StackTraceElement->vec) + (map interpret-stack-trace-element) + (filter :var-scope) + (drop-while plumbing?)) + elems))) + (defn- spec-checking-fn [v f fn-spec] (let [fn-spec (@#'s/maybe-spec fn-spec) conform! (fn [v role spec data args] (let [conformed (s/conform spec data)] (if (= ::s/invalid conformed) - (let [ed (assoc (s/explain-data* spec [role] [] [] data) - ::s/args args - ::s/failure :instrument-check-failed)] + (let [caller (->> (.getStackTrace (Thread/currentThread)) + stacktrace-relevant-to-instrument + first) + ed (merge (assoc (s/explain-data* spec [role] [] [] data) + ::s/args args + ::s/failure :instrument-check-failed) + (when caller + {::caller (dissoc caller :class :method)}))] (throw (ex-info (str "Call to " v " did not conform to spec:\n" (with-out-str (s/explain-out ed))) ed))) @@ -252,7 +301,7 @@ with explain-data under ::check-call." (defn- check-fn [f specs {gen :gen opts ::stc/opts}] - (let [{:keys [num-tests] :or {num-tests 100}} opts + (let [{:keys [num-tests] :or {num-tests 1000}} opts g (try (s/gen (:args specs) gen) (catch Throwable t t))] (if (throwable? g) {:result g} From 0f2e5e575b26a7937d3b94e3c4270137d247690a Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 1 Jul 2016 12:18:24 -0400 Subject: [PATCH 253/854] merge, not flow, in merge conform/unform --- src/clj/clojure/spec.clj | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 32bcd3dbb2..f7cf9db90f 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -986,8 +986,11 @@ [forms preds gfn] (reify Spec - (conform* [_ x] (and-preds x preds forms)) - (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) + (conform* [_ x] (let [ms (map #(dt %1 x %2) preds forms)] + (if (some #{::invalid} ms) + ::invalid + (apply c/merge ms)))) + (unform* [_ x] (apply c/merge (map #(unform % x) (reverse preds)))) (explain* [_ path via in x] (apply concat (map #(explain-1 %1 %2 path via in x) From 12e0e417285f81be67d2de6f3141c18b8eccc22d Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Sat, 2 Jul 2016 12:00:21 -0400 Subject: [PATCH 254/854] spec assert Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 80 +++++++++++++++++++++++++++++++----- src/jvm/clojure/lang/RT.java | 11 +++++ 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index f7cf9db90f..6139ee41db 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -7,7 +7,7 @@ ; You must not remove this notice, or any other, from this software. (ns clojure.spec - (:refer-clojure :exclude [+ * and or cat def keys merge]) + (:refer-clojure :exclude [+ * and assert or cat def keys merge]) (:require [clojure.walk :as walk] [clojure.spec.gen :as gen] [clojure.string :as str])) @@ -267,7 +267,7 @@ (defn ^:skip-wiki def-impl "Do not call this directly, use 'def'" [k form spec] - (assert (c/and (named? k) (namespace k)) "k must be namespaced keyword or resolvable symbol") + (c/assert (c/and (named? k) (namespace k)) "k must be namespaced keyword or resolvable symbol") (let [spec (if (c/or (spec? spec) (regex? spec) (get @registry-ref spec)) spec (spec-impl form spec nil nil))] @@ -382,8 +382,8 @@ (let [unk #(-> % name keyword) req-keys (filterv keyword? (flatten req)) req-un-specs (filterv keyword? (flatten req-un)) - _ (assert (every? #(c/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un)) - "all keys must be namespace-qualified keywords") + _ (c/assert (every? #(c/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un)) + "all keys must be namespace-qualified keywords") req-specs (into req-keys req-un-specs) req-keys (into req-keys (map unk req-un-specs)) opt-keys (into (vec opt) (map unk opt-un)) @@ -424,7 +424,7 @@ keys (mapv first pairs) pred-forms (mapv second pairs) pf (mapv res pred-forms)] - (assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords") + (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords") `(or-spec-impl ~keys '~pf ~pred-forms nil))) (defmacro and @@ -547,7 +547,7 @@ keys (mapv first pairs) pred-forms (mapv second pairs) pf (mapv res pred-forms)] - (assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords") + (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords") `(alt-impl ~keys ~pred-forms '~pf))) (defmacro cat @@ -563,7 +563,7 @@ pred-forms (mapv second pairs) pf (mapv res pred-forms)] ;;(prn key-pred-forms) - (assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords") + (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords") `(cat-impl ~keys ~pred-forms '~pf))) (defmacro & @@ -607,7 +607,7 @@ where each element conforms to the corresponding pred. Each element will be referred to in paths using its ordinal." [& preds] - (assert (not (empty? preds))) + (c/assert (not (empty? preds))) `(tuple-impl '~(mapv res preds) ~(vec preds))) (defn- macroexpand-check @@ -869,7 +869,7 @@ (recur (if (identical? cv v) ret (assoc ret i cv)) (inc i)))))))) (unform* [_ x] - (assert (c/and (vector? x) + (c/assert (c/and (vector? x) (= (count x) (count preds)))) (loop [ret x, i 0] (if (= i (count x)) @@ -1535,7 +1535,7 @@ (gfn) (gen/return (fn [& args] - (assert (valid? argspec args) (with-out-str (explain argspec args))) + (c/assert (valid? argspec args) (with-out-str (explain argspec args))) (gen/generate (gen retspec overrides)))))) (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) @@ -1634,3 +1634,63 @@ ~@(when max `[#(<= % ~max)]) ~@(when min `[#(<= ~min %)])) :gen #(gen/double* ~m))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; assert ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defonce + ^{:dynamic true + :doc "If true, compiler will enable spec asserts, which are then +subject to runtime control via check-asserts? If false, compiler +will eliminate all spec assert overhead. See 'assert'. + +Initially set to boolean value of clojure.spec.compile-asserts +system property. Defaults to true."} + *compile-asserts* + (not= "false" (System/getProperty "clojure.spec.compile-asserts"))) + +(defn check-asserts? + "Returns the value set by check-asserts." + [] + clojure.lang.RT/checkSpecAsserts) + +(defn check-asserts + "Checktime enable/disable of spec asserts that have been compiled +with '*compile-asserts*' true. See 'assert'. + +Initially set to boolean value of clojure.spec.check-asserts +system property. Defaults to false." + [flag] + (set! (. clojure.lang.RT checkSpecAsserts) flag)) + +(defn assert* + "Do not call this directly, use 'assert'." + [spec x] + (if (valid? spec x) + x + (let [ed (c/merge (assoc (explain-data* spec [] [] [] x) + ::failure :assertion-failed))] + (throw (ex-info + (str "Spec assertion failed\n" (with-out-str (explain-out ed))) + ed))))) + +(defmacro assert + "spec-checking assert expression. Returns x if x is valid? according +to spec, else throws an ex-info with explain-data plus ::failure of +:assertion-failed. + +Can be disabled at either compile time or runtime: + +If *compile-asserts* is false at compile time, compiles to x. Defaults +to value of 'clojure.spec.compile-asserts' system property, or true if +not set. + +If (check-asserts?) is false at runtime, always returns x. Defaults to +value of 'clojure.spec.check-asserts' system property, or false if not +set. You can toggle check-asserts? with (check-asserts bool)." + [spec x] + (if *compile-asserts* + `(if clojure.lang.RT/checkSpecAsserts + (assert* ~spec ~x) + ~x) + x)) + + diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 0682c80531..f4c4807705 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -169,6 +169,14 @@ public class RT{ // single instance of UTF-8 Charset, so as to avoid catching UnsupportedCharsetExceptions everywhere static public Charset UTF8 = Charset.forName("UTF-8"); +static boolean readTrueFalseDefault(String s, boolean def){ + if("true".equals(s)) + return Boolean.TRUE; + else if("false".equals(s)) + return Boolean.FALSE; + return def; +} + static Object readTrueFalseUnknown(String s){ if(s.equals("true")) return Boolean.TRUE; @@ -298,6 +306,9 @@ static public void addURL(Object url) throws MalformedURLException{ throw new IllegalAccessError("Context classloader is not a DynamicClassLoader"); } +public static boolean checkSpecAsserts = readTrueFalseDefault( + System.getProperty("clojure.spec.check-asserts"), false); + static{ Keyword arglistskw = Keyword.intern(null, "arglists"); Symbol namesym = Symbol.intern("name"); From 2ecf02d54a4e5fad94f833a36fa4656ce4671afe Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Mon, 4 Jul 2016 09:15:58 -0400 Subject: [PATCH 255/854] spec improvements: - defonce instrumented-vars - s/test/check - better keyword names for check results Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 110 +++++++++++++++++----------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index e4312af30f..5fc64cc61d 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -125,7 +125,7 @@ failure in instrument." first) ed (merge (assoc (s/explain-data* spec [role] [] [] data) ::s/args args - ::s/failure :instrument-check-failed) + ::s/failure :instrument) (when caller {::caller (dissoc caller :class :method)}))] (throw (ex-info @@ -141,14 +141,12 @@ failure in instrument." (.applyTo ^clojure.lang.IFn f args))) (.applyTo ^clojure.lang.IFn f args))))) -(defn- no-fn-spec +(defn- no-fspec [v spec] (ex-info (str "Fn at " v " is not spec'ed.") - {:var v :spec spec ::s/failure :no-fn-spec})) + {:var v :spec spec ::s/failure :no-fspec})) -(def ^:private instrumented-vars - "Map for instrumented vars to :raw/:wrapped fns" - (atom {})) +(defonce ^:private instrumented-vars (atom {})) (defn- instrument-choose-fn "Helper for instrument." @@ -170,7 +168,7 @@ failure in instrument." current @v to-wrap (if (= wrapped current) raw current) ospec (or (instrument-choose-spec spec s opts) - (throw (no-fn-spec v spec))) + (throw (no-fspec v spec))) ofn (instrument-choose-fn to-wrap ospec s opts) checked (spec-checking-fn v ofn ospec)] (alter-var-root v (constantly checked)) @@ -272,15 +270,15 @@ Returns a collection of syms naming the vars unstrumented." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; testing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- explain-test +(defn- explain-check [args spec v role] (ex-info - "Specification-based test failed" + "Specification-based check failed" (when-not (s/valid? spec v nil) (assoc (s/explain-data* spec [role] [] [] v) ::args args ::val v - ::s/failure :test-failed)))) + ::s/failure :check-failed)))) (defn- check-call "Returns true if call passes specs, otherwise *returns* an exception @@ -288,18 +286,18 @@ with explain-data under ::check-call." [f specs args] (let [cargs (when (:args specs) (s/conform (:args specs) args))] (if (= cargs ::s/invalid) - (explain-test args (:args specs) args :args) + (explain-check args (:args specs) args :args) (let [ret (apply f args) cret (when (:ret specs) (s/conform (:ret specs) ret))] (if (= cret ::s/invalid) - (explain-test args (:ret specs) ret :ret) + (explain-check args (:ret specs) ret :ret) (if (and (:args specs) (:ret specs) (:fn specs)) (if (s/valid? (:fn specs) {:args cargs :ret cret}) true - (explain-test args (:fn specs) {:args cargs :ret cret} :fn)) + (explain-check args (:fn specs) {:args cargs :ret cret} :fn)) true)))))) -(defn- check-fn +(defn- quick-check [f specs {gen :gen opts ::stc/opts}] (let [{:keys [num-tests] :or {num-tests 1000}} opts g (try (s/gen (:args specs) gen) (catch Throwable t t))] @@ -312,19 +310,19 @@ with explain-data under ::check-call." [x] (::s/failure (ex-data x))) -(defn- make-test-result +(defn- make-check-result "Builds spec result map." - [test-sym spec test-check-ret] + [check-sym spec test-check-ret] (merge {:spec spec ::stc/ret test-check-ret} - (when test-sym - {:sym test-sym}) + (when check-sym + {:sym check-sym}) (when-let [result (-> test-check-ret :result)] {:result result}) (when-let [shrunk (-> test-check-ret :shrunk)] {:result (:result shrunk)}))) -(defn- test-1 +(defn- check-1 [{:keys [s f v spec]} {:keys [result-callback] :as opts}] (when v (unstrument s)) (try @@ -334,46 +332,46 @@ with explain-data under ::check-call." {::s/failure :no-fn :sym s :spec spec} (:args spec) - (let [tcret (check-fn f spec opts)] - (make-test-result s spec tcret)) + (let [tcret (quick-check f spec opts)] + (make-check-result s spec tcret)) :default {::s/failure :no-args-spec :sym s :spec spec})) (finally (when v (instrument s))))) -(defn- sym->test-map +(defn- sym->check-map [s] (let [v (resolve s)] {:s s :v v :spec (when v (s/get-spec v))})) -(defn- validate-test-opts +(defn- validate-check-opts [opts] - (assert (every? ident? (keys (:gen opts))) "test :gen expects ident keys")) + (assert (every? ident? (keys (:gen opts))) "check :gen expects ident keys")) -(defn test-fn +(defn check-fn "Runs generative tests for fn f using spec and opts. See -'test' for options and return." - ([f spec] (test-fn f spec nil)) +'check' for options and return." + ([f spec] (check-fn f spec nil)) ([f spec opts] - (validate-test-opts opts) - (test-1 {:f f :spec spec} opts))) + (validate-check-opts opts) + (check-1 {:f f :spec spec} opts))) (defn testable-syms - "Given an opts map as per test, returns the set of syms that + "Given an opts map as per check, returns the set of syms that can be tested." ([] (testable-syms nil)) ([opts] - (validate-test-opts opts) + (validate-check-opts opts) (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) (keys (:spec opts))]))) -(defn test +(defn check "Run generative tests for spec conformance on vars named by sym-or-syms, a symbol or collection of symbols. If sym-or-syms -is not specified, test all testable vars. +is not specified, check all testable vars. The opts map includes the following optional keys, where stc aliases clojure.spec.test.check: @@ -385,39 +383,41 @@ The ::stc/opts include :num-tests in addition to the keys documented by test.check. Generator overrides are passed to spec/gen when generating function args. -Returns a lazy sequence of test result maps with the following +Returns a lazy sequence of check result maps with the following keys :spec the spec tested -:type the type of the test result +:type the type of the check result :sym optional symbol naming the var tested -:result optional test result +:result optional check result ::stc/ret optional value returned by test.check/quick-check Values for the :result key can be one of -true passing test -exception code under test threw +true passing check +exception code under check threw map with explain-data under :clojure.spec/problems Values for the :type key can be one of -:pass test passed -:fail test failed -:error test threw -:no-argspec no :args in fn-spec -:no-gen unable to generate :args -:no-fn unable to resolve fn to test +:check-passed all checked fn returns conformed +:check-failed at least one checked return did not conform +:check-threw checked fn threw an exception +:no-args-spec no :args spec provided +:no-fn no fn provided +:no-fspec no fspec provided +:no-gen unable to generate :args +:instrument invalid args detected by instrument " - ([] (test (testable-syms))) - ([sym-or-syms] (test sym-or-syms nil)) + ([] (check (testable-syms))) + ([sym-or-syms] (check sym-or-syms nil)) ([sym-or-syms opts] (->> (collectionize sym-or-syms) (filter (testable-syms opts)) (pmap - #(test-1 (sym->test-map %) opts))))) + #(check-1 (sym->check-map %) opts))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; test reporting ;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; check reporting ;;;;;;;;;;;;;;;;;;;;;;;; (defn- unwrap-failure [x] @@ -429,12 +429,12 @@ Values for the :type key can be one of [ret] (let [result (:result ret)] (cond - (true? result) :test-passed + (true? result) :check-passed (failure-type result) (failure-type result) - :default :test-threw))) + :default :check-threw))) (defn abbrev-result - "Given a test result, returns an abbreviated version + "Given a check result, returns an abbreviated version suitable for summary use." [x] (if (true? (:result x)) @@ -444,13 +444,13 @@ suitable for summary use." (update :result unwrap-failure)))) (defn summarize-results - "Given a collection of test-results, e.g. from 'test', pretty + "Given a collection of check-results, e.g. from 'check', pretty prints the summary-result (default abbrev-result) of each. Returns a map with :total, the total number of results, plus a key with a count for each different :type of result." - ([test-results] (summarize-results test-results abbrev-result)) - ([test-results summary-result] + ([check-results] (summarize-results check-results abbrev-result)) + ([check-results summary-result] (reduce (fn [summary result] (pp/pprint (summary-result result)) @@ -458,7 +458,7 @@ key with a count for each different :type of result." (update :total inc) (update (result-type result) (fnil inc 0)))) {:total 0} - test-results))) + check-results))) From 12c35e20ba806851645df5c6f794ca19c587c2b8 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Mon, 4 Jul 2016 23:13:28 -0400 Subject: [PATCH 256/854] spec.test bugfixes: - test should only re-instrument what it unstrumented - docstring fixes - give all check-1 results same shape Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 79 +++++++++++++++++------------------ 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index 5fc64cc61d..68141816c7 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -179,11 +179,11 @@ failure in instrument." [s] (when-let [v (resolve s)] (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] + (swap! instrumented-vars dissoc v) (let [current @v] (when (= wrapped current) - (alter-var-root v (constantly raw)))) - (swap! instrumented-vars dissoc v)) - (->sym v))) + (alter-var-root v (constantly raw)) + (->sym v)))))) (defn- opt-syms "Returns set of symbols referenced by 'instrument' opts map" @@ -282,7 +282,7 @@ Returns a collection of syms naming the vars unstrumented." (defn- check-call "Returns true if call passes specs, otherwise *returns* an exception -with explain-data under ::check-call." +with explain-data + ::s/failure." [f specs args] (let [cargs (when (:args specs) (s/conform (:args specs) args))] (if (= cargs ::s/invalid) @@ -306,10 +306,6 @@ with explain-data under ::check-call." (let [prop (gen/for-all* [g] #(check-call f specs %))] (apply gen/quick-check num-tests prop (mapcat identity opts)))))) -(defn- failure-type - [x] - (::s/failure (ex-data x))) - (defn- make-check-result "Builds spec result map." [check-sym spec test-check-ret] @@ -318,27 +314,29 @@ with explain-data under ::check-call." (when check-sym {:sym check-sym}) (when-let [result (-> test-check-ret :result)] - {:result result}) + (when-not (true? result) {:failure result})) (when-let [shrunk (-> test-check-ret :shrunk)] - {:result (:result shrunk)}))) + {:failure (:result shrunk)}))) (defn- check-1 - [{:keys [s f v spec]} {:keys [result-callback] :as opts}] - (when v (unstrument s)) - (try - (let [f (or f (when v @v))] + [{:keys [s f v spec] :as foo} {:keys [result-callback] :as opts}] + (let [f (or v (when v @v)) + re-inst? (and v (seq (unstrument s)) true)] + (try (cond (nil? f) - {::s/failure :no-fn :sym s :spec spec} + {:failure (ex-info "No fn to spec" {::s/failure :no-fn}) + :sym s :spec spec} (:args spec) (let [tcret (quick-check f spec opts)] (make-check-result s spec tcret)) :default - {::s/failure :no-args-spec :sym s :spec spec})) - (finally - (when v (instrument s))))) + {:failure (ex-info "No :args spec" {::s/failure :no-args-spec}) + :sym s :spec spec}) + (finally + (when re-inst? (instrument s)))))) (defn- sym->check-map [s] @@ -359,10 +357,10 @@ with explain-data under ::check-call." (validate-check-opts opts) (check-1 {:f f :spec spec} opts))) -(defn testable-syms +(defn checkable-syms "Given an opts map as per check, returns the set of syms that can be tested." - ([] (testable-syms nil)) + ([] (checkable-syms nil)) ([opts] (validate-check-opts opts) (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) @@ -371,7 +369,7 @@ can be tested." (defn check "Run generative tests for spec conformance on vars named by sym-or-syms, a symbol or collection of symbols. If sym-or-syms -is not specified, check all testable vars. +is not specified, check all checkable vars. The opts map includes the following optional keys, where stc aliases clojure.spec.test.check: @@ -387,38 +385,34 @@ Returns a lazy sequence of check result maps with the following keys :spec the spec tested -:type the type of the check result :sym optional symbol naming the var tested -:result optional check result +:failure optional test failure ::stc/ret optional value returned by test.check/quick-check -Values for the :result key can be one of - -true passing check -exception code under check threw -map with explain-data under :clojure.spec/problems +The value for :failure can be any exception. Exceptions thrown by +spec itself will have an ::s/failure value in ex-data: -Values for the :type key can be one of - -:check-passed all checked fn returns conformed :check-failed at least one checked return did not conform -:check-threw checked fn threw an exception :no-args-spec no :args spec provided :no-fn no fn provided :no-fspec no fspec provided :no-gen unable to generate :args :instrument invalid args detected by instrument " - ([] (check (testable-syms))) + ([] (check (checkable-syms))) ([sym-or-syms] (check sym-or-syms nil)) ([sym-or-syms opts] (->> (collectionize sym-or-syms) - (filter (testable-syms opts)) + (filter (checkable-syms opts)) (pmap #(check-1 (sym->check-map %) opts))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; check reporting ;;;;;;;;;;;;;;;;;;;;;;;; +(defn- failure-type + [x] + (::s/failure (ex-data x))) + (defn- unwrap-failure [x] (if (failure-type x) @@ -426,22 +420,27 @@ Values for the :type key can be one of x)) (defn- result-type + "Returns the type of the check result. This can be any of the +::s/failure keywords documented in 'check', or: + + :check-passed all checked fn returns conformed + :check-threw checked fn threw an exception" [ret] - (let [result (:result ret)] + (let [failure (:failure ret)] (cond - (true? result) :check-passed - (failure-type result) (failure-type result) + (nil? failure) :check-passed + (failure-type failure) (failure-type failure) :default :check-threw))) (defn abbrev-result "Given a check result, returns an abbreviated version suitable for summary use." [x] - (if (true? (:result x)) - (dissoc x :spec ::stc/ret :result) + (if (:failure x) (-> (dissoc x ::stc/ret) (update :spec s/describe) - (update :result unwrap-failure)))) + (update :failure unwrap-failure)) + (dissoc x :spec ::stc/ret))) (defn summarize-results "Given a collection of check-results, e.g. from 'check', pretty From 174347ae766b5a2089f8a9b5780d00c5fea62b16 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 5 Jul 2016 10:32:00 -0500 Subject: [PATCH 257/854] Fix typos and clean up Signed-off-by: Stuart Halloway Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 2 +- src/clj/clojure/spec/test.clj | 6 +++--- src/jvm/clojure/lang/RT.java | 11 +---------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 6139ee41db..0a9f7077c3 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1653,7 +1653,7 @@ system property. Defaults to true."} clojure.lang.RT/checkSpecAsserts) (defn check-asserts - "Checktime enable/disable of spec asserts that have been compiled + "Enable or disable spec asserts that have been compiled with '*compile-asserts*' true. See 'assert'. Initially set to boolean value of clojure.spec.check-asserts diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index 68141816c7..ac080a6b94 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -319,8 +319,8 @@ with explain-data + ::s/failure." {:failure (:result shrunk)}))) (defn- check-1 - [{:keys [s f v spec] :as foo} {:keys [result-callback] :as opts}] - (let [f (or v (when v @v)) + [{:keys [s f v spec]} opts] + (let [f (or f (when v @v)) re-inst? (and v (seq (unstrument s)) true)] (try (cond @@ -359,7 +359,7 @@ with explain-data + ::s/failure." (defn checkable-syms "Given an opts map as per check, returns the set of syms that -can be tested." +can be checked." ([] (checkable-syms nil)) ([opts] (validate-check-opts opts) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index f4c4807705..4a6d0a5791 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -169,14 +169,6 @@ public class RT{ // single instance of UTF-8 Charset, so as to avoid catching UnsupportedCharsetExceptions everywhere static public Charset UTF8 = Charset.forName("UTF-8"); -static boolean readTrueFalseDefault(String s, boolean def){ - if("true".equals(s)) - return Boolean.TRUE; - else if("false".equals(s)) - return Boolean.FALSE; - return def; -} - static Object readTrueFalseUnknown(String s){ if(s.equals("true")) return Boolean.TRUE; @@ -306,8 +298,7 @@ static public void addURL(Object url) throws MalformedURLException{ throw new IllegalAccessError("Context classloader is not a DynamicClassLoader"); } -public static boolean checkSpecAsserts = readTrueFalseDefault( - System.getProperty("clojure.spec.check-asserts"), false); +public static boolean checkSpecAsserts = Boolean.getBoolean("clojure.spec.check-asserts"); static{ Keyword arglistskw = Keyword.intern(null, "arglists"); From d8aad06ba91827bf7373ac3f3d469817e6331322 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Tue, 5 Jul 2016 12:30:39 -0400 Subject: [PATCH 258/854] =?UTF-8?q?don=E2=80=99t=20instrument=20macros,=20?= =?UTF-8?q?use=20uninstrumented=20fn=20under=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index ac080a6b94..f6cea53933 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -163,17 +163,18 @@ failure in instrument." (defn- instrument-1 [s opts] (when-let [v (resolve s)] - (let [spec (s/get-spec v) - {:keys [raw wrapped]} (get @instrumented-vars v) - current @v - to-wrap (if (= wrapped current) raw current) - ospec (or (instrument-choose-spec spec s opts) + (when-not (-> v meta :macro) + (let [spec (s/get-spec v) + {:keys [raw wrapped]} (get @instrumented-vars v) + current @v + to-wrap (if (= wrapped current) raw current) + ospec (or (instrument-choose-spec spec s opts) (throw (no-fspec v spec))) - ofn (instrument-choose-fn to-wrap ospec s opts) - checked (spec-checking-fn v ofn ospec)] - (alter-var-root v (constantly checked)) - (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked})) - (->sym v))) + ofn (instrument-choose-fn to-wrap ospec s opts) + checked (spec-checking-fn v ofn ospec)] + (alter-var-root v (constantly checked)) + (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked}) + (->sym v))))) (defn- unstrument-1 [s] @@ -320,8 +321,8 @@ with explain-data + ::s/failure." (defn- check-1 [{:keys [s f v spec]} opts] - (let [f (or f (when v @v)) - re-inst? (and v (seq (unstrument s)) true)] + (let [re-inst? (and v (seq (unstrument s)) true) + f (or f (when v @v))] (try (cond (nil? f) From 74a3dce56c8ae76f3bb73dbd9398f6ccb25159da Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 5 Jul 2016 14:21:00 -0500 Subject: [PATCH 259/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..11047640e0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha9 http://clojure.org/ Clojure core environment and runtime library. From 87b4023a9fabe7a169ea8c99c8ae2bd586b10b6d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 5 Jul 2016 14:21:00 -0500 Subject: [PATCH 260/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11047640e0..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha9 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 0929d1d13d036973a03db78a5a03f29d19c9e4b2 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 8 Jul 2016 11:30:54 -0400 Subject: [PATCH 261/854] add any? to core, remove ::spec/any, gens for any? and some? --- src/clj/clojure/core.clj | 6 ++++++ src/clj/clojure/spec.clj | 3 +-- src/clj/clojure/spec/gen.clj | 4 +++- test/clojure/test_clojure/spec.clj | 6 +++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index cd070d9126..d454eb10ec 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -535,6 +535,12 @@ :static true} [x] (not (nil? x))) +(defn any? + "Returns true given any argument." + {:tag Boolean + :added "1.9"} + [x] true) + (defn str "With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 0a9f7077c3..cee3f8e86e 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1541,7 +1541,6 @@ (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; non-primitives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(clojure.spec/def ::any (spec (constantly true) :gen gen/any)) (clojure.spec/def ::kvs->map (conformer #(zipmap (map ::k %) (map ::v %)) #(map (fn [[k v]] {::k k ::v v}) %))) (defmacro keys* @@ -1559,7 +1558,7 @@ user=> (s/conform (s/cat :i1 integer? :m (s/keys* :req-un [::a ::c]) :i2 integer?) [42 :a 1 :c 2 :d 4 99]) {:i1 42, :m {:a 1, :c 2, :d 4}, :i2 99}" [& kspecs] - `(clojure.spec/& (* (cat ::k keyword? ::v ::any)) ::kvs->map (keys ~@kspecs))) + `(clojure.spec/& (* (cat ::k keyword? ::v any?)) ::kvs->map (keys ~@kspecs))) (defmacro nilable "returns a spec that accepts nil and values satisfiying pred" diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index 194721d9c7..8f1c4e3881 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -129,7 +129,9 @@ gens, each of which should generate something sequential." gen-builtins (c/delay (let [simple (simple-type-printable)] - {number? (one-of [(large-integer) (double)]) + {any? (one-of [(return nil) (any-printable)]) + some? (such-that some? (any-printable)) + number? (one-of [(large-integer) (double)]) integer? (large-integer) int? (large-integer) pos-int? (large-integer* {:min 1}) diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 1ad805f18e..c388693bad 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -33,9 +33,9 @@ opt (s/? keyword?) andre (s/& (s/* keyword?) even-count?) m (s/map-of keyword? string?) - mkeys (s/map-of (s/and keyword? (s/conformer name)) ::s/any) - mkeys2 (s/map-of (s/and keyword? (s/conformer name)) ::s/any :conform-keys true) - s (s/coll-of (s/spec (s/cat :tag keyword? :val ::s/any)) :kind list?) + mkeys (s/map-of (s/and keyword? (s/conformer name)) any?) + mkeys2 (s/map-of (s/and keyword? (s/conformer name)) any? :conform-keys true) + s (s/coll-of (s/spec (s/cat :tag keyword? :val any?)) :kind list?) v (s/coll-of keyword? :kind vector?) coll (s/coll-of keyword?) lrange (s/int-in 7 42) From 357df34ef0c38cce4d0aa19cdf3fa6b2f9bb3c05 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 8 Jul 2016 12:04:47 -0400 Subject: [PATCH 262/854] gen overrides should be no-arg fns --- src/clj/clojure/spec.clj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index cee3f8e86e..a4f205b02c 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -219,8 +219,9 @@ [spec overrides path rmap form] ;;(prn {:spec spec :over overrides :path path :form form}) (let [spec (specize spec)] - (if-let [g (c/or (get overrides (c/or (spec-name spec) spec)) - (get overrides path) + (if-let [g (c/or (when-let [gfn (c/or (get overrides (c/or (spec-name spec) spec)) + (get overrides path))] + (gfn)) (gen* spec overrides path rmap))] (gen/such-that #(valid? spec %) g 100) (let [abbr (abbrev form)] @@ -230,8 +231,8 @@ (defn gen "Given a spec, returns the generator for it, or throws if none can be constructed. Optionally an overrides map can be provided which - should map spec names or paths (vectors of keywords) to - generators. These will be used instead of the generators at those + should map spec names or paths (vectors of keywords) to no-arg + generator-creating fns. These will be used instead of the generators at those names/paths. Note that parent generator (in the spec or overrides map) will supersede those of any subtrees. A generator for a regex op must always return a sequential collection (i.e. a generator for From 1e236448104fc8a0fc51e26eae7cdb7e650b7ae9 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 8 Jul 2016 13:39:01 -0400 Subject: [PATCH 263/854] with-gen now works on regexes w/o lifting to specs, used by keys* so it can now gen --- src/clj/clojure/spec.clj | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index a4f205b02c..50afde970c 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -156,7 +156,10 @@ (defn with-gen "Takes a spec and a no-arg, generator-returning fn and returns a version of that spec that uses that generator" [spec gen-fn] - (with-gen* (specize spec) gen-fn)) + (let [spec (reg-resolve spec)] + (if (regex? spec) + (assoc spec ::gfn gen-fn) + (with-gen* (specize spec) gen-fn)))) (defn explain-data* [spec path via in x] (let [probs (explain* (specize spec) path via in x)] @@ -1384,7 +1387,7 @@ (defn- re-gen [p overrides path rmap f] ;;(prn {:op op :ks ks :forms forms}) - (let [{:keys [::op ps ks p1 p2 forms splice ret id] :as p} (reg-resolve! p) + (let [{:keys [::op ps ks p1 p2 forms splice ret id ::gfn] :as p} (reg-resolve! p) rmap (if id (inck rmap id) rmap) ggens (fn [ps ks forms] (let [gen (fn [p k f] @@ -1398,6 +1401,8 @@ (case op (:accept nil) (gen/fmap vector g) g)) + (when gfn + (gfn)) (when p (case op ::accept (if (= ret ::nil) @@ -1559,7 +1564,9 @@ user=> (s/conform (s/cat :i1 integer? :m (s/keys* :req-un [::a ::c]) :i2 integer?) [42 :a 1 :c 2 :d 4 99]) {:i1 42, :m {:a 1, :c 2, :d 4}, :i2 99}" [& kspecs] - `(clojure.spec/& (* (cat ::k keyword? ::v any?)) ::kvs->map (keys ~@kspecs))) + `(let [mspec# (keys ~@kspecs)] + (with-gen (clojure.spec/& (* (cat ::k keyword? ::v any?)) ::kvs->map mspec#) + (fn [] (gen/fmap (fn [m#] (apply concat m#)) (gen mspec#)))))) (defmacro nilable "returns a spec that accepts nil and values satisfiying pred" From 88fc01f43b5bb09068e01d967c15b4528614e058 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 6 Jul 2016 09:54:56 -0500 Subject: [PATCH 264/854] CLJ-1977 Fix Throwable->map conversion when stack has been omitted Signed-off-by: Rich Hickey --- src/clj/clojure/core_print.clj | 21 +++++++++++---------- test/clojure/test_clojure/errors.clj | 14 +++++++++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index bcbc77cbce..6dd99b5183 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -451,13 +451,13 @@ {:added "1.7"} [^Throwable o] (let [base (fn [^Throwable t] - (let [m {:type (symbol (.getName (class t))) - :message (.getLocalizedMessage t) - :at (StackTraceElement->vec (get (.getStackTrace t) 0))} - data (ex-data t)] - (if data - (assoc m :data data) - m))) + (merge {:type (symbol (.getName (class t))) + :message (.getLocalizedMessage t)} + (when-let [ed (ex-data t)] + {:data ed}) + (let [st (.getStackTrace t)] + (when (pos? (alength st)) + {:at (StackTraceElement->vec (aget st 0))})))) via (loop [via [], ^Throwable t o] (if t (recur (conj via t) (.getCause t)) @@ -482,9 +482,10 @@ (when-let [data (:data %)] (.write w "\n :data ") (print-method data w)) - (.write w "\n :at ") - (print-method (:at %) w) - (.write w "}"))] + (when-let [at (:at %)] + (.write w "\n :at ") + (print-method (:at %) w)) + (.write w "}"))] (print-method cause w) (when data (.write w "\n :data ") diff --git a/test/clojure/test_clojure/errors.clj b/test/clojure/test_clojure/errors.clj index 9131f6a581..16b937a366 100644 --- a/test/clojure/test_clojure/errors.clj +++ b/test/clojure/test_clojure/errors.clj @@ -79,7 +79,19 @@ data-top-level :data} (Throwable->map (ex-info "ex-info" {:some "data"}))] - (is (= data data-top-level {:some "data"}))))) + (is (= data data-top-level {:some "data"})))) + (testing "nil stack handled" + (let [t (Throwable. "abc")] + ;; simulate what can happen when Java omits stack traces + (.setStackTrace t (into-array StackTraceElement [])) + (let [{:keys [cause via trace]} (Throwable->map t)] + (is (= cause "abc")) + (is (= trace [])) + + ;; fail if printing throws an exception + (try + (with-out-str (pr t)) + (catch Throwable t (is nil))))))) (deftest ex-info-disallows-nil-data (is (thrown? IllegalArgumentException (ex-info "message" nil))) From f374423053b75b7b484ffbc8b49b2ede9d92e406 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 11 Jul 2016 08:55:54 -0500 Subject: [PATCH 265/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha10 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..5c2380259f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha10 http://clojure.org/ Clojure core environment and runtime library. From be9f054b61d8f99c5158f9823b31651835fc1d51 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 11 Jul 2016 08:55:54 -0500 Subject: [PATCH 266/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5c2380259f..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha10 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 8c6803ed0b645f23f213f8e57b749a4e8917cd3f Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 12 Jul 2016 17:43:01 -0400 Subject: [PATCH 267/854] fix regexes to use new gen overrides method --- src/clj/clojure/spec.clj | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 50afde970c..b6b422a82e 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1387,7 +1387,8 @@ (defn- re-gen [p overrides path rmap f] ;;(prn {:op op :ks ks :forms forms}) - (let [{:keys [::op ps ks p1 p2 forms splice ret id ::gfn] :as p} (reg-resolve! p) + (let [origp p + {:keys [::op ps ks p1 p2 forms splice ret id ::gfn] :as p} (reg-resolve! p) rmap (if id (inck rmap id) rmap) ggens (fn [ps ks forms] (let [gen (fn [p k f] @@ -1397,10 +1398,12 @@ (gen/delay (re-gen p overrides (if k (conj path k) path) rmap (c/or f p))) (re-gen p overrides (if k (conj path k) path) rmap (c/or f p)))))] (map gen ps (c/or (seq ks) (repeat nil)) (c/or (seq forms) (repeat nil)))))] - (c/or (when-let [g (get overrides path)] + (c/or (when-let [gfn (c/or (get overrides (spec-name origp)) + (get overrides (spec-name p) ) + (get overrides path))] (case op - (:accept nil) (gen/fmap vector g) - g)) + (:accept nil) (gen/fmap vector (gfn)) + (gfn))) (when gfn (gfn)) (when p From d920ada9fab7e9b8342d28d8295a600a814c1d8a Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 14 Jul 2016 10:09:09 -0400 Subject: [PATCH 268/854] fix docstring - merge doesn't flow --- src/clj/clojure/spec.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index b6b422a82e..295e6941a4 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -444,8 +444,7 @@ (defmacro merge "Takes map-validating specs (e.g. 'keys' specs) and returns a spec that returns a conformed map satisfying all of the - specs. Successive conformed values propagate through rest of - predicates. Unlike 'and', merge can generate maps satisfying the + specs. Unlike 'and', merge can generate maps satisfying the union of the predicates." [& pred-forms] `(merge-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) From e8065abc374b574e830627ad90d5695b993537fd Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 15 Jul 2016 16:35:56 -0500 Subject: [PATCH 269/854] fix lost type hints in map destructuring Signed-off-by: Rich Hickey --- src/clj/clojure/core.clj | 2 +- test/clojure/test_clojure/special.clj | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index d454eb10ec..5cbb02605f 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -4410,7 +4410,7 @@ (if (seq bes) (let [bb (key (first bes)) bk (val (first bes)) - local (if (instance? clojure.lang.Named bb) (symbol nil (name bb)) bb) + local (if (instance? clojure.lang.Named bb) (with-meta (symbol nil (name bb)) (meta bb)) bb) bv (if (contains? defaults local) (list `get gmap bk (defaults local)) (list `get gmap bk))] diff --git a/test/clojure/test_clojure/special.clj b/test/clojure/test_clojure/special.clj index 87f0e3ffe6..cae206b735 100644 --- a/test/clojure/test_clojure/special.clj +++ b/test/clojure/test_clojure/special.clj @@ -13,7 +13,8 @@ ;; (ns clojure.test-clojure.special - (:use clojure.test)) + (:use clojure.test) + (:require [clojure.test-helper :refer [should-not-reflect]])) ; http://clojure.org/special_forms @@ -98,3 +99,9 @@ (.getCause) (ex-data) (:form)))))) + +(deftest typehints-retained-destructuring + (should-not-reflect + (defn foo + [{:keys [^String s]}] + (.indexOf s "boo")))) \ No newline at end of file From df2749ce31728da6e1ca75953a8a3f9757fcec1d Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Wed, 27 Jul 2016 12:52:45 -0400 Subject: [PATCH 270/854] fix guard in check-1 Signed-off-by: Rich Hickey --- src/clj/clojure/spec/test.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index f6cea53933..a7d32a6f8c 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -325,7 +325,7 @@ with explain-data + ::s/failure." f (or f (when v @v))] (try (cond - (nil? f) + (or (nil? f) (some-> v meta :macro)) {:failure (ex-info "No fn to spec" {::s/failure :no-fn}) :sym s :spec spec} From dcc29d897718ab80bc993553ecf1b8e5a78bbe26 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 18 Jul 2016 09:13:54 -0500 Subject: [PATCH 271/854] specs for let, if-let, when-let Signed-off-by: Rich Hickey --- src/clj/clojure/core/specs.clj | 61 ++++++++++++++++++++++++++- test/clojure/test_clojure/errors.clj | 9 +--- test/clojure/test_clojure/special.clj | 12 +++--- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index 0850386140..1c588b4e92 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -1,4 +1,63 @@ (ns ^{:skip-wiki true} clojure.core.specs (:require [clojure.spec :as s])) -(alias 'cc 'clojure.core) \ No newline at end of file +;;;; destructure + +(s/def ::local-name (s/and simple-symbol? #(not= '& %))) + +(s/def ::binding-form + (s/or :sym ::local-name + :seq ::seq-binding-form + :map ::map-binding-form)) + +;; sequential destructuring + +(s/def ::seq-binding-form + (s/cat :elems (s/* ::binding-form) + :rest (s/? (s/cat :amp #{'&} :form ::binding-form)) + :as (s/? (s/cat :as #{:as} :sym ::local-name)))) + +;; map destructuring + +(s/def ::keys (s/coll-of ident? :kind vector?)) +(s/def ::syms (s/coll-of symbol? :kind vector?)) +(s/def ::strs (s/coll-of simple-symbol? :kind vector?)) +(s/def ::or (s/map-of simple-symbol? any?)) +(s/def ::as ::local-name) + +(s/def ::map-special-binding + (s/keys :opt-un [::as ::or ::keys ::syms ::strs])) + +(s/def ::map-binding (s/tuple ::binding-form any?)) + +(s/def ::ns-keys + (s/tuple + (s/and qualified-keyword? #(-> % name #{"keys" "syms"})) + (s/coll-of simple-symbol? :kind vector?))) + +(s/def ::map-bindings + (s/every (s/or :mb ::map-binding + :nsk ::ns-keys + :msb (s/tuple #{:as :or :keys :syms :strs} any?)) :into {})) + +(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding)) + +;; bindings + +(s/def ::binding (s/cat :binding ::binding-form :init-expr any?)) +(s/def ::bindings (s/and vector? (s/* ::binding))) + +;; let, if-let, when-let + +(s/fdef clojure.core/let + :args (s/cat :bindings ::bindings + :body (s/* any?))) + +(s/fdef clojure.core/if-let + :args (s/cat :bindings (s/and vector? ::binding) + :then any? + :else (s/? any?))) + +(s/fdef clojure.core/when-let + :args (s/cat :bindings (s/and vector? ::binding) + :body (s/* any?))) diff --git a/test/clojure/test_clojure/errors.clj b/test/clojure/test_clojure/errors.clj index 16b937a366..a1c827710e 100644 --- a/test/clojure/test_clojure/errors.clj +++ b/test/clojure/test_clojure/errors.clj @@ -42,15 +42,10 @@ (refer 'clojure.core :rename '{with-open renamed-with-open}) ; would have used `are` here, but :line meta on &form doesn't survive successive macroexpansions - (doseq [[msg-regex-str form] [["if-let .* in %s:\\d+" '(if-let [a 5 - b 6] - true nil)] - ["let .* in %s:\\d+" '(let [a])] - ["let .* in %s:\\d+" '(let (a))] - ["renamed-with-open .* in %s:\\d+" '(renamed-with-open [a])]]] + (doseq [[msg-regex-str form] [["renamed-with-open" "(renamed-with-open [a])"]]] (is (thrown-with-msg? IllegalArgumentException (re-pattern (format msg-regex-str *ns*)) - (macroexpand form))))) + (macroexpand (read-string form)))))) (deftest extract-ex-data (try diff --git a/test/clojure/test_clojure/special.clj b/test/clojure/test_clojure/special.clj index cae206b735..abfe1bfb7f 100644 --- a/test/clojure/test_clojure/special.clj +++ b/test/clojure/test_clojure/special.clj @@ -67,19 +67,19 @@ (is (= [1 2 3] [b c d])))) (deftest keywords-not-allowed-in-let-bindings - (is (thrown-with-msg? Exception #"Unsupported binding form: :a" + (is (thrown-with-msg? Exception #"did not conform to spec" (eval '(let [:a 1] a)))) - (is (thrown-with-msg? Exception #"Unsupported binding form: :a/b" + (is (thrown-with-msg? Exception #"did not conform to spec" (eval '(let [:a/b 1] b)))) - (is (thrown-with-msg? Exception #"Unsupported binding form: :a" + (is (thrown-with-msg? Exception #"did not conform to spec" (eval '(let [[:a] [1]] a)))) - (is (thrown-with-msg? Exception #"Unsupported binding form: :a/b" + (is (thrown-with-msg? Exception #"did not conform to spec" (eval '(let [[:a/b] [1]] b))))) (deftest namespaced-syms-only-allowed-in-map-destructuring - (is (thrown-with-msg? Exception #"Can't let qualified name: a/x" + (is (thrown-with-msg? Exception #"did not conform to spec" (eval '(let [a/x 1, [y] [1]] x)))) - (is (thrown-with-msg? Exception #"Can't let qualified name: a/x" + (is (thrown-with-msg? Exception #"did not conform to spec" (eval '(let [[a/x] [1]] x))))) (deftest or-doesnt-create-bindings From 4322f3b36c4ea7763b9451618c38ab5bc2840dd9 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 21 Jul 2016 15:38:54 -0500 Subject: [PATCH 272/854] specs for defn, defn-, fn Signed-off-by: Rich Hickey --- src/clj/clojure/core/specs.clj | 35 +++++++++++++++++++++++++++++++ test/clojure/test_clojure/def.clj | 12 +++++------ test/clojure/test_clojure/fn.clj | 16 +++++++------- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index 1c588b4e92..fe21254776 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -61,3 +61,38 @@ (s/fdef clojure.core/when-let :args (s/cat :bindings (s/and vector? ::binding) :body (s/* any?))) + +;; defn, defn-, fn + +(s/def ::arg-list + (s/and + vector? + (s/cat :args (s/* ::binding-form) + :varargs (s/? (s/cat :amp #{'&} :form ::binding-form))))) + +(s/def ::args+body + (s/cat :args ::arg-list + :prepost (s/? map?) + :body (s/* any?))) + +(def defn-args + (s/cat :name simple-symbol? + :docstring (s/? string?) + :meta (s/? map?) + :bs (s/alt :arity-1 ::args+body + :arity-n (s/cat :bodies (s/+ (s/spec ::args+body)) + :attr (s/? map?))))) + +(s/fdef clojure.core/defn + :args defn-args + :ret any?) + +(s/fdef clojure.core/defn- + :args defn-args + :ret any?) + +(s/fdef clojure.core/fn + :args (s/cat :name (s/? simple-symbol?) + :bs (s/alt :arity-1 ::args+body + :arity-n (s/+ (s/spec ::args+body)))) + :ret any?) diff --git a/test/clojure/test_clojure/def.clj b/test/clojure/test_clojure/def.clj index a01489aaeb..2b73ea3cd4 100644 --- a/test/clojure/test_clojure/def.clj +++ b/test/clojure/test_clojure/def.clj @@ -14,13 +14,13 @@ (testing "multiarity syntax invalid parameter declaration" (is (fails-with-cause? IllegalArgumentException - #"Parameter declaration \"arg1\" should be a vector" + #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn foo (arg1 arg2)))))) (testing "multiarity syntax invalid signature" (is (fails-with-cause? IllegalArgumentException - #"Invalid signature \"\[a b\]\" should be a list" + #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn foo ([a] 1) [a b]))))) @@ -28,19 +28,19 @@ (testing "assume single arity syntax" (is (fails-with-cause? IllegalArgumentException - #"Parameter declaration \"a\" should be a vector" + #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn foo a))))) (testing "bad name" (is (fails-with-cause? IllegalArgumentException - #"First argument to defn must be a symbol" + #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn "bad docstring" testname [arg1 arg2]))))) (testing "missing parameter/signature" (is (fails-with-cause? IllegalArgumentException - #"Parameter declaration missing" + #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn testname))))) (testing "allow trailing map" @@ -49,7 +49,7 @@ (testing "don't allow interleaved map" (is (fails-with-cause? IllegalArgumentException - #"Invalid signature \"\{:a :b\}\" should be a list" + #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn a "asdf" ([a] 1) {:a :b} ([] 1))))))) (deftest non-dynamic-warnings diff --git a/test/clojure/test_clojure/fn.clj b/test/clojure/test_clojure/fn.clj index c85b155d95..3c1314809e 100644 --- a/test/clojure/test_clojure/fn.clj +++ b/test/clojure/test_clojure/fn.clj @@ -14,42 +14,42 @@ (deftest fn-error-checking (testing "bad arglist" (is (fails-with-cause? java.lang.IllegalArgumentException - #"Parameter declaration a should be a vector" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn "a" a))))) (testing "treat first param as args" (is (fails-with-cause? java.lang.IllegalArgumentException - #"Parameter declaration a should be a vector" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn "a" []))))) (testing "looks like listy signature, but malformed declaration" (is (fails-with-cause? java.lang.IllegalArgumentException - #"Parameter declaration 1 should be a vector" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn (1)))))) (testing "checks each signature" (is (fails-with-cause? java.lang.IllegalArgumentException - #"Parameter declaration a should be a vector" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn ([a] 1) ("a" 2)))))) (testing "correct name but invalid args" (is (fails-with-cause? java.lang.IllegalArgumentException - #"Parameter declaration a should be a vector" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn a "a"))))) (testing "first sig looks multiarity, rest of sigs should be lists" (is (fails-with-cause? java.lang.IllegalArgumentException - #"Invalid signature \[a b\] should be a list" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn a ([a] 1) [a b]))))) (testing "missing parameter declaration" (is (fails-with-cause? java.lang.IllegalArgumentException - #"Parameter declaration missing" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn a)))) (is (fails-with-cause? java.lang.IllegalArgumentException - #"Parameter declaration missing" + #"Call to clojure.core/fn did not conform to spec" (eval '(fn)))))) From 1c8a13a9a9dbcafda3168ab191ba6e5ce61c8f96 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 26 Jul 2016 13:43:24 -0500 Subject: [PATCH 273/854] specs for ns Signed-off-by: Rich Hickey --- src/clj/clojure/core/specs.clj | 103 +++++++++++++++++++++++++++++ test/clojure/test_clojure/data.clj | 2 +- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index fe21254776..761749e9e1 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -96,3 +96,106 @@ :bs (s/alt :arity-1 ::args+body :arity-n (s/+ (s/spec ::args+body)))) :ret any?) + +;;;; ns + +(s/def ::exclude (s/coll-of simple-symbol?)) +(s/def ::only (s/coll-of simple-symbol?)) +(s/def ::rename (s/map-of simple-symbol? simple-symbol?)) + +(s/def ::ns-refer-clojure + (s/spec (s/cat :clause #{:refer-clojure} + :filters (s/keys* :opt-un [::exclude ::only ::rename])))) + +(s/def ::refer (s/or :all #{:all} + :syms (s/coll-of simple-symbol?))) + +(s/def ::prefix-list + (s/spec + (s/cat :prefix simple-symbol? + :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list)) + :refer (s/keys* :opt-un [::as ::refer])))) + +(s/def ::ns-require + (s/spec (s/cat :clause #{:require} + :libs (s/* (s/alt :lib simple-symbol? + :prefix-list ::prefix-list + :flag #{:reload :reload-all :verbose}))))) + +(s/def ::package-list + (s/spec + (s/cat :package simple-symbol? + :classes (s/* simple-symbol?)))) + +(s/def ::ns-import + (s/spec + (s/cat :clause #{:import} + :classes (s/* (s/alt :class simple-symbol? + :package-list ::package-list))))) + +(s/def ::ns-refer + (s/spec (s/cat :clause #{:refer} + :lib simple-symbol? + :filters (s/keys* :opt-un [::exclude ::only ::rename])))) + +(s/def ::use-prefix-list + (s/spec + (s/cat :prefix simple-symbol? + :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::use-prefix-list)) + :filters (s/keys* :opt-un [::exclude ::only ::rename])))) + +(s/def ::ns-use + (s/spec (s/cat :clause #{:use} + :libs (s/* (s/alt :lib simple-symbol? + :prefix-list ::use-prefix-list + :flag #{:reload :reload-all :verbose}))))) + +(s/def ::ns-load + (s/spec (s/cat :clause #{:load} + :libs (s/* string?)))) + +(s/def ::name simple-symbol?) +(s/def ::extends simple-symbol?) +(s/def ::implements (s/coll-of simple-symbol? :kind vector?)) +(s/def ::init symbol?) +(s/def ::signature (s/coll-of simple-symbol? :kind vector?)) +(s/def ::constructors (s/map-of ::signature ::signature)) +(s/def ::post-init symbol?) +(s/def ::method (s/and vector? + (s/cat :name simple-symbol? + :param-types ::signature + :return-type simple-symbol?))) +(s/def ::methods (s/coll-of ::method :kind vector?)) +(s/def ::main boolean?) +(s/def ::factory simple-symbol?) +(s/def ::state simple-symbol?) +(s/def ::get simple-symbol?) +(s/def ::set simple-symbol?) +(s/def ::expose (s/keys :opt-un [::get ::set])) +(s/def ::exposes (s/map-of simple-symbol? ::expose)) +(s/def ::prefix string?) +(s/def ::impl-ns simple-symbol?) +(s/def ::load-impl-ns boolean?) + +(s/def ::ns-gen-class + (s/spec (s/cat :clause #{:gen-class} + :options (s/keys* :opt-un [::name ::extends ::implements + ::init ::constructors ::post-init + ::methods ::main ::factory ::state + ::exposes ::prefix ::impl-ns ::load-impl-ns])))) + +(s/def ::ns-clauses + (s/* (s/alt :refer-clojure ::ns-refer-clojure + :require ::ns-require + :import ::ns-import + :use ::ns-use + :refer ::ns-refer + :load ::ns-load + :gen-class ::ns-gen-class))) + +(s/fdef clojure.core/ns + :args (s/cat :name simple-symbol? + :docstring (s/? string?) + :attr-map (s/? map?) + :clauses ::ns-clauses) + :ret any?) \ No newline at end of file diff --git a/test/clojure/test_clojure/data.clj b/test/clojure/test_clojure/data.clj index 5a241e07ee..0b6e5d5474 100644 --- a/test/clojure/test_clojure/data.clj +++ b/test/clojure/test_clojure/data.clj @@ -8,7 +8,7 @@ (ns clojure.test-clojure.data (:use clojure.data clojure.test) - (import java.util.HashSet)) + (:import java.util.HashSet)) (deftest diff-test (are [d x y] (= d (diff x y)) From d287874f016db26c198840cd8cb1a2c43559e870 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 15 Aug 2016 15:18:10 -0500 Subject: [PATCH 274/854] register defn-args spec Signed-off-by: Rich Hickey --- src/clj/clojure/core/specs.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index 761749e9e1..c66c6066d0 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -75,7 +75,7 @@ :prepost (s/? map?) :body (s/* any?))) -(def defn-args +(s/def ::defn-args (s/cat :name simple-symbol? :docstring (s/? string?) :meta (s/? map?) @@ -84,11 +84,11 @@ :attr (s/? map?))))) (s/fdef clojure.core/defn - :args defn-args + :args ::defn-args :ret any?) (s/fdef clojure.core/defn- - :args defn-args + :args ::defn-args :ret any?) (s/fdef clojure.core/fn @@ -198,4 +198,4 @@ :docstring (s/? string?) :attr-map (s/? map?) :clauses ::ns-clauses) - :ret any?) \ No newline at end of file + :ret any?) From de899203203b2e38bf20e1babc19fbdd9f791c9c Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 20 Jul 2016 08:34:51 -0500 Subject: [PATCH 275/854] pass unconform along on conformer with-gen Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 295e6941a4..7842525552 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -802,7 +802,7 @@ (gen* [_ _ _ _] (if gfn (gfn) (gen/gen-for-pred pred))) - (with-gen* [_ gfn] (spec-impl form pred gfn cpred?)) + (with-gen* [_ gfn] (spec-impl form pred gfn cpred? unc)) (describe* [_] form))))) (defn ^:skip-wiki multi-spec-impl From b49c1984a1527d17951fbb23ddf9406805a1343f Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 16 Aug 2016 10:07:32 -0500 Subject: [PATCH 276/854] add *print-namespace-maps* flag to control namespace map printing - false by default, but true by default in the repl Signed-off-by: Rich Hickey --- src/clj/clojure/core_print.clj | 16 ++++++++++++---- src/clj/clojure/main.clj | 1 + test/clojure/test_clojure/printer.clj | 4 +++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 6dd99b5183..99da234231 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -38,6 +38,12 @@ (def ^:dynamic *verbose-defrecords* false) +(def ^:dynamic + ^{:doc "*print-namespace-maps* controls whether the printer will print + namespace map literal syntax. It defaults to true." + :added "1.9"} + *print-namespace-maps* false) + (defn- print-sequential [^String begin, print-one, ^String sep, ^String end, sequence, ^Writer w] (binding [*print-level* (and (not *print-dup*) *print-level* (dec *print-level*))] (if (and *print-level* (neg? *print-level*)) @@ -240,10 +246,12 @@ (defmethod print-method clojure.lang.IPersistentMap [m, ^Writer w] (print-meta m w) - (let [[ns lift-map] (lift-ns m)] - (if ns - (print-prefix-map (str "#:" ns) lift-map pr-on w) - (print-map m pr-on w)))) + (if *print-namespace-maps* + (let [[ns lift-map] (lift-ns m)] + (if ns + (print-prefix-map (str "#:" ns) lift-map pr-on w) + (print-map m pr-on w))) + (print-map m pr-on w))) (defmethod print-dup java.util.Map [m, ^Writer w] (print-ctor m #(print-map (seq %1) print-dup %2) w)) diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj index 14af9c7e85..97ec7d365c 100644 --- a/src/clj/clojure/main.clj +++ b/src/clj/clojure/main.clj @@ -74,6 +74,7 @@ *print-meta* *print-meta* *print-length* *print-length* *print-level* *print-level* + *print-namespace-maps* true *data-readers* *data-readers* *default-data-reader-fn* *default-data-reader-fn* *compile-path* (System/getProperty "clojure.compile.path" "classes") diff --git a/test/clojure/test_clojure/printer.clj b/test/clojure/test_clojure/printer.clj index aa75d1058d..61efcf44b3 100644 --- a/test/clojure/test_clojure/printer.clj +++ b/test/clojure/test_clojure/printer.clj @@ -134,4 +134,6 @@ (ex-info "the root" {:with "even" :more 'data}))))))) - +(deftest print-ns-maps + (is (= "#:user{:a 1}" (binding [*print-namespace-maps* true] (pr-str {:user/a 1})))) + (is (= "{:user/a 1}" (binding [*print-namespace-maps* false] (pr-str {:user/a 1}))))) From d57b5559829be8e8b3dcb28a20876b32615af0cb Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 16 Aug 2016 15:07:08 -0500 Subject: [PATCH 277/854] fix *print-namespace-map* docstring Signed-off-by: Rich Hickey --- src/clj/clojure/core_print.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 99da234231..da02330254 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -40,7 +40,8 @@ (def ^:dynamic ^{:doc "*print-namespace-maps* controls whether the printer will print - namespace map literal syntax. It defaults to true." + namespace map literal syntax. It defaults to false, but the REPL binds + to true." :added "1.9"} *print-namespace-maps* false) From 9e6020c30ea229e80227877bd51254ecafc9e4a4 Mon Sep 17 00:00:00 2001 From: Alan Malloy Date: Mon, 18 Jan 2016 17:28:38 -0600 Subject: [PATCH 278/854] CLJ-1423 Allow vars to be invoked with infinite arglists Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Var.java | 2 +- test/clojure/test_clojure/vars.clj | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/jvm/clojure/lang/Var.java b/src/jvm/clojure/lang/Var.java index 9e79aac830..3dc9580c64 100644 --- a/src/jvm/clojure/lang/Var.java +++ b/src/jvm/clojure/lang/Var.java @@ -697,7 +697,7 @@ public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object } public Object applyTo(ISeq arglist) { - return AFn.applyToHelper(this, arglist); + return fn().applyTo(arglist); } static IFn assoc = new AFn(){ diff --git a/test/clojure/test_clojure/vars.clj b/test/clojure/test_clojure/vars.clj index b1d9e2da03..6b454d2bad 100644 --- a/test/clojure/test_clojure/vars.clj +++ b/test/clojure/test_clojure/vars.clj @@ -97,4 +97,13 @@ (is (= 2 dynamic-var)) (with-redefs [dynamic-var 3] (is (= 2 dynamic-var)))) - (is (= 1 dynamic-var))) \ No newline at end of file + (is (= 1 dynamic-var))) + +(defn sample [& args] + 0) + +(deftest test-vars-apply-lazily + (is (= 0 (deref (future (apply sample (range))) + 1000 :timeout))) + (is (= 0 (deref (future (apply #'sample (range))) + 1000 :timeout)))) From 1f4318021e61e8dfa68960cf44018ed4d4f79a44 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Wed, 3 Jun 2015 16:37:58 +0200 Subject: [PATCH 279/854] CLJ-1744: clear unused locals Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index c455d32c26..8bc97c77d8 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -5858,6 +5858,7 @@ public static class LocalBinding{ public final PathNode clearPathRoot; public boolean canBeCleared = !RT.booleanCast(getCompilerOption(disableLocalsClearingKey)); public boolean recurMistmatch = false; + public boolean used = false; public LocalBinding(int num, Symbol sym, Symbol tag, Expr init, boolean isArg,PathNode clearPathRoot) { @@ -5910,6 +5911,7 @@ public LocalBindingExpr(LocalBinding b, Symbol tag) this.clearPath = (PathNode)CLEAR_PATH.get(); this.clearRoot = (PathNode)CLEAR_ROOT.get(); IPersistentCollection sites = (IPersistentCollection) RT.get(CLEAR_SITES.get(),b); + b.used = true; if(b.idx > 0) { @@ -6371,7 +6373,10 @@ public void doEmit(C context, ObjExpr objx, GeneratorAdapter gen, boolean emitUn else { bi.init.emit(C.EXPRESSION, objx, gen); - gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), bi.binding.idx); + if (!bi.binding.used && bi.binding.canBeCleared) + gen.pop(); + else + gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), bi.binding.idx); } bindingLabels.put(bi, gen.mark()); } From 134c29990e046d19b209c92a1e8033e9841489ac Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 14 Apr 2016 21:34:45 -0700 Subject: [PATCH 280/854] CLJ-1914 Avoid race in concurrent range realization Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/LongRange.java | 2 +- test/clojure/test_clojure/sequences.clj | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/LongRange.java b/src/jvm/clojure/lang/LongRange.java index 90629d90c2..348e362081 100644 --- a/src/jvm/clojure/lang/LongRange.java +++ b/src/jvm/clojure/lang/LongRange.java @@ -128,8 +128,8 @@ public void forceChunk() { if (count > CHUNK_SIZE) { // not last chunk long nextStart = start + (step * CHUNK_SIZE); // cannot overflow, must be < end - _chunk = new LongChunk(start, step, CHUNK_SIZE); _chunkNext = new LongRange(nextStart, end, step, boundsCheck); + _chunk = new LongChunk(start, step, CHUNK_SIZE); } else { // last chunk _chunk = new LongChunk(start, step, (int) count); // count must be <= CHUNK_SIZE } diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj index e3adb277e9..146869beb7 100644 --- a/test/clojure/test_clojure/sequences.clj +++ b/test/clojure/test_clojure/sequences.clj @@ -1058,6 +1058,24 @@ (reduce + (iterator-seq (.iterator (range 100)))) 4950 (reduce + (iterator-seq (.iterator (range 0.0 100.0 1.0)))) 4950.0 )) +(deftest range-test + (let [threads 10 + n 1000 + r (atom (range (inc n))) + m (atom 0)] + ; Iterate through the range concurrently, + ; updating m to the highest seen value in the range + (->> (range threads) + (map (fn [id] + (future + (loop [] + (when-let [r (swap! r next)] + (swap! m max (first r)) + (recur)))))) + (map deref) + dorun) + (is (= n @m)))) + (defn unlimited-range-create [& args] (let [[arg1 arg2 arg3] args] (case (count args) From b26a8d0efc0675d0040dfbb28e8b489b866f7507 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Tue, 22 Dec 2015 23:58:28 +0000 Subject: [PATCH 281/854] CLJ-1870: don't destroy defmulti metadata on reload Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 5cbb02605f..6b8a1b5e1a 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1757,7 +1757,8 @@ m) m (if (meta mm-name) (conj (meta mm-name) m) - m)] + m) + mm-name (with-meta mm-name m)] (when (= (count options) 1) (throw (Exception. "The syntax for defmulti has changed. Example: (defmulti name dispatch-fn :default dispatch-value)"))) (let [options (apply hash-map options) @@ -1766,7 +1767,7 @@ (check-valid-options options :default :hierarchy) `(let [v# (def ~mm-name)] (when-not (and (.hasRoot v#) (instance? clojure.lang.MultiFn (deref v#))) - (def ~(with-meta mm-name m) + (def ~mm-name (new clojure.lang.MultiFn ~(name mm-name) ~dispatch-fn ~default ~hierarchy))))))) (defmacro defmethod From 21256078bc0304ab5b6af521f64e986eedbe5ee2 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 19 Aug 2016 12:59:01 -0500 Subject: [PATCH 282/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha11 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..61a3b7f42e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha11 http://clojure.org/ Clojure core environment and runtime library. From 313da5553bc52ba257e42e53c8f612eb1ae9fcca Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 19 Aug 2016 12:59:01 -0500 Subject: [PATCH 283/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 61a3b7f42e..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha11 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 05a8e8b323042fa043355b716facaed6003af324 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 22 Aug 2016 16:56:41 -0500 Subject: [PATCH 284/854] pprint maps with namespace map literal syntax when *print-namespace-maps* Signed-off-by: Rich Hickey --- src/clj/clojure/core_print.clj | 33 ++++++++++++++--------------- src/clj/clojure/pprint/dispatch.clj | 30 ++++++++++++++------------ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index da02330254..6c75e87974 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -233,26 +233,25 @@ (defn- lift-ns "Returns [lifted-ns lifted-map] or nil if m can't be lifted." [m] - (loop [ns nil - [[k v :as entry] & entries] (seq m) - lm (empty m)] - (if entry - (when (or (keyword? k) (symbol? k)) - (if ns - (when (= ns (namespace k)) - (recur ns entries (assoc lm (strip-ns k) v))) - (when-let [new-ns (namespace k)] - (recur new-ns entries (assoc lm (strip-ns k) v))))) - [ns lm]))) + (when *print-namespace-maps* + (loop [ns nil + [[k v :as entry] & entries] (seq m) + lm (empty m)] + (if entry + (when (or (keyword? k) (symbol? k)) + (if ns + (when (= ns (namespace k)) + (recur ns entries (assoc lm (strip-ns k) v))) + (when-let [new-ns (namespace k)] + (recur new-ns entries (assoc lm (strip-ns k) v))))) + [ns lm])))) (defmethod print-method clojure.lang.IPersistentMap [m, ^Writer w] (print-meta m w) - (if *print-namespace-maps* - (let [[ns lift-map] (lift-ns m)] - (if ns - (print-prefix-map (str "#:" ns) lift-map pr-on w) - (print-map m pr-on w))) - (print-map m pr-on w))) + (let [[ns lift-map] (lift-ns m)] + (if ns + (print-prefix-map (str "#:" ns) lift-map pr-on w) + (print-map m pr-on w)))) (defmethod print-dup java.util.Map [m, ^Writer w] (print-ctor m #(print-map (seq %1) print-dup %2) w)) diff --git a/src/clj/clojure/pprint/dispatch.clj b/src/clj/clojure/pprint/dispatch.clj index 323348eb59..1ef9a578b6 100644 --- a/src/clj/clojure/pprint/dispatch.clj +++ b/src/clj/clojure/pprint/dispatch.clj @@ -92,19 +92,23 @@ ;;; (def pprint-map (formatter-out "~<{~;~@{~<~w~^ ~_~w~:>~^, ~_~}~;}~:>")) (defn- pprint-map [amap] - (pprint-logical-block :prefix "{" :suffix "}" - (print-length-loop [aseq (seq amap)] - (when aseq - (pprint-logical-block - (write-out (ffirst aseq)) - (.write ^java.io.Writer *out* " ") - (pprint-newline :linear) - (set! *current-length* 0) ; always print both parts of the [k v] pair - (write-out (fnext (first aseq)))) - (when (next aseq) - (.write ^java.io.Writer *out* ", ") - (pprint-newline :linear) - (recur (next aseq))))))) + (let [[ns lift-map] (when (not (record? amap)) + (#'clojure.core/lift-ns amap)) + amap (or lift-map amap) + prefix (if ns (str "#:" ns "{") "{")] + (pprint-logical-block :prefix prefix :suffix "}" + (print-length-loop [aseq (seq amap)] + (when aseq + (pprint-logical-block + (write-out (ffirst aseq)) + (.write ^java.io.Writer *out* " ") + (pprint-newline :linear) + (set! *current-length* 0) ; always print both parts of the [k v] pair + (write-out (fnext (first aseq)))) + (when (next aseq) + (.write ^java.io.Writer *out* ", ") + (pprint-newline :linear) + (recur (next aseq)))))))) (def ^{:private true} pprint-set (formatter-out "~<#{~;~@{~w~^ ~:_~}~;}~:>")) From b3d3a5d6ff0a2f435bb6a5326da2b960038adad4 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 25 Aug 2016 16:51:34 -0500 Subject: [PATCH 285/854] throw ex-info on macroexpand spec error with ex-data Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 5 +++-- test/clojure/test_clojure/def.clj | 12 ++++++------ test/clojure/test_clojure/fn.clj | 16 ++++++++-------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 7842525552..cc8e130eaa 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -621,10 +621,11 @@ (let [ed (assoc (explain-data* arg-spec [:args] (if-let [name (spec-name arg-spec)] [name] []) [] args) ::args args)] - (throw (IllegalArgumentException. + (throw (ex-info (str "Call to " (->sym v) " did not conform to spec:\n" - (with-out-str (explain-out ed)))))))))) + (with-out-str (explain-out ed))) + ed))))))) (defmacro fdef "Takes a symbol naming a function, and one or more of the following: diff --git a/test/clojure/test_clojure/def.clj b/test/clojure/test_clojure/def.clj index 2b73ea3cd4..7ea442ef70 100644 --- a/test/clojure/test_clojure/def.clj +++ b/test/clojure/test_clojure/def.clj @@ -13,13 +13,13 @@ (deftest defn-error-messages (testing "multiarity syntax invalid parameter declaration" (is (fails-with-cause? - IllegalArgumentException + clojure.lang.ExceptionInfo #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn foo (arg1 arg2)))))) (testing "multiarity syntax invalid signature" (is (fails-with-cause? - IllegalArgumentException + clojure.lang.ExceptionInfo #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn foo ([a] 1) @@ -27,19 +27,19 @@ (testing "assume single arity syntax" (is (fails-with-cause? - IllegalArgumentException + clojure.lang.ExceptionInfo #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn foo a))))) (testing "bad name" (is (fails-with-cause? - IllegalArgumentException + clojure.lang.ExceptionInfo #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn "bad docstring" testname [arg1 arg2]))))) (testing "missing parameter/signature" (is (fails-with-cause? - IllegalArgumentException + clojure.lang.ExceptionInfo #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn testname))))) @@ -48,7 +48,7 @@ (testing "don't allow interleaved map" (is (fails-with-cause? - IllegalArgumentException + clojure.lang.ExceptionInfo #"Call to clojure.core/defn did not conform to spec" (eval-in-temp-ns (defn a "asdf" ([a] 1) {:a :b} ([] 1))))))) diff --git a/test/clojure/test_clojure/fn.clj b/test/clojure/test_clojure/fn.clj index 3c1314809e..dfd1eaf244 100644 --- a/test/clojure/test_clojure/fn.clj +++ b/test/clojure/test_clojure/fn.clj @@ -13,43 +13,43 @@ (deftest fn-error-checking (testing "bad arglist" - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn "a" a))))) (testing "treat first param as args" - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn "a" []))))) (testing "looks like listy signature, but malformed declaration" - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn (1)))))) (testing "checks each signature" - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn ([a] 1) ("a" 2)))))) (testing "correct name but invalid args" - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn a "a"))))) (testing "first sig looks multiarity, rest of sigs should be lists" - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn a ([a] 1) [a b]))))) (testing "missing parameter declaration" - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn a)))) - (is (fails-with-cause? java.lang.IllegalArgumentException + (is (fails-with-cause? clojure.lang.ExceptionInfo #"Call to clojure.core/fn did not conform to spec" (eval '(fn)))))) From 99ab306f82620e6db6a978a5565d2ccd668c0798 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 25 Aug 2016 11:12:50 -0500 Subject: [PATCH 286/854] Make clojure spec explain printer pluggable. Moved old printer to clojure.spec/explain-printer. Added dynamic variable clojure.spec/*explain-out*. Set default *explain-out* to explain-printer. Changed explain-out to invoke *explain-out*. Changed repl to bind *explain-out* so it can be set!'ed. Signed-off-by: Rich Hickey --- src/clj/clojure/main.clj | 1 + src/clj/clojure/spec.clj | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj index 97ec7d365c..c023f1f863 100644 --- a/src/clj/clojure/main.clj +++ b/src/clj/clojure/main.clj @@ -81,6 +81,7 @@ *command-line-args* *command-line-args* *unchecked-math* *unchecked-math* *assert* *assert* + clojure.spec/*explain-out* clojure.spec/*explain-out* *1 nil *2 nil *3 nil diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index cc8e130eaa..80206973f9 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -175,8 +175,8 @@ [spec x] (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) [] x)) -(defn explain-out - "prints explanation data (per 'explain-data') to *out*." +(defn explain-printer + "Default printer for explain-data. nil indicates a successful validation." [ed] (if ed (do @@ -206,6 +206,14 @@ (newline)))) (println "Success!"))) +(def ^:dynamic *explain-out* explain-printer) + +(defn explain-out + "Prints explanation data (per 'explain-data') to *out* using the printer in *explain-out*, + by default explain-printer." + [ed] + (*explain-out* ed)) + (defn explain "Given a spec and a value that fails to conform, prints an explanation to *out*." [spec x] From de6a2b528a18bcb4768e82d0d707d2cab26268a6 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 30 Aug 2016 14:59:48 -0400 Subject: [PATCH 287/854] perf tweaks (resolve, or et al) --- src/clj/clojure/spec.clj | 102 +++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 36 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 80206973f9..2fc8b8d5db 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -44,28 +44,21 @@ (defonce ^:private registry-ref (atom {})) -(defn- named? [x] (instance? clojure.lang.Named x)) - -(defn- with-name [spec name] - (with-meta spec (assoc (meta spec) ::name name))) - -(defn- spec-name [spec] - (cond - (keyword? spec) spec - - (instance? clojure.lang.IObj spec) - (-> (meta spec) ::name))) +(defn- deep-resolve [reg k] + (loop [spec k] + (if (ident? spec) + (recur (get reg spec)) + spec))) (defn- reg-resolve - "returns the spec/regex at end of alias chain starting with k, nil if not found, k if k not Named" + "returns the spec/regex at end of alias chain starting with k, nil if not found, k if k not ident" [k] - (if (named? k) - (let [reg @registry-ref] - (loop [spec k] - (if (named? spec) - (recur (get reg spec)) - (when spec - (with-name spec k))))) + (if (ident? k) + (let [reg @registry-ref + spec (get reg k)] + (if-not (ident? spec) + spec + (deep-resolve reg spec))) k)) (defn- reg-resolve! @@ -86,15 +79,32 @@ [x] (c/and (::op x) x)) +(defn- with-name [spec name] + (cond + (ident? spec) spec + (regex? spec) (assoc spec ::name name) + + (instance? clojure.lang.IObj spec) + (with-meta spec (assoc (meta spec) ::name name)))) + +(defn- spec-name [spec] + (cond + (ident? spec) spec + + (regex? spec) (::name spec) + + (instance? clojure.lang.IObj spec) + (-> (meta spec) ::name))) + (declare spec-impl) (declare regex-spec-impl) (defn- maybe-spec "spec-or-k must be a spec, regex or resolvable kw/sym, else returns nil." [spec-or-k] - (let [s (c/or (spec? spec-or-k) + (let [s (c/or (c/and (ident? spec-or-k) (reg-resolve spec-or-k)) + (spec? spec-or-k) (regex? spec-or-k) - (c/and (named? spec-or-k) (reg-resolve spec-or-k)) nil)] (if (regex? s) (with-name (regex-spec-impl s nil) (spec-name s)) @@ -104,11 +114,27 @@ "spec-or-k must be a spec, regex or kw/sym, else returns nil. Throws if unresolvable kw/sym" [spec-or-k] (c/or (maybe-spec spec-or-k) - (when (named? spec-or-k) + (when (ident? spec-or-k) (throw (Exception. (str "Unable to resolve spec: " spec-or-k)))))) +(defprotocol Specize + (specize* [_])) + +(extend-protocol Specize + clojure.lang.Keyword + (specize* [k] (specize* (reg-resolve! k))) + + clojure.lang.Symbol + (specize* [s] (specize* (reg-resolve! s))) + + clojure.spec.Spec + (specize* [s] s) + + Object + (specize* [o] (spec-impl ::unknown o nil nil))) + (defn- specize [s] - (c/or (the-spec s) (spec-impl ::unknown s nil nil))) + (specize* s)) (defn conform "Given a spec and a value, returns :clojure.spec/invalid if value does not match spec, @@ -279,11 +305,11 @@ (defn ^:skip-wiki def-impl "Do not call this directly, use 'def'" [k form spec] - (c/assert (c/and (named? k) (namespace k)) "k must be namespaced keyword or resolvable symbol") + (c/assert (c/and (ident? k) (namespace k)) "k must be namespaced keyword or resolvable symbol") (let [spec (if (c/or (spec? spec) (regex? spec) (get @registry-ref spec)) spec (spec-impl form spec nil nil))] - (swap! registry-ref assoc k spec) + (swap! registry-ref assoc k (with-name spec k)) k)) (defn- ns-qualify @@ -795,11 +821,13 @@ (cond (spec? pred) (cond-> pred gfn (with-gen gfn)) (regex? pred) (regex-spec-impl pred gfn) - (named? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) + (ident? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) :else (reify Spec - (conform* [_ x] (dt pred x form cpred?)) + (conform* [_ x] (if cpred? + (pred x) + (if (pred x) x ::invalid))) (unform* [_ x] (if cpred? (if unc (unc x) @@ -924,15 +952,17 @@ [keys forms preds gfn] (let [id (java.util.UUID/randomUUID) kps (zipmap keys preds) - cform (fn [x] - (loop [i 0] - (if (< i (count preds)) - (let [pred (preds i)] - (let [ret (dt pred x (nth forms i))] - (if (= ::invalid ret) - (recur (inc i)) - (tagged-ret (keys i) ret)))) - ::invalid)))] + cform (let [specs (delay (mapv specize preds))] + (fn [x] + (let [specs @specs] + (loop [i 0] + (if (< i (count specs)) + (let [spec (specs i)] + (let [ret (conform* spec x)] + (if (= ::invalid ret) + (recur (inc i)) + (tagged-ret (keys i) ret)))) + ::invalid)))))] (reify Spec (conform* [_ x] (cform x)) From defa7b8ef268ea2b8772658ade2010ca5ad00dc4 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 30 Aug 2016 17:31:42 -0400 Subject: [PATCH 288/854] perf tweaks (and, unrolling, nonconforming in nilable) --- src/clj/clojure/spec.clj | 109 +++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 2fc8b8d5db..f925f0fcd9 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -952,17 +952,39 @@ [keys forms preds gfn] (let [id (java.util.UUID/randomUUID) kps (zipmap keys preds) - cform (let [specs (delay (mapv specize preds))] - (fn [x] - (let [specs @specs] - (loop [i 0] - (if (< i (count specs)) - (let [spec (specs i)] - (let [ret (conform* spec x)] - (if (= ::invalid ret) - (recur (inc i)) - (tagged-ret (keys i) ret)))) - ::invalid)))))] + specs (delay (mapv specize preds)) + cform (case (count preds) + 2 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (= ::invalid ret) + (let [ret (conform* (specs 1) x)] + (if (= ::invalid ret) + ::invalid + (tagged-ret (keys 1) ret))) + (tagged-ret (keys 0) ret)))) + 3 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (= ::invalid ret) + (let [ret (conform* (specs 1) x)] + (if (= ::invalid ret) + (let [ret (conform* (specs 2) x)] + (if (= ::invalid ret) + ::invalid + (tagged-ret (keys 2) ret))) + (tagged-ret (keys 1) ret))) + (tagged-ret (keys 0) ret)))) + (fn [x] + (let [specs @specs] + (loop [i 0] + (if (< i (count specs)) + (let [spec (specs i)] + (let [ret (conform* spec x)] + (if (= ::invalid ret) + (recur (inc i)) + (tagged-ret (keys i) ret)))) + ::invalid)))))] (reify Spec (conform* [_ x] (cform x)) @@ -1014,14 +1036,42 @@ (defn ^:skip-wiki and-spec-impl "Do not call this directly, use 'and'" [forms preds gfn] - (reify - Spec - (conform* [_ x] (and-preds x preds forms)) - (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) - (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) - (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) - (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) - (describe* [_] `(and ~@forms)))) + (let [specs (delay (mapv specize preds)) + cform + (case (count preds) + 2 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (= ::invalid ret) + ::invalid + (conform* (specs 1) ret)))) + 3 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (= ::invalid ret) + ::invalid + (let [ret (conform* (specs 1) ret)] + (if (= ::invalid ret) + ::invalid + (conform* (specs 2) ret)))))) + (fn [x] + (let [specs @specs] + (loop [ret x i 0] + (if (< i (count specs)) + (let [nret (conform* (specs i) ret)] + (if (= ::invalid nret) + ::invalid + ;;propagate conformed values + (recur nret (inc i)))) + ret)))))] + (reify + Spec + (conform* [_ x] (cform x)) + (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) + (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) + (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) + (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) + (describe* [_] `(and ~@forms))))) (defn ^:skip-wiki merge-spec-impl "Do not call this directly, use 'merge'" @@ -1609,10 +1659,27 @@ (with-gen (clojure.spec/& (* (cat ::k keyword? ::v any?)) ::kvs->map mspec#) (fn [] (gen/fmap (fn [m#] (apply concat m#)) (gen mspec#)))))) +(defn nonconforming + "takes a spec and returns a spec that has the same properties except + 'conform' returns the original (not the conformed) value. Note, will specize regex ops." + [spec] + (let [spec (specize spec)] + (reify + Spec + (conform* [_ x] (let [ret (conform* spec x)] + (if (= ::invalid ret) + ::invalid + x))) + (unform* [_ x] (unform* spec x)) + (explain* [_ path via in x] (explain* spec path via in x)) + (gen* [_ overrides path rmap] (gen* spec overrides path rmap)) + (with-gen* [_ gfn] (nonconforming (with-gen* spec gfn))) + (describe* [_] `(nonconforming ~(describe* spec)))))) + (defmacro nilable - "returns a spec that accepts nil and values satisfiying pred" + "returns a spec that accepts nil and values satisfying pred" [pred] - `(and (or ::nil nil? ::pred ~pred) (conformer second #(if (nil? %) [::nil nil] [::pred %])))) + `(nonconforming (or ::nil nil? ::pred ~pred))) (defn exercise "generates a number (default 10) of values compatible with spec and maps conform over them, From 021a3adf131d3f4158acd9e5d08ca91eb36ab56d Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 1 Sep 2016 20:42:01 -0400 Subject: [PATCH 289/854] perf tweaks --- src/clj/clojure/spec.clj | 268 +++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 122 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index f925f0fcd9..53b7063b7c 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -118,23 +118,33 @@ (throw (Exception. (str "Unable to resolve spec: " spec-or-k)))))) (defprotocol Specize - (specize* [_])) + (specize* [_] [_ form])) (extend-protocol Specize clojure.lang.Keyword - (specize* [k] (specize* (reg-resolve! k))) + (specize* ([k] (specize* (reg-resolve! k))) + ([k _] (specize* (reg-resolve! k)))) clojure.lang.Symbol - (specize* [s] (specize* (reg-resolve! s))) + (specize* ([s] (specize* (reg-resolve! s))) + ([s _] (specize* (reg-resolve! s)))) clojure.spec.Spec - (specize* [s] s) + (specize* ([s] s) + ([s _] s)) Object - (specize* [o] (spec-impl ::unknown o nil nil))) + (specize* ([o] (spec-impl ::unknown o nil nil)) + ([o form] (spec-impl form o nil nil)))) -(defn- specize [s] - (specize* s)) +(defn- specize + ([s] (specize* s)) + ([s form] (specize* s form))) + +(defn invalid? + "tests the validity of a conform return value" + [ret] + (identical? ::invalid ret)) (defn conform "Given a spec and a value, returns :clojure.spec/invalid if value does not match spec, @@ -426,26 +436,28 @@ req-keys (into req-keys (map unk req-un-specs)) opt-keys (into (vec opt) (map unk opt-un)) opt-specs (into (vec opt) opt-un) + gx (gensym) parse-req (fn [rk f] (map (fn [x] (if (keyword? x) - `#(contains? % ~(f x)) - (let [gx (gensym)] - `(fn* [~gx] - ~(walk/postwalk - (fn [y] (if (keyword? y) `(contains? ~gx ~(f y)) y)) - x))))) + `(contains? ~gx ~(f x)) + (walk/postwalk + (fn [y] (if (keyword? y) `(contains? ~gx ~(f y)) y)) + x))) rk)) - pred-exprs [`map?] + pred-exprs [`(map? ~gx)] pred-exprs (into pred-exprs (parse-req req identity)) pred-exprs (into pred-exprs (parse-req req-un unk)) + keys-pred `(fn* [~gx] (c/and ~@pred-exprs)) + pred-exprs (mapv (fn [e] `(fn* [~gx] ~e)) pred-exprs) pred-forms (walk/postwalk res pred-exprs)] - ;; `(map-spec-impl ~req-keys '~req ~opt '~pred-forms ~pred-exprs ~gen) + ;; `(map-spec-impl ~req-keys '~req ~opt '~pred-forms ~pred-exprs ~gen) `(map-spec-impl {:req '~req :opt '~opt :req-un '~req-un :opt-un '~opt-un :req-keys '~req-keys :req-specs '~req-specs :opt-keys '~opt-keys :opt-specs '~opt-specs :pred-forms '~pred-forms :pred-exprs ~pred-exprs + :keys-pred ~keys-pred :gfn ~gen}))) (defmacro or @@ -651,7 +663,7 @@ [v args] (let [fn-spec (get-spec v)] (when-let [arg-spec (:args fn-spec)] - (when (= ::invalid (conform arg-spec args)) + (when (invalid? (conform arg-spec args)) (let [ed (assoc (explain-data* arg-spec [:args] (if-let [name (spec-name arg-spec)] [name] []) [] args) ::args args)] @@ -720,9 +732,18 @@ (defn valid? "Helper function that returns true when x is valid for spec." ([spec x] - (not= ::invalid (dt spec x ::unknown))) + (let [spec (specize spec)] + (not (invalid? (conform* spec x))))) ([spec x form] - (not= ::invalid (dt spec x form)))) + (let [spec (specize spec form)] + (not (invalid? (conform* spec x)))))) + +(defn- pvalid? + "internal helper function that returns true when x is valid for spec." + ([pred x] + (not (invalid? (dt pred x ::unknown)))) + ([pred x form] + (not (invalid? (dt pred x form))))) (defn- explain-1 [form pred path via in v] ;;(prn {:form form :pred pred :path path :in in :v v}) @@ -733,36 +754,35 @@ (defn ^:skip-wiki map-spec-impl "Do not call this directly, use 'spec' with a map argument" - [{:keys [req-un opt-un pred-exprs opt-keys req-specs req req-keys opt-specs pred-forms opt gfn] + [{:keys [req-un opt-un keys-pred pred-exprs opt-keys req-specs req req-keys opt-specs pred-forms opt gfn] :as argm}] - (let [keys-pred (apply every-pred pred-exprs) - k->s (zipmap (concat req-keys opt-keys) (concat req-specs opt-specs)) - keys->specs #(c/or (k->s %) %) + (let [k->s (zipmap (concat req-keys opt-keys) (concat req-specs opt-specs)) + keys->specnames #(c/or (k->s %) %) id (java.util.UUID/randomUUID)] (reify Spec (conform* [_ m] (if (keys-pred m) (let [reg (registry)] - (loop [ret m, [k & ks :as keys] (c/keys m)] + (loop [ret m, [[k v] & ks :as keys] m] (if keys - (if (contains? reg (keys->specs k)) - (let [v (get m k) - cv (conform (keys->specs k) v)] - (if (= cv ::invalid) - ::invalid - (recur (if (identical? cv v) ret (assoc ret k cv)) - ks))) - (recur ret ks)) + (let [sname (keys->specnames k)] + (if-let [s (get reg sname)] + (let [cv (conform s v)] + (if (invalid? cv) + ::invalid + (recur (if (identical? cv v) ret (assoc ret k cv)) + ks))) + (recur ret ks))) ret))) ::invalid)) (unform* [_ m] (let [reg (registry)] (loop [ret m, [k & ks :as keys] (c/keys m)] (if keys - (if (contains? reg (keys->specs k)) + (if (contains? reg (keys->specnames k)) (let [cv (get m k) - v (unform (keys->specs k) cv)] + v (unform (keys->specnames k) cv)] (recur (if (identical? cv v) ret (assoc ret k v)) ks)) (recur ret ks)) @@ -780,9 +800,9 @@ #(identity {:path path :pred % :val x :via via :in in}) probs)) (map (fn [[k v]] - (when-not (c/or (not (contains? reg (keys->specs k))) - (valid? (keys->specs k) v k)) - (explain-1 (keys->specs k) (keys->specs k) (conj path k) via (conj in k) v))) + (when-not (c/or (not (contains? reg (keys->specnames k))) + (pvalid? (keys->specnames k) v k)) + (explain-1 (keys->specnames k) (keys->specnames k) (conj path k) via (conj in k) v))) (seq x)))))) (gen* [_ overrides path rmap] (if gfn @@ -825,16 +845,17 @@ :else (reify Spec - (conform* [_ x] (if cpred? - (pred x) - (if (pred x) x ::invalid))) + (conform* [_ x] (let [ret (pred x)] + (if cpred? + ret + (if ret x ::invalid)))) (unform* [_ x] (if cpred? (if unc (unc x) (throw (IllegalStateException. "no unform fn for conformer"))) x)) (explain* [_ path via in x] - (when (= ::invalid (dt pred x form cpred?)) + (when (invalid? (dt pred x form cpred?)) [{:path path :pred (abbrev form) :val x :via via :in in}])) (gen* [_ _ _ _] (if gfn (gfn) @@ -881,7 +902,7 @@ #(tag % k) (gensub p overrides (conj path k) rmap (list 'method form k)))))))) gs (->> (methods @mmvar) - (remove (fn [[k]] (= k ::invalid))) + (remove (fn [[k]] (invalid? k))) (map gen) (remove nil?))] (when (every? identity gs) @@ -893,56 +914,59 @@ "Do not call this directly, use 'tuple'" ([forms preds] (tuple-impl forms preds nil)) ([forms preds gfn] - (reify - Spec - (conform* [_ x] - (if-not (c/and (vector? x) - (= (count x) (count preds))) - ::invalid - (loop [ret x, i 0] - (if (= i (count x)) - ret - (let [v (x i) - cv (dt (preds i) v (forms i))] - (if (= ::invalid cv) - ::invalid - (recur (if (identical? cv v) ret (assoc ret i cv)) - (inc i)))))))) - (unform* [_ x] - (c/assert (c/and (vector? x) - (= (count x) (count preds)))) - (loop [ret x, i 0] - (if (= i (count x)) - ret - (let [cv (x i) - v (unform (preds i) cv)] - (recur (if (identical? cv v) ret (assoc ret i v)) - (inc i)))))) - (explain* [_ path via in x] - (cond - (not (vector? x)) - [{:path path :pred 'vector? :val x :via via :in in}] - - (not= (count x) (count preds)) - [{:path path :pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}] - - :else - (apply concat - (map (fn [i form pred] - (let [v (x i)] - (when-not (valid? pred v) - (explain-1 form pred (conj path i) via (conj in i) v)))) - (range (count preds)) forms preds)))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (let [gen (fn [i p f] - (gensub p overrides (conj path i) rmap f)) - gs (map gen (range (count preds)) preds forms)] - (when (every? identity gs) - (apply gen/tuple gs))))) - (with-gen* [_ gfn] (tuple-impl forms preds gfn)) - (describe* [_] `(tuple ~@forms))))) + (let [specs (delay (mapv specize* preds forms)) + cnt (count preds)] + (reify + Spec + (conform* [_ x] + (let [specs @specs] + (if-not (c/and (vector? x) + (= (count x) cnt)) + ::invalid + (loop [ret x, i 0] + (if (= i cnt) + ret + (let [v (x i) + cv (conform* (specs i) v)] + (if (invalid? cv) + ::invalid + (recur (if (identical? cv v) ret (assoc ret i cv)) + (inc i))))))))) + (unform* [_ x] + (c/assert (c/and (vector? x) + (= (count x) (count preds)))) + (loop [ret x, i 0] + (if (= i (count x)) + ret + (let [cv (x i) + v (unform (preds i) cv)] + (recur (if (identical? cv v) ret (assoc ret i v)) + (inc i)))))) + (explain* [_ path via in x] + (cond + (not (vector? x)) + [{:path path :pred 'vector? :val x :via via :in in}] + + (not= (count x) (count preds)) + [{:path path :pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}] + + :else + (apply concat + (map (fn [i form pred] + (let [v (x i)] + (when-not (pvalid? pred v) + (explain-1 form pred (conj path i) via (conj in i) v)))) + (range (count preds)) forms preds)))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [i p f] + (gensub p overrides (conj path i) rmap f)) + gs (map gen (range (count preds)) preds forms)] + (when (every? identity gs) + (apply gen/tuple gs))))) + (with-gen* [_ gfn] (tuple-impl forms preds gfn)) + (describe* [_] `(tuple ~@forms)))))) (defn- tagged-ret [tag ret] (clojure.lang.MapEntry. tag ret)) @@ -952,25 +976,25 @@ [keys forms preds gfn] (let [id (java.util.UUID/randomUUID) kps (zipmap keys preds) - specs (delay (mapv specize preds)) + specs (delay (mapv specize preds forms)) cform (case (count preds) 2 (fn [x] (let [specs @specs ret (conform* (specs 0) x)] - (if (= ::invalid ret) + (if (invalid? ret) (let [ret (conform* (specs 1) x)] - (if (= ::invalid ret) + (if (invalid? ret) ::invalid (tagged-ret (keys 1) ret))) (tagged-ret (keys 0) ret)))) 3 (fn [x] (let [specs @specs ret (conform* (specs 0) x)] - (if (= ::invalid ret) + (if (invalid? ret) (let [ret (conform* (specs 1) x)] - (if (= ::invalid ret) + (if (invalid? ret) (let [ret (conform* (specs 2) x)] - (if (= ::invalid ret) + (if (invalid? ret) ::invalid (tagged-ret (keys 2) ret))) (tagged-ret (keys 1) ret))) @@ -981,7 +1005,7 @@ (if (< i (count specs)) (let [spec (specs i)] (let [ret (conform* spec x)] - (if (= ::invalid ret) + (if (invalid? ret) (recur (inc i)) (tagged-ret (keys i) ret)))) ::invalid)))))] @@ -990,10 +1014,10 @@ (conform* [_ x] (cform x)) (unform* [_ [k x]] (unform (kps k) x)) (explain* [this path via in x] - (when-not (valid? this x) + (when-not (pvalid? this x) (apply concat (map (fn [k form pred] - (when-not (valid? pred x) + (when-not (pvalid? pred x) (explain-1 form pred (conj path k) via in x))) keys forms preds)))) (gen* [_ overrides path rmap] @@ -1016,7 +1040,7 @@ [form & forms] forms] (if pred (let [nret (dt pred ret form)] - (if (= ::invalid nret) + (if (invalid? nret) ::invalid ;;propagate conformed values (recur nret preds forms))) @@ -1029,29 +1053,29 @@ [pred & preds] preds] (when pred (let [nret (dt pred ret form)] - (if (not= ::invalid nret) - (recur nret forms preds) - (explain-1 form pred path via in ret)))))) + (if (invalid? nret) + (explain-1 form pred path via in ret) + (recur nret forms preds)))))) (defn ^:skip-wiki and-spec-impl "Do not call this directly, use 'and'" [forms preds gfn] - (let [specs (delay (mapv specize preds)) + (let [specs (delay (mapv specize preds forms)) cform (case (count preds) 2 (fn [x] (let [specs @specs ret (conform* (specs 0) x)] - (if (= ::invalid ret) + (if (invalid? ret) ::invalid (conform* (specs 1) ret)))) 3 (fn [x] (let [specs @specs ret (conform* (specs 0) x)] - (if (= ::invalid ret) + (if (invalid? ret) ::invalid (let [ret (conform* (specs 1) ret)] - (if (= ::invalid ret) + (if (invalid? ret) ::invalid (conform* (specs 2) ret)))))) (fn [x] @@ -1059,7 +1083,7 @@ (loop [ret x i 0] (if (< i (count specs)) (let [nret (conform* (specs i) ret)] - (if (= ::invalid nret) + (if (invalid? nret) ::invalid ;;propagate conformed values (recur nret (inc i)))) @@ -1079,7 +1103,7 @@ (reify Spec (conform* [_ x] (let [ms (map #(dt %1 x %2) preds forms)] - (if (some #{::invalid} ms) + (if (some invalid? ms) ::invalid (apply c/merge ms)))) (unform* [_ x] (apply c/merge (map #(unform % x) (reverse preds)))) @@ -1102,7 +1126,7 @@ (let [pred (c/or kfn coll?) kform (c/or kform `coll?)] (cond - (not (valid? pred x)) + (not (pvalid? pred x)) (explain-1 kform pred path via in x) (c/and distinct (not (empty? x)) (not (apply distinct? x))) @@ -1127,7 +1151,7 @@ :as opts} gfn] (let [conform-into gen-into - check? #(valid? pred %) + check? #(pvalid? pred %) kfn (c/or kfn (fn [i v] i)) addcv (fn [ret i v cv] (conj ret cv)) cfns (fn [x] @@ -1166,7 +1190,7 @@ (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] (if vseq (let [cv (dt pred v nil)] - (if (= ::invalid cv) + (if (invalid? cv) ::invalid (recur (add ret i v cv) (inc i) vs))) (complete ret)))) @@ -1326,7 +1350,7 @@ ::amp (c/and (accept-nil? p1) (c/or (noret? p1 (preturn p1)) (let [ret (-> (preturn p1) (and-preds ps (next forms)))] - (not= ret ::invalid)))) + (not (invalid? ret))))) ::rep (c/or (identical? p1 p2) (accept-nil? p1)) ::pcat (every? accept-nil? ps) ::alt (c/some accept-nil? ps)))) @@ -1389,11 +1413,11 @@ (case op ::accept nil nil (let [ret (dt p x p)] - (when-not (= ::invalid ret) (accept ret))) + (when-not (invalid? ret) (accept ret))) ::amp (when-let [p1 (deriv p1 x)] (if (= ::accept (::op p1)) (let [ret (-> (preturn p1) (and-preds ps (next forms)))] - (when-not (= ret ::invalid) + (when-not (invalid? ret) (accept ret))) (amp-impl p1 ps forms))) ::pcat (alt2 (pcat* {:ps (cons (deriv p0 x) pr), :ks ks, :forms forms, :ret ret}) @@ -1579,12 +1603,12 @@ (defn- call-valid? [f specs args] (let [cargs (conform (:args specs) args)] - (when-not (= cargs ::invalid) + (when-not (invalid? cargs) (let [ret (apply f args) cret (conform (:ret specs) ret)] - (c/and (not= cret ::invalid) + (c/and (not (invalid? cret)) (if (:fn specs) - (valid? (:fn specs) {:args cargs :ret cret}) + (pvalid? (:fn specs) {:args cargs :ret cret}) true)))))) (defn- validate-fn @@ -1622,7 +1646,7 @@ [{:path path :pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via :in in}] (let [cret (dt retspec ret rform)] - (if (= ::invalid cret) + (if (invalid? cret) (explain-1 rform retspec (conj path :ret) via in ret) (when fnspec (let [cargs (conform argspec args)] @@ -1632,7 +1656,7 @@ (gfn) (gen/return (fn [& args] - (c/assert (valid? argspec args) (with-out-str (explain argspec args))) + (c/assert (pvalid? argspec args) (with-out-str (explain argspec args))) (gen/generate (gen retspec overrides)))))) (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) @@ -1667,7 +1691,7 @@ (reify Spec (conform* [_ x] (let [ret (conform* spec x)] - (if (= ::invalid ret) + (if (invalid? ret) ::invalid x))) (unform* [_ x] (unform* spec x)) From 9fa85c6a908c9a3e89b4c0c449c49887a4c35248 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 7 Sep 2016 15:19:19 -0400 Subject: [PATCH 290/854] perf tweaks --- src/clj/clojure/spec.clj | 142 +++++++++++++++++++++++++++------------ 1 file changed, 99 insertions(+), 43 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 53b7063b7c..b6ff66a481 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -72,7 +72,8 @@ (defn spec? "returns x if x is a spec object, else logical false" [x] - (c/and (extends? Spec (class x)) x)) + (when (instance? clojure.spec.Spec x) + x)) (defn regex? "returns x if x is a (clojure.spec) regex op, else logical false" @@ -129,17 +130,13 @@ (specize* ([s] (specize* (reg-resolve! s))) ([s _] (specize* (reg-resolve! s)))) - clojure.spec.Spec - (specize* ([s] s) - ([s _] s)) - Object (specize* ([o] (spec-impl ::unknown o nil nil)) ([o form] (spec-impl form o nil nil)))) (defn- specize - ([s] (specize* s)) - ([s form] (specize* s form))) + ([s] (c/or (spec? s) (specize* s))) + ([s form] (c/or (spec? s) (specize* s form)))) (defn invalid? "tests the validity of a conform return value" @@ -525,8 +522,19 @@ See also - coll-of, every-kv " [pred & {:keys [into kind count max-count min-count distinct gen-max gen] :as opts}] - (let [nopts (-> opts (dissoc :gen) (assoc ::kind-form `'~(res (:kind opts))))] - `(every-impl '~pred ~pred ~nopts ~gen))) + (let [nopts (-> opts (dissoc :gen) (assoc ::kind-form `'~(res (:kind opts)))) + gx (gensym) + cpreds (cond-> [(list (c/or kind `coll?) gx)] + count (conj `(= ~count (bounded-count ~count ~gx))) + + (c/or min-count max-count) + (conj `(<= (c/or ~min-count 0) + (bounded-count (if ~max-count (inc ~max-count) ~min-count) ~gx) + (c/or ~max-count Integer/MAX_VALUE))) + + distinct + (conj `(c/or (empty? ~gx) (apply distinct? ~gx))))] + `(every-impl '~pred ~pred ~(assoc nopts ::cpred `(fn* [~gx] (c/and ~@cpreds))) ~gen))) (defmacro every-kv "like 'every' but takes separate key and val preds and works on associative collections. @@ -760,6 +768,10 @@ keys->specnames #(c/or (k->s %) %) id (java.util.UUID/randomUUID)] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ m] (if (keys-pred m) @@ -844,6 +856,10 @@ (ident? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) :else (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (let [ret (pred x)] (if cpred? @@ -877,6 +893,10 @@ #(assoc %1 retag %2) retag)] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (if-let [pred (predx x)] (dt pred x form) @@ -914,9 +934,13 @@ "Do not call this directly, use 'tuple'" ([forms preds] (tuple-impl forms preds nil)) ([forms preds gfn] - (let [specs (delay (mapv specize* preds forms)) + (let [specs (delay (mapv specize preds forms)) cnt (count preds)] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (let [specs @specs] @@ -1010,6 +1034,10 @@ (tagged-ret (keys i) ret)))) ::invalid)))))] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (cform x)) (unform* [_ [k x]] (unform (kps k) x)) @@ -1089,6 +1117,10 @@ (recur nret (inc i)))) ret)))))] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (cform x)) (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) @@ -1101,6 +1133,10 @@ "Do not call this directly, use 'merge'" [forms preds gfn] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (let [ms (map #(dt %1 x %2) preds forms)] (if (some invalid? ms) @@ -1129,9 +1165,6 @@ (not (pvalid? pred x)) (explain-1 kform pred path via in x) - (c/and distinct (not (empty? x)) (not (apply distinct? x))) - [{:path path :pred 'distinct? :val x :via via :in in}] - (c/and count (not= count (bounded-count count x))) [{:path path :pred `(= ~count (c/count ~'%)) :val x :via via :in in}] @@ -1139,19 +1172,23 @@ (not (<= (c/or min-count 0) (bounded-count (if max-count (inc max-count) min-count) x) (c/or max-count Integer/MAX_VALUE)))) - [{:path path :pred `(<= ~(c/or min-count 0) (c/count ~'%) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}]))) + [{:path path :pred `(<= ~(c/or min-count 0) (c/count ~'%) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}] + + (c/and distinct (not (empty? x)) (not (apply distinct? x))) + [{:path path :pred 'distinct? :val x :via via :in in}]))) (defn ^:skip-wiki every-impl "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" ([form pred opts] (every-impl form pred opts nil)) ([form pred {gen-into :into - :keys [kind ::kind-form count max-count min-count distinct gen-max ::kfn + :keys [kind ::kind-form count max-count min-count distinct gen-max ::kfn ::cpred conform-keys ::conform-all] :or {gen-max 20} :as opts} gfn] (let [conform-into gen-into - check? #(pvalid? pred %) + spec (delay (specize pred)) + check? #(valid? @spec %) kfn (c/or kfn (fn [i v] i)) addcv (fn [ret i v cv] (conj ret cv)) cfns (fn [x] @@ -1178,35 +1215,42 @@ :else [#(empty (c/or conform-into %)) addcv identity]))] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] - (cond - (coll-prob x kind kind-form distinct count min-count max-count - nil nil nil) - ::invalid - - conform-all - (let [[init add complete] (cfns x)] - (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] - (if vseq - (let [cv (dt pred v nil)] - (if (invalid? cv) - ::invalid - (recur (add ret i v cv) (inc i) vs))) - (complete ret)))) - - - :else - (if (indexed? x) - (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] - (loop [i 0] - (if (>= i (c/count x)) - x - (if (check? (nth x i)) - (recur (c/+ i step)) - ::invalid)))) - (c/or (c/and (every? check? (take *coll-check-limit* x)) x) - ::invalid)))) + (let [spec @spec] + (cond + (not (cpred x)) ::invalid + + conform-all + (let [[init add complete] (cfns x)] + (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] + (if vseq + (let [cv (conform* spec v)] + (if (invalid? cv) + ::invalid + (recur (add ret i v cv) (inc i) vs))) + (complete ret)))) + + + :else + (if (indexed? x) + (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] + (loop [i 0] + (if (>= i (c/count x)) + x + (if (valid? spec (nth x i)) + (recur (c/+ i step)) + ::invalid)))) + (let [limit *coll-check-limit*] + (loop [i 0 [v & vs :as vseq] (seq x)] + (cond + (c/or (nil? vseq) (= i limit)) x + (valid? spec v) (recur (inc i) vs) + :else ::invalid))))))) (unform* [_ x] x) (explain* [_ path via in x] (c/or (coll-prob x kind kind-form distinct count min-count max-count @@ -1581,6 +1625,10 @@ "Do not call this directly, use 'spec' with a regex op argument" [re gfn] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (if (c/or (nil? x) (coll? x)) @@ -1630,6 +1678,10 @@ (valAt [this k] (get specs k)) (valAt [_ k not-found] (get specs k not-found)) + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ f] (if (ifn? f) (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) @@ -1689,6 +1741,10 @@ [spec] (let [spec (specize spec)] (reify + Specize + (specize* [s] s) + (specize* [s _] s) + Spec (conform* [_ x] (let [ret (conform* spec x)] (if (invalid? ret) From aaa982a89acfbb48bf052149d76f966f71617620 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 26 Aug 2016 12:41:40 -0500 Subject: [PATCH 291/854] CLJ-2012 Fix bad spec on gen-class signatures to allow class names as strings Signed-off-by: Stuart Halloway --- src/clj/clojure/core/specs.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index c66c6066d0..d8adc46401 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -158,7 +158,8 @@ (s/def ::extends simple-symbol?) (s/def ::implements (s/coll-of simple-symbol? :kind vector?)) (s/def ::init symbol?) -(s/def ::signature (s/coll-of simple-symbol? :kind vector?)) +(s/def ::class-ident (s/or :class simple-symbol? :class-name string?)) +(s/def ::signature (s/coll-of ::class-ident :kind vector?)) (s/def ::constructors (s/map-of ::signature ::signature)) (s/def ::post-init symbol?) (s/def ::method (s/and vector? From 6d928d0ba6f839211a5e3178aba9664cd4fab05e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 23 Aug 2016 12:33:43 -0500 Subject: [PATCH 292/854] CLJ-2008 omit macros from checkable-syms Signed-off-by: Stuart Halloway --- src/clj/clojure/spec/test.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index a7d32a6f8c..73d34a30f0 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -193,7 +193,8 @@ failure in instrument." (defn- fn-spec-name? [s] - (symbol? s)) + (and (symbol? s) + (not (some-> (resolve s) meta :macro)))) (defn instrumentable-syms "Given an opts map as per instrument, returns the set of syms From 7cc165f5370aa3fb71fa5834e1886ba9552a3dbe Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 23 Aug 2016 12:39:11 -0500 Subject: [PATCH 293/854] CLJ-2006 Fix old function name in docstring Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index b6ff66a481..957385a587 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -697,7 +697,7 @@ by calling get-spec with the var or fully-qualified symbol. Once registered, function specs are included in doc, checked by - instrument, tested by the runner clojure.spec.test/run-tests, and (if + instrument, tested by the runner clojure.spec.test/check, and (if a macro) used to explain errors during macroexpansion. Note that :fn specs require the presence of :args and :ret specs to From 5e83c2ab898fefe655ee45495d56d69a6bd10304 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 30 Aug 2016 10:36:57 -0500 Subject: [PATCH 294/854] CLJ-2004 include retag in multi-spec form Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 957385a587..def7d3f034 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -928,7 +928,7 @@ (when (every? identity gs) (gen/one-of gs))))) (with-gen* [_ gfn] (multi-spec-impl form mmvar retag gfn)) - (describe* [_] `(multi-spec ~form)))))) + (describe* [_] `(multi-spec ~form ~retag)))))) (defn ^:skip-wiki tuple-impl "Do not call this directly, use 'tuple'" From edf869a0fa56df3aa2503980af65931d76e2e00b Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 23 Aug 2016 16:43:34 -0500 Subject: [PATCH 295/854] CLJ-1988 Extend coll-of to handle sequences Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 4 ++-- test/clojure/test_clojure/spec.clj | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index def7d3f034..23e7e6e1b9 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1209,8 +1209,8 @@ ret (assoc ret (nth (if conform-keys cv v) 0) (nth cv 1)))) identity] - - (c/or (list? conform-into) (c/and (not conform-into) (list? x))) + + (c/or (list? conform-into) (seq? conform-into) (c/and (not conform-into) (c/or (list? x) (seq? x)))) [(constantly ()) addcv reverse] :else [#(empty (c/or conform-into %)) addcv identity]))] diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index c388693bad..481e940ec7 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -143,6 +143,7 @@ coll [] [] nil coll [:a] [:a] nil coll [:a :b] [:a :b] nil + coll (map identity [:a :b]) '(:a :b) nil ;;coll [:a "b"] ::s/invalid '[{:pred (coll-checker keyword?), :val [:a b]}] ))) From 131865556fe0350ad7103efc142779b3fca43319 Mon Sep 17 00:00:00 2001 From: Jason Whitlark Date: Tue, 13 Oct 2015 10:54:44 -0700 Subject: [PATCH 296/854] CLJ-1673 Improve clojure.repl/dir-fn. dir-fn will now work on namespace aliases in addition to canonical namespaces. Add test for same, compatible with direct linking, introduced in Clojure 1.8.0-alpha3. Signed-off-by: Stuart Halloway --- src/clj/clojure/repl.clj | 4 ++-- test/clojure/test_clojure/repl.clj | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index e77b78845d..2dec1f3f02 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -195,9 +195,9 @@ str-or-pattern." (defn dir-fn "Returns a sorted seq of symbols naming public vars in - a namespace" + a namespace or namespace alias. Looks for aliases in *ns*" [ns] - (sort (map first (ns-publics (the-ns ns))))) + (sort (map first (ns-publics (the-ns (get (ns-aliases *ns*) ns ns)))))) (defmacro dir "Prints a sorted directory of public vars in a namespace" diff --git a/test/clojure/test_clojure/repl.clj b/test/clojure/test_clojure/repl.clj index 609056b56c..17bd084262 100644 --- a/test/clojure/test_clojure/repl.clj +++ b/test/clojure/test_clojure/repl.clj @@ -24,6 +24,8 @@ (deftest test-dir (is (thrown? Exception (dir-fn 'non-existent-ns))) (is (= '[bar foo] (dir-fn 'clojure.test-clojure.repl.example))) + (binding [*ns* (the-ns 'clojure.test-clojure.repl)] + (is (= (dir-fn 'clojure.string) (dir-fn 'str)))) (is (= (platform-newlines "bar\nfoo\n") (with-out-str (dir clojure.test-clojure.repl.example))))) (deftest test-apropos From a1c3dafec01ab02fb10d91f98b9ffd3241e860c0 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Tue, 6 Sep 2016 14:08:40 -0500 Subject: [PATCH 297/854] CLJ-1224: cache hasheq and hashCode for records Signed-off-by: Stuart Halloway --- src/clj/clojure/core_deftype.clj | 43 +++++++---- src/jvm/clojure/lang/Compiler.java | 73 +++++++++++++++++-- test/clojure/test_clojure/data_structures.clj | 15 +++- 3 files changed, 109 insertions(+), 22 deletions(-) diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index 723ed24c41..8795ee5d12 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -156,7 +156,9 @@ hinted-fields fields fields (vec (map #(with-meta % nil) fields)) base-fields fields - fields (conj fields '__meta '__extmap) + fields (conj fields '__meta '__extmap + '^:unsynchronized-mutable __hash + '^:unsynchronized-mutable __hasheq) type-hash (hash classname)] (when (some #{:volatile-mutable :unsynchronized-mutable} (mapcat (comp keys meta) hinted-fields)) (throw (IllegalArgumentException. ":volatile-mutable or :unsynchronized-mutable not supported for record fields"))) @@ -168,8 +170,18 @@ (eqhash [[i m]] [(conj i 'clojure.lang.IHashEq) (conj m - `(hasheq [this#] (bit-xor ~type-hash (clojure.lang.APersistentMap/mapHasheq this#))) - `(hashCode [this#] (clojure.lang.APersistentMap/mapHash this#)) + `(hasheq [this#] (let [hq# ~'__hasheq] + (if (zero? hq#) + (let [h# (int (bit-xor ~type-hash (clojure.lang.APersistentMap/mapHasheq this#)))] + (set! ~'__hasheq h#) + h#) + hq#))) + `(hashCode [this#] (let [hash# ~'__hash] + (if (zero? hash#) + (let [h# (clojure.lang.APersistentMap/mapHash this#)] + (set! ~'__hash h#) + h#) + hash#))) `(equals [this# ~gs] (clojure.lang.APersistentMap/mapEquals this# ~gs)))]) (iobj [[i m]] [(conj i 'clojure.lang.IObj) @@ -220,12 +232,12 @@ `(assoc [this# k# ~gs] (condp identical? k# ~@(mapcat (fn [fld] - [(keyword fld) (list* `new tagname (replace {fld gs} fields))]) + [(keyword fld) (list* `new tagname (replace {fld gs} (remove '#{__hash __hasheq} fields)))]) base-fields) - (new ~tagname ~@(remove #{'__extmap} fields) (assoc ~'__extmap k# ~gs)))) + (new ~tagname ~@(remove '#{__extmap __hash __hasheq} fields) (assoc ~'__extmap k# ~gs)))) `(without [this# k#] (if (contains? #{~@(map keyword base-fields)} k#) (dissoc (with-meta (into {} this#) ~'__meta) k#) - (new ~tagname ~@(remove #{'__extmap} fields) + (new ~tagname ~@(remove '#{__extmap __hash __hasheq} fields) (not-empty (dissoc ~'__extmap k#))))))]) (ijavamap [[i m]] [(conj i 'java.util.Map 'java.io.Serializable) @@ -243,8 +255,11 @@ `(entrySet [this#] (set this#)))]) ] (let [[i m] (-> [interfaces methods] irecord eqhash iobj ilookup imap ijavamap)] - `(deftype* ~(symbol (name (ns-name *ns*)) (name tagname)) ~classname ~(conj hinted-fields '__meta '__extmap) - :implements ~(vec i) + `(deftype* ~(symbol (name (ns-name *ns*)) (name tagname)) ~classname + ~(conj hinted-fields '__meta '__extmap + '^int ^:unsynchronized-mutable __hash + '^int ^:unsynchronized-mutable __hasheq) + :implements ~(vec i) ~@(mapcat identity opts) ~@m)))))) @@ -280,7 +295,7 @@ [fields name] (when-not (vector? fields) (throw (AssertionError. "No fields vector given."))) - (let [specials #{'__meta '__extmap}] + (let [specials '#{__meta __hash __hasheq __extmap}] (when (some specials fields) (throw (AssertionError. (str "The names in " specials " cannot be used as field names for types or records."))))) (let [non-syms (remove symbol? fields)] @@ -357,9 +372,9 @@ Two constructors will be defined, one taking the designated fields followed by a metadata map (nil for none) and an extension field map (nil for none), and one taking only the fields (using nil for - meta and extension fields). Note that the field names __meta - and __extmap are currently reserved and should not be used when - defining your own records. + meta and extension fields). Note that the field names __meta, + __extmap, __hash and __hasheq are currently reserved and should not + be used when defining your own records. Given (defrecord TypeName ...), two factory functions will be defined: ->TypeName, taking positional parameters for the fields, @@ -465,8 +480,8 @@ writes the .class file to the *compile-path* directory. One constructor will be defined, taking the designated fields. Note - that the field names __meta and __extmap are currently reserved and - should not be used when defining your own types. + that the field names __meta, __extmap, __hash and __hasheq are currently + reserved and should not be used when defining your own types. Given (deftype TypeName ...), a factory function called ->TypeName will be defined, taking positional parameters for the fields" diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 8bc97c77d8..7a17d0901d 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -4414,8 +4414,34 @@ void compile(String superName, String[] interfaceNames, boolean oneTimeUse) thro ctorgen.visitCode(); ctorgen.loadThis(); ctorgen.loadArgs(); - for(int i=0;i", Type.VOID_TYPE, ctorTypes)); + + ctorgen.returnValue(); + ctorgen.endMethod(); + + // alt ctor no __hash, __hasheq + altCtorTypes = new Type[ctorTypes.length-2]; + for(int i=0;i", Type.VOID_TYPE, altCtorTypes); + ctorgen = new GeneratorAdapter(ACC_PUBLIC, + alt, + null, + null, + cv); + ctorgen.visitCode(); + ctorgen.loadThis(); + ctorgen.loadArgs(); + + ctorgen.visitInsn(Opcodes.ICONST_0); //__hash + ctorgen.visitInsn(Opcodes.ICONST_0); //__hasheq ctorgen.invokeConstructor(objtype, new Method("", Type.VOID_TYPE, ctorTypes)); @@ -7766,7 +7792,11 @@ static ObjExpr build(IPersistentVector interfaceSyms, IPersistentVector fieldSym //use array map to preserve ctor order ret.closes = new PersistentArrayMap(closesvec); ret.fields = fmap; - for(int i=fieldSyms.count()-1;i >= 0 && (((Symbol)fieldSyms.nth(i)).name.equals("__meta") || ((Symbol)fieldSyms.nth(i)).name.equals("__extmap"));--i) + for(int i=fieldSyms.count()-1;i >= 0 && (((Symbol)fieldSyms.nth(i)).name.equals("__meta") + || ((Symbol)fieldSyms.nth(i)).name.equals("__extmap") + || ((Symbol)fieldSyms.nth(i)).name.equals("__hash") + || ((Symbol)fieldSyms.nth(i)).name.equals("__hasheq") + );--i) ret.altCtorDrops++; } //todo - set up volatiles @@ -7910,8 +7940,35 @@ static Class compileStub(String superName, NewInstanceExpr ret, String[] interfa ctorgen.visitCode(); ctorgen.loadThis(); ctorgen.loadArgs(); - for(int i=0;i", Type.VOID_TYPE, ctorTypes)); + + ctorgen.returnValue(); + ctorgen.endMethod(); + + // alt ctor no __hash, __hasheq + altCtorTypes = new Type[ctorTypes.length-2]; + for(int i=0;i", Type.VOID_TYPE, altCtorTypes); + ctorgen = new GeneratorAdapter(ACC_PUBLIC, + alt, + null, + null, + cv); + ctorgen.visitCode(); + ctorgen.loadThis(); + ctorgen.loadArgs(); + + ctorgen.visitInsn(Opcodes.ICONST_0); //__hash + ctorgen.visitInsn(Opcodes.ICONST_0); //__hasheq ctorgen.invokeConstructor(Type.getObjectType(COMPILE_STUB_PREFIX + "/" + ret.internalName), new Method("", Type.VOID_TYPE, ctorTypes)); @@ -8006,9 +8063,11 @@ protected void emitStatics(ClassVisitor cv) { } } - mv.visitInsn(ACONST_NULL); - mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(ACONST_NULL); //__meta + mv.visitVarInsn(ALOAD, 0); //__extmap mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/RT", "seqOrElse", "(Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitInsn(ICONST_0); //__hash + mv.visitInsn(ICONST_0); //__hasheq mv.visitMethodInsn(INVOKESPECIAL, className, "", ctor.getDescriptor()); mv.visitInsn(ARETURN); mv.visitMaxs(4+fieldCount, 1+fieldCount); diff --git a/test/clojure/test_clojure/data_structures.clj b/test/clojure/test_clojure/data_structures.clj index 9151ceb9db..35cf501f3c 100644 --- a/test/clojure/test_clojure/data_structures.clj +++ b/test/clojure/test_clojure/data_structures.clj @@ -1287,4 +1287,17 @@ (defspec seq-and-iter-match-for-structs identity [^{:tag clojure.test-clojure.data-structures/gen-struct} s] - (seq-iter-match s s)) \ No newline at end of file + (seq-iter-match s s)) + +(deftest record-hashing + (let [r (->Rec 1 1) + _ (hash r) + r2 (assoc r :c 2)] + (is (= (hash (->Rec 1 1)) (hash r))) + (is (= (hash r) (hash (with-meta r {:foo 2})))) + (is (not= (hash (->Rec 1 1)) (hash (assoc (->Rec 1 1) :a 2)))) + (is (not= (hash (->Rec 1 1)) (hash r2))) + (is (not= (hash (->Rec 1 1)) (hash (assoc r :a 2)))) + (is (= (hash (->Rec 1 1)) (hash (assoc r :a 1)))) + (is (= (hash (->Rec 1 1)) (hash (dissoc r2 :c)))) + (is (= (hash (->Rec 1 1)) (hash (dissoc (assoc r :c 1) :c)))))) From 522ba8b82ba6eb6c50284a211e7533db51363b8f Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 30 Aug 2016 09:29:49 -0500 Subject: [PATCH 298/854] CLJ-1935 Use multimethod dispatch value method lookup to take hierarchies into account in multi-spec Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 23e7e6e1b9..c1f361686a 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -885,8 +885,7 @@ ([form mmvar retag gfn] (let [id (java.util.UUID/randomUUID) predx #(let [^clojure.lang.MultiFn mm @mmvar] - (c/and (contains? (methods mm) - ((.dispatchFn mm) %)) + (c/and (.getMethod mm ((.dispatchFn mm) %)) (mm %))) dval #((.dispatchFn ^clojure.lang.MultiFn @mmvar) %) tag (if (keyword? retag) From e6c3b3f7e2f5d6be627720c6d2e3cac0368f0e9b Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 7 Sep 2016 15:48:34 -0500 Subject: [PATCH 299/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..4f7f151e49 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha12 http://clojure.org/ Clojure core environment and runtime library. From f572a60262852af68cdb561784a517143a5847cf Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 7 Sep 2016 15:48:34 -0500 Subject: [PATCH 300/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f7f151e49..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha12 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From 7ff4c70b13d29cf0a1fc7f19ba53413a38bb03d5 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 22 Sep 2016 13:18:35 -0500 Subject: [PATCH 301/854] Fix nilable conformance Signed-off-by: Rich Hickey --- src/clj/clojure/spec.clj | 45 ++++++++++++++++++++++++------ src/clj/clojure/spec/gen.clj | 2 +- test/clojure/test_clojure/spec.clj | 22 +++++++++++++++ 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index c1f361686a..f571e3dae0 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1734,31 +1734,58 @@ (with-gen (clojure.spec/& (* (cat ::k keyword? ::v any?)) ::kvs->map mspec#) (fn [] (gen/fmap (fn [m#] (apply concat m#)) (gen mspec#)))))) -(defn nonconforming +(defn ^:skip-wiki nonconforming "takes a spec and returns a spec that has the same properties except 'conform' returns the original (not the conformed) value. Note, will specize regex ops." [spec] - (let [spec (specize spec)] + (let [spec (delay (specize spec))] (reify Specize (specize* [s] s) (specize* [s _] s) Spec - (conform* [_ x] (let [ret (conform* spec x)] + (conform* [_ x] (let [ret (conform* @spec x)] (if (invalid? ret) ::invalid x))) - (unform* [_ x] (unform* spec x)) - (explain* [_ path via in x] (explain* spec path via in x)) - (gen* [_ overrides path rmap] (gen* spec overrides path rmap)) - (with-gen* [_ gfn] (nonconforming (with-gen* spec gfn))) - (describe* [_] `(nonconforming ~(describe* spec)))))) + (unform* [_ x] x) + (explain* [_ path via in x] (explain* @spec path via in x)) + (gen* [_ overrides path rmap] (gen* @spec overrides path rmap)) + (with-gen* [_ gfn] (nonconforming (with-gen* @spec gfn))) + (describe* [_] `(nonconforming ~(describe* @spec)))))) + +(defn ^:skip-wiki nilable-impl + "Do not call this directly, use 'nilable'" + [form pred gfn] + (let [spec (delay (specize pred form))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (if (nil? x) nil (conform* @spec x))) + (unform* [_ x] (if (nil? x) nil (unform* @spec x))) + (explain* [_ path via in x] + (when-not (c/or (pvalid? @spec x) (nil? x)) + (conj + (explain-1 form pred (conj path ::pred) via in x) + {:path (conj path ::nil) :pred 'nil? :val x :via via :in in}))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (gen/frequency + [[1 (gen/delay (gen/return nil))] + [9 (gen/delay (gensub pred overrides (conj path ::pred) rmap form))]]))) + (with-gen* [_ gfn] (nilable-impl form pred gfn)) + (describe* [_] `(nilable ~(describe* @spec)))))) (defmacro nilable "returns a spec that accepts nil and values satisfying pred" [pred] - `(nonconforming (or ::nil nil? ::pred ~pred))) + (let [pf (res pred)] + `(nilable-impl '~pf ~pred nil))) (defn exercise "generates a number (default 10) of values compatible with spec and maps conform over them, diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj index 8f1c4e3881..6d8e2388ed 100644 --- a/src/clj/clojure/spec/gen.clj +++ b/src/clj/clojure/spec/gen.clj @@ -91,7 +91,7 @@ (lazy-combinators hash-map list map not-empty set vector vector-distinct fmap elements bind choose fmap one-of such-that tuple sample return - large-integer* double*) + large-integer* double* frequency) (defmacro ^:skip-wiki lazy-prim "Implementation macro, do not call directly." diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 481e940ec7..4f08ecdd7b 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -147,6 +147,28 @@ ;;coll [:a "b"] ::s/invalid '[{:pred (coll-checker keyword?), :val [:a b]}] ))) +(defn check-conform-unform [spec vals expected-conforms] + (let [actual-conforms (map #(s/conform spec %) vals) + unforms (map #(s/unform spec %) actual-conforms)] + (is (= actual-conforms expected-conforms)) + (is (= vals unforms)))) + +(deftest nilable-conform-unform + (check-conform-unform + (s/nilable int?) + [5 nil] + [5 nil]) + (check-conform-unform + (s/nilable (s/or :i int? :s string?)) + [5 "x" nil] + [[:i 5] [:s "x"] nil])) + +(deftest nonconforming-conform-unform + (check-conform-unform + (s/nonconforming (s/or :i int? :s string?)) + [5 "x"] + [5 "x"])) + (comment (require '[clojure.test :refer (run-tests)]) (in-ns 'clojure.test-clojure.spec) From cfa0e126bad63e8f5eb6774c35fa78d19672b244 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 26 Sep 2016 13:23:36 -0500 Subject: [PATCH 302/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..7d1e3ac02b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha13 http://clojure.org/ Clojure core environment and runtime library. From 7ce2f66c6bf109783be282886a6031cf0afdbb7b Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 26 Sep 2016 13:23:36 -0500 Subject: [PATCH 303/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7d1e3ac02b..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha13 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From e547d35bb796051bb2cbf07bbca6ee67c5bc022f Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Tue, 4 Oct 2016 16:31:45 -0400 Subject: [PATCH 304/854] add arity 0/1 to into --- src/clj/clojure/core.clj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 6b8a1b5e1a..9cc6ce2117 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6765,6 +6765,8 @@ from-coll conjoined. A transducer may be supplied." {:added "1.0" :static true} + ([] []) + ([to] to) ([to from] (if (instance? clojure.lang.IEditableCollection to) (with-meta (persistent! (reduce conj! (transient to) from)) (meta to)) From 7aad2f7dbb3a66019e5cef3726d52d721e9c60df Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Wed, 12 Oct 2016 15:37:51 -0400 Subject: [PATCH 305/854] added halt-when transducer --- src/clj/clojure/core.clj | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 9cc6ce2117..72383d6b1b 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7503,6 +7503,30 @@ ([result input] (reduce rrf result input))))) +(defn halt-when + "Returns a transducer that ends transduction when pred returns true + for an input. When retf is supplied it must be a fn of 2 arguments - + it will be passed the (completed) result so far and the input that + triggered the predicate, and its return value (if it does not throw + an exception) will be the return value of the transducer. If retf + is not supplied, the input that triggered the predicate will be + returned. If the predicate never returns true the transduction is + unaffected." + {:added "1.9"} + ([pred] (halt-when pred nil)) + ([pred retf] + (fn [rf] + (fn + ([] (rf)) + ([result] + (if (and (map? result) (contains? result ::halt)) + (::halt result) + (rf result))) + ([result input] + (if (pred input) + (reduced {::halt (if retf (retf (rf result) input) input)}) + (rf result input))))))) + (defn dedupe "Returns a lazy sequence removing consecutive duplicates in coll. Returns a transducer when no collection is provided." From ee9a3bd15c48057a6fa941671c2e341784d5cd6e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 14 Oct 2016 13:21:16 -0500 Subject: [PATCH 306/854] CLJ-2042 s/form of s/? does not resolve pred Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index f571e3dae0..e9e48fbfd6 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1481,7 +1481,7 @@ (list `+ rep+) (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) forms))) ::alt (if maybe - (list `? maybe) + (list `? (res maybe)) (cons `alt (mapcat vector ks forms))) ::rep (list (if splice `+ `*) forms))))) From 48966ee6ed154ebb299be9d541906f84167d5933 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 3 Oct 2016 12:21:32 -0500 Subject: [PATCH 307/854] CLJ-2032 Add check that :args spec exists before conforming fspec Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index e9e48fbfd6..ae8ce8e063 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1682,9 +1682,11 @@ (specize* [s _] s) Spec - (conform* [_ f] (if (ifn? f) - (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) - ::invalid)) + (conform* [this f] (if argspec + (if (ifn? f) + (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) + ::invalid) + (throw (Exception. (str "Can't conform fspec without args spec: " (pr-str (describe this))))))) (unform* [_ f] f) (explain* [_ path via in f] (if (ifn? f) From 0b930f1e7e2bb2beef4ed1a12c51e4e17782666d Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 26 Sep 2016 12:42:03 -0500 Subject: [PATCH 308/854] CLJ-2027 Defer calling empty until later in namespace map printing Signed-off-by: Stuart Halloway --- src/clj/clojure/core_print.clj | 4 ++-- test/clojure/test_clojure/printer.clj | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 6c75e87974..f17e2f7a56 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -236,7 +236,7 @@ (when *print-namespace-maps* (loop [ns nil [[k v :as entry] & entries] (seq m) - lm (empty m)] + lm {}] (if entry (when (or (keyword? k) (symbol? k)) (if ns @@ -244,7 +244,7 @@ (recur ns entries (assoc lm (strip-ns k) v))) (when-let [new-ns (namespace k)] (recur new-ns entries (assoc lm (strip-ns k) v))))) - [ns lm])))) + [ns (apply conj (empty m) lm)])))) (defmethod print-method clojure.lang.IPersistentMap [m, ^Writer w] (print-meta m w) diff --git a/test/clojure/test_clojure/printer.clj b/test/clojure/test_clojure/printer.clj index 61efcf44b3..dc15618749 100644 --- a/test/clojure/test_clojure/printer.clj +++ b/test/clojure/test_clojure/printer.clj @@ -136,4 +136,7 @@ (deftest print-ns-maps (is (= "#:user{:a 1}" (binding [*print-namespace-maps* true] (pr-str {:user/a 1})))) - (is (= "{:user/a 1}" (binding [*print-namespace-maps* false] (pr-str {:user/a 1}))))) + (is (= "{:user/a 1}" (binding [*print-namespace-maps* false] (pr-str {:user/a 1})))) + (let [date-map (bean (java.util.Date. 0))] + (is (= (binding [*print-namespace-maps* true] (pr-str date-map)) + (binding [*print-namespace-maps* false] (pr-str date-map)))))) From c6b76fadb4750c8f73d842cfdf882b4a05683cae Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 28 Oct 2016 08:42:26 -0500 Subject: [PATCH 309/854] CLJ-2024 stest/check should resolve function spec Signed-off-by: Stuart Halloway --- src/clj/clojure/spec/test.clj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj index 73d34a30f0..587f441e3c 100644 --- a/src/clj/clojure/spec/test.clj +++ b/src/clj/clojure/spec/test.clj @@ -323,15 +323,16 @@ with explain-data + ::s/failure." (defn- check-1 [{:keys [s f v spec]} opts] (let [re-inst? (and v (seq (unstrument s)) true) - f (or f (when v @v))] + f (or f (when v @v)) + specd (s/spec spec)] (try (cond (or (nil? f) (some-> v meta :macro)) {:failure (ex-info "No fn to spec" {::s/failure :no-fn}) :sym s :spec spec} - (:args spec) - (let [tcret (quick-check f spec opts)] + (:args specd) + (let [tcret (quick-check f specd opts)] (make-check-result s spec tcret)) :default From 2ff700ede3866f97d7b1f53342e201df94384aee Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Sat, 7 Nov 2015 02:58:40 +0100 Subject: [PATCH 310/854] CLJ-1790: emit a cast to the interface during procol callsite emission or the jvm verifier will complain Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 7a17d0901d..7211e6c41f 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -3712,6 +3712,7 @@ public void emitProto(C context, ObjExpr objx, GeneratorAdapter gen){ gen.mark(onLabel); //target if(protocolOn != null) { + gen.checkCast(Type.getType(protocolOn)); MethodExpr.emitTypedArgs(objx, gen, onMethod.getParameterTypes(), RT.subvec(args,1,args.count())); if(context == C.RETURN) { From b80e1fe4b14654d943e2f8b060b0bc56e18b4757 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Fri, 7 Oct 2016 21:23:39 +0100 Subject: [PATCH 311/854] CLJ-1242: equals doesn't throw on sorted collections Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/PersistentTreeMap.java | 16 ++++++++++++++++ src/jvm/clojure/lang/PersistentTreeSet.java | 16 ++++++++++++++++ test/clojure/test_clojure/data_structures.clj | 2 ++ 3 files changed, 34 insertions(+) diff --git a/src/jvm/clojure/lang/PersistentTreeMap.java b/src/jvm/clojure/lang/PersistentTreeMap.java index adbbb9734b..7c792bb424 100644 --- a/src/jvm/clojure/lang/PersistentTreeMap.java +++ b/src/jvm/clojure/lang/PersistentTreeMap.java @@ -94,6 +94,22 @@ public boolean containsKey(Object key){ return entryAt(key) != null; } +public boolean equals(Object obj){ + try { + return super.equals(obj); + } catch (ClassCastException e) { + return false; + } +} + +public boolean equiv(Object obj){ + try { + return super.equiv(obj); + } catch (ClassCastException e) { + return false; + } +} + public PersistentTreeMap assocEx(Object key, Object val) { Box found = new Box(null); Node t = add(tree, key, val, found); diff --git a/src/jvm/clojure/lang/PersistentTreeSet.java b/src/jvm/clojure/lang/PersistentTreeSet.java index a5dc8ec6ef..4a11269212 100644 --- a/src/jvm/clojure/lang/PersistentTreeSet.java +++ b/src/jvm/clojure/lang/PersistentTreeSet.java @@ -42,6 +42,22 @@ static public PersistentTreeSet create(Comparator comp, ISeq items){ this._meta = meta; } +public boolean equals(Object obj){ + try { + return super.equals(obj); + } catch (ClassCastException e) { + return false; + } +} + +public boolean equiv(Object obj){ + try { + return super.equiv(obj); + } catch (ClassCastException e) { + return false; + } +} + public IPersistentSet disjoin(Object key) { if(contains(key)) return new PersistentTreeSet(meta(),impl.without(key)); diff --git a/test/clojure/test_clojure/data_structures.clj b/test/clojure/test_clojure/data_structures.clj index 35cf501f3c..7e32fe5c71 100644 --- a/test/clojure/test_clojure/data_structures.clj +++ b/test/clojure/test_clojure/data_structures.clj @@ -202,6 +202,8 @@ (hash-map :a 1 :b 2) (array-map :a 1 :b 2)) + (is (not= (sorted-set :a) (sorted-set 1))) + ; sorted-set vs. hash-set (is (not= (class (sorted-set 1)) (class (hash-set 1)))) (are [x y] (= x y) From e3c4d2e8c7538cfda40accd5c410a584495cb357 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 28 Oct 2016 14:24:47 -0500 Subject: [PATCH 312/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0635caad0..adccc5afba 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha14 http://clojure.org/ Clojure core environment and runtime library. From c0326d2386dd1227f35f46f1c75a8f87e2e93076 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 28 Oct 2016 14:24:47 -0500 Subject: [PATCH 313/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index adccc5afba..e0635caad0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha14 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. From ea994282e844de6e7db4d0a38595042117187c89 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 14 Oct 2016 11:04:23 -0500 Subject: [PATCH 314/854] CLJ-2035 Fix bad s/form for collection specs Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 27 ++++++++++++++++++++++----- test/clojure/test_clojure/spec.clj | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index ae8ce8e063..6fe02bb94b 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -492,6 +492,15 @@ [& pred-forms] `(merge-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) +(defn- res-kind + [opts] + (let [{kind :kind :as mopts} opts] + (->> + (if kind + (assoc mopts :kind `~(res kind)) + mopts) + (mapcat identity)))) + (defmacro every "takes a pred and validates collection elements against that pred. @@ -522,7 +531,11 @@ See also - coll-of, every-kv " [pred & {:keys [into kind count max-count min-count distinct gen-max gen] :as opts}] - (let [nopts (-> opts (dissoc :gen) (assoc ::kind-form `'~(res (:kind opts)))) + (let [desc (::describe opts) + nopts (-> opts + (dissoc :gen ::describe) + (assoc ::kind-form `'~(res (:kind opts)) + ::describe (c/or desc `'(every ~(res pred) ~@(res-kind opts))))) gx (gensym) cpreds (cond-> [(list (c/or kind `coll?) gx)] count (conj `(= ~count (bounded-count ~count ~gx))) @@ -544,7 +557,8 @@ See also - map-of" [kpred vpred & opts] - `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :into {} ~@opts)) + (let [desc `(every-kv ~(res kpred) ~(res vpred) ~@(res-kind opts))] + `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :into {} ::describe '~desc ~@opts))) (defmacro coll-of "Returns a spec for a collection of items satisfying pred. Unlike @@ -556,7 +570,8 @@ See also - every, map-of" [pred & opts] - `(every ~pred ::conform-all true ~@opts)) + (let [desc `(coll-of ~(res pred) ~@(res-kind opts))] + `(every ~pred ::conform-all true ::describe '~desc ~@opts))) (defmacro map-of "Returns a spec for a map whose keys satisfy kpred and vals satisfy @@ -569,7 +584,8 @@ See also - every-kv" [kpred vpred & opts] - `(every-kv ~kpred ~vpred ::conform-all true :kind map? ~@opts)) + (let [desc `(map-of ~(res kpred) ~(res vpred) ~@(res-kind opts))] + `(every-kv ~kpred ~vpred ::conform-all true :kind map? ::describe '~desc ~@opts))) (defmacro * @@ -1180,6 +1196,7 @@ "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" ([form pred opts] (every-impl form pred opts nil)) ([form pred {gen-into :into + describe-form ::describe :keys [kind ::kind-form count max-count min-count distinct gen-max ::kfn ::cpred conform-keys ::conform-all] :or {gen-max 20} @@ -1294,7 +1311,7 @@ (gen/vector pgen 0 gen-max)))))))) (with-gen* [_ gfn] (every-impl form pred opts gfn)) - (describe* [_] `(every ~form ~@(mapcat identity opts))))))) + (describe* [_] (c/or describe-form `(every ~(res form) ~@(mapcat identity opts)))))))) ;;;;;;;;;;;;;;;;;;;;;;; regex ;;;;;;;;;;;;;;;;;;; ;;See: diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj index 4f08ecdd7b..658017e984 100644 --- a/test/clojure/test_clojure/spec.clj +++ b/test/clojure/test_clojure/spec.clj @@ -169,6 +169,30 @@ [5 "x"] [5 "x"])) +(deftest coll-form + (are [spec form] + (= (s/form spec) form) + (s/map-of int? any?) + '(clojure.spec/map-of clojure.core/int? clojure.core/any?) + + (s/coll-of int?) + '(clojure.spec/coll-of clojure.core/int?) + + (s/every-kv int? int?) + '(clojure.spec/every-kv clojure.core/int? clojure.core/int?) + + (s/every int?) + '(clojure.spec/every clojure.core/int?) + + (s/coll-of (s/tuple (s/tuple int?))) + '(clojure.spec/coll-of (clojure.spec/tuple (clojure.spec/tuple clojure.core/int?))) + + (s/coll-of int? :kind vector?) + '(clojure.spec/coll-of clojure.core/int? :kind clojure.core/vector?) + + (s/coll-of int? :gen #(gen/return [1 2])) + '(clojure.spec/coll-of clojure.core/int? :gen (fn* [] (gen/return [1 2]))))) + (comment (require '[clojure.test :refer (run-tests)]) (in-ns 'clojure.test-clojure.spec) From 8708ffec6d39d16f2595cf9d5101c8e31f08efef Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 14 Oct 2016 14:23:48 -0500 Subject: [PATCH 315/854] CLJ-2043 fix s/form of s/conformer Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index 6fe02bb94b..ee2497cf48 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -652,8 +652,8 @@ (possibly converted) value or :clojure.spec/invalid, and returns a spec that uses it as a predicate/conformer. Optionally takes a second fn that does unform of result of first" - ([f] `(spec-impl '~f ~f nil true)) - ([f unf] `(spec-impl '~f ~f nil true ~unf))) + ([f] `(spec-impl '(conformer ~(res f)) ~f nil true)) + ([f unf] `(spec-impl '(conformer ~(res f) ~(res unf)) ~f nil true ~unf))) (defmacro fspec "takes :args :ret and (optional) :fn kwargs whose values are preds From 7f3749d6456e858c95801b81146954d10ffc52b1 Mon Sep 17 00:00:00 2001 From: Brandon Bloom Date: Tue, 8 Nov 2016 15:19:51 -0800 Subject: [PATCH 316/854] CLJ-2055: Fix spec for map binding forms. Signed-off-by: Stuart Halloway --- src/clj/clojure/core/specs.clj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index d8adc46401..7b53c88206 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -13,9 +13,10 @@ ;; sequential destructuring (s/def ::seq-binding-form - (s/cat :elems (s/* ::binding-form) - :rest (s/? (s/cat :amp #{'&} :form ::binding-form)) - :as (s/? (s/cat :as #{:as} :sym ::local-name)))) + (s/and vector? + (s/cat :elems (s/* ::binding-form) + :rest (s/? (s/cat :amp #{'&} :form ::binding-form)) + :as (s/? (s/cat :as #{:as} :sym ::local-name))))) ;; map destructuring From b2f5a3b37244b9e2bd9b0b26f88d4d7c16b29d24 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 29 Aug 2016 13:33:44 -0500 Subject: [PATCH 317/854] specs for import and refer-clojure Signed-off-by: Stuart Halloway --- src/clj/clojure/core/specs.clj | 87 +++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index 7b53c88206..2be75caaaa 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -103,57 +103,61 @@ (s/def ::exclude (s/coll-of simple-symbol?)) (s/def ::only (s/coll-of simple-symbol?)) (s/def ::rename (s/map-of simple-symbol? simple-symbol?)) +(s/def ::filters (s/keys* :opt-un [::exclude ::only ::rename])) (s/def ::ns-refer-clojure (s/spec (s/cat :clause #{:refer-clojure} - :filters (s/keys* :opt-un [::exclude ::only ::rename])))) + :filters ::filters))) (s/def ::refer (s/or :all #{:all} - :syms (s/coll-of simple-symbol?))) + :syms (s/coll-of simple-symbol?))) (s/def ::prefix-list (s/spec (s/cat :prefix simple-symbol? - :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list)) - :refer (s/keys* :opt-un [::as ::refer])))) + :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list)) + :refer (s/keys* :opt-un [::as ::refer])))) (s/def ::ns-require (s/spec (s/cat :clause #{:require} - :libs (s/* (s/alt :lib simple-symbol? - :prefix-list ::prefix-list - :flag #{:reload :reload-all :verbose}))))) + :libs (s/* (s/alt :lib simple-symbol? + :prefix-list ::prefix-list + :flag #{:reload :reload-all :verbose}))))) (s/def ::package-list (s/spec (s/cat :package simple-symbol? - :classes (s/* simple-symbol?)))) + :classes (s/* simple-symbol?)))) + +(s/def ::import-list + (s/* (s/alt :class simple-symbol? + :package-list ::package-list))) (s/def ::ns-import (s/spec (s/cat :clause #{:import} - :classes (s/* (s/alt :class simple-symbol? - :package-list ::package-list))))) + :classes ::import-list))) (s/def ::ns-refer (s/spec (s/cat :clause #{:refer} - :lib simple-symbol? - :filters (s/keys* :opt-un [::exclude ::only ::rename])))) + :lib simple-symbol? + :filters ::filters))) (s/def ::use-prefix-list (s/spec (s/cat :prefix simple-symbol? - :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::use-prefix-list)) - :filters (s/keys* :opt-un [::exclude ::only ::rename])))) + :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::use-prefix-list)) + :filters ::filters))) (s/def ::ns-use (s/spec (s/cat :clause #{:use} :libs (s/* (s/alt :lib simple-symbol? - :prefix-list ::use-prefix-list - :flag #{:reload :reload-all :verbose}))))) + :prefix-list ::use-prefix-list + :flag #{:reload :reload-all :verbose}))))) (s/def ::ns-load (s/spec (s/cat :clause #{:load} - :libs (s/* string?)))) + :libs (s/* string?)))) (s/def ::name simple-symbol?) (s/def ::extends simple-symbol?) @@ -165,8 +169,8 @@ (s/def ::post-init symbol?) (s/def ::method (s/and vector? (s/cat :name simple-symbol? - :param-types ::signature - :return-type simple-symbol?))) + :param-types ::signature + :return-type simple-symbol?))) (s/def ::methods (s/coll-of ::method :kind vector?)) (s/def ::main boolean?) (s/def ::factory simple-symbol?) @@ -181,23 +185,40 @@ (s/def ::ns-gen-class (s/spec (s/cat :clause #{:gen-class} - :options (s/keys* :opt-un [::name ::extends ::implements - ::init ::constructors ::post-init - ::methods ::main ::factory ::state - ::exposes ::prefix ::impl-ns ::load-impl-ns])))) + :options (s/keys* :opt-un [::name ::extends ::implements + ::init ::constructors ::post-init + ::methods ::main ::factory ::state + ::exposes ::prefix ::impl-ns ::load-impl-ns])))) (s/def ::ns-clauses (s/* (s/alt :refer-clojure ::ns-refer-clojure - :require ::ns-require - :import ::ns-import - :use ::ns-use - :refer ::ns-refer - :load ::ns-load - :gen-class ::ns-gen-class))) + :require ::ns-require + :import ::ns-import + :use ::ns-use + :refer ::ns-refer + :load ::ns-load + :gen-class ::ns-gen-class))) (s/fdef clojure.core/ns :args (s/cat :name simple-symbol? - :docstring (s/? string?) - :attr-map (s/? map?) - :clauses ::ns-clauses) - :ret any?) + :docstring (s/? string?) + :attr-map (s/? map?) + :clauses ::ns-clauses)) + +(defmacro ^:private quotable + "Returns a spec that accepts both the spec and a (quote ...) form of the spec" + [spec] + `(s/or :spec ~spec :quoted-spec (s/cat :quote #{'quote} :spec ~spec))) + +(s/def ::quotable-import-list + (s/* (s/alt :class (quotable simple-symbol?) + :package-list (quotable ::package-list)))) + +(s/fdef clojure.core/import + :args ::quotable-import-list) + +(s/fdef clojure.core/refer-clojure + :args (s/* (s/alt + :exclude (s/cat :op (quotable #{:exclude}) :arg (quotable ::exclude)) + :only (s/cat :op (quotable #{:only}) :arg (quotable ::only)) + :rename (s/cat :op (quotable #{:rename}) :arg (quotable ::rename))))) \ No newline at end of file From 62a10c977380e33df91cf25a47aeb2881edf111e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 19 Jan 2017 11:43:32 -0600 Subject: [PATCH 318/854] CLJ-2100 s/nilable form should retain original spec form Signed-off-by: Stuart Halloway --- src/clj/clojure/spec.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj index ee2497cf48..2d12e19cc2 100644 --- a/src/clj/clojure/spec.clj +++ b/src/clj/clojure/spec.clj @@ -1798,7 +1798,7 @@ [[1 (gen/delay (gen/return nil))] [9 (gen/delay (gensub pred overrides (conj path ::pred) rmap form))]]))) (with-gen* [_ gfn] (nilable-impl form pred gfn)) - (describe* [_] `(nilable ~(describe* @spec)))))) + (describe* [_] `(nilable ~(res form)))))) (defmacro nilable "returns a spec that accepts nil and values satisfying pred" From 70d48164a02d6d6043b571491d886315f7c1fdd0 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Thu, 16 Feb 2017 23:35:08 +0000 Subject: [PATCH 319/854] CLJ-2144: conform map fn bodies as :body rather than as :prepost Signed-off-by: Stuart Halloway --- src/clj/clojure/core/specs.clj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj index 2be75caaaa..8cea00e27a 100644 --- a/src/clj/clojure/core/specs.clj +++ b/src/clj/clojure/core/specs.clj @@ -73,8 +73,9 @@ (s/def ::args+body (s/cat :args ::arg-list - :prepost (s/? map?) - :body (s/* any?))) + :body (s/alt :prepost+body (s/cat :prepost map? + :body (s/+ any?)) + :body (s/* any?)))) (s/def ::defn-args (s/cat :name simple-symbol? From e631f158711adc538a16ec1d09f2afe9b7238e5e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 14 Dec 2016 10:25:36 -0600 Subject: [PATCH 320/854] deployment updates Signed-off-by: Stuart Halloway --- pom.xml | 88 +++++++++++++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/pom.xml b/pom.xml index e0635caad0..475e1ac197 100644 --- a/pom.xml +++ b/pom.xml @@ -26,12 +26,6 @@ - - org.sonatype.oss - oss-parent - 7 - - scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git @@ -75,6 +69,14 @@ + + + + sonatype-nexus-staging + https://oss.sonatype.org/content/repositories/snapshots + + + @@ -90,11 +92,11 @@ org.apache.maven.plugins maven-compiler-plugin - 2.3.2 + 3.1 1.6 1.6 - ${project.build.sourceEncoding} + UTF-8 @@ -206,7 +208,7 @@ instead, push SCM changes in Hudson configuration --> org.apache.maven.plugins maven-release-plugin - 2.1 + 2.4.1 false true @@ -221,6 +223,36 @@ true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + @@ -265,43 +297,5 @@ - - sonatype-oss-release - - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.7 - - true - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.4.4 - - - default-deploy - deploy - - - deploy - - - - - - https://oss.sonatype.org/ - - sonatype-nexus-staging - - - - - From 4622e8cc7540fa755b348a93c6a941a47e96ab78 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 14 Mar 2017 10:52:23 -0500 Subject: [PATCH 321/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha15 --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 475e1ac197..5974291637 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha15 http://clojure.org/ Clojure core environment and runtime library. @@ -30,6 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git + clojure-1.9.0-alpha15 From 303496aa196f75f9fd7c899b2e1fd3b430a96521 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 14 Mar 2017 10:52:23 -0500 Subject: [PATCH 322/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5974291637..3ae4fc233f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha15 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-alpha15 + HEAD From 4cc97e3e40616a7c0dd637d2401529799b440133 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 23 Mar 2017 09:46:49 -0500 Subject: [PATCH 323/854] Move artifact signing to a profile Signed-off-by: Rich Hickey --- pom.xml | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 3ae4fc233f..dafc02958a 100644 --- a/pom.xml +++ b/pom.xml @@ -239,21 +239,6 @@ - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - @@ -298,5 +283,27 @@ + + + sign + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + From a26dfc1390c53ca10dba750b8d5e6b93e846c067 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 20 Apr 2017 10:52:32 -0400 Subject: [PATCH 324/854] added Var serialization - serializes identity, not value --- src/jvm/clojure/lang/Var.java | 28 ++++++++++++++++++++- test/clojure/test_clojure/serialization.clj | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/jvm/clojure/lang/Var.java b/src/jvm/clojure/lang/Var.java index 3dc9580c64..07f8a8e5fd 100644 --- a/src/jvm/clojure/lang/Var.java +++ b/src/jvm/clojure/lang/Var.java @@ -12,10 +12,12 @@ package clojure.lang; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.util.concurrent.atomic.AtomicBoolean; -public final class Var extends ARef implements IFn, IRef, Settable{ +public final class Var extends ARef implements IFn, IRef, Settable, Serializable{ static class TBox{ @@ -712,4 +714,28 @@ public Object invoke(Object c, Object k) { return RT.dissoc(c, k); } }; + + +/*** + Note - serialization only supports reconnecting the Var identity on the deserializing end + Neither the value in the var nor any of its properties are serialized +***/ + +private static class Serialized implements Serializable{ + public Serialized(Symbol nsName, Symbol sym){ + this.nsName = nsName; + this.sym = sym; + } + + private Symbol nsName; + private Symbol sym; + + private Object readResolve() throws ObjectStreamException{ + return intern(nsName, sym); + } +} + +private Object writeReplace() throws ObjectStreamException{ + return new Serialized(ns.getName(), sym); +} } diff --git a/test/clojure/test_clojure/serialization.clj b/test/clojure/test_clojure/serialization.clj index 60cd65c9fd..316fbd6270 100644 --- a/test/clojure/test_clojure/serialization.clj +++ b/test/clojure/test_clojure/serialization.clj @@ -169,7 +169,7 @@ (atom nil) (ref nil) (agent nil) - #'+ + ;;#'+ ;; stateful seqs (enumeration-seq (java.util.Collections/enumeration (range 50))) From 42a7fd42cfae973d2af16d4bed40c7594574b58b Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 26 Apr 2017 19:56:24 -0500 Subject: [PATCH 325/854] Remove clojure.spec and rename spec namespace usage Signed-off-by: Stuart Halloway --- build.xml | 11 +- pom.xml | 26 + src/clj/clojure/core/specs.clj | 225 ---- src/clj/clojure/main.clj | 2 +- src/clj/clojure/repl.clj | 2 +- src/clj/clojure/spec.clj | 1936 ---------------------------- src/clj/clojure/spec/gen.clj | 224 ---- src/clj/clojure/spec/test.clj | 466 ------- src/jvm/clojure/lang/Compiler.java | 9 +- src/jvm/clojure/lang/RT.java | 4 +- test/clojure/test_clojure/spec.clj | 201 --- 11 files changed, 36 insertions(+), 3070 deletions(-) delete mode 100644 src/clj/clojure/core/specs.clj delete mode 100644 src/clj/clojure/spec.clj delete mode 100644 src/clj/clojure/spec/gen.clj delete mode 100644 src/clj/clojure/spec/test.clj delete mode 100644 test/clojure/test_clojure/spec.clj diff --git a/build.xml b/build.xml index 535ac0177c..41790f5039 100644 --- a/build.xml +++ b/build.xml @@ -59,7 +59,6 @@ - @@ -82,13 +81,6 @@ - - - - - @@ -99,8 +91,9 @@ Direct linking = ${directlinking} + ${test-classes}:${test}:${build}:${cljsrc}:${maven.compile.classpath} diff --git a/pom.xml b/pom.xml index dafc02958a..566b8df6fd 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,32 @@ + + org.clojure + spec.alpha + 0.1.94 + + + org.clojure + clojure + + + + + org.clojure + core.specs.alpha + 0.1.10 + + + org.clojure + clojure + + + org.clojure + spec.alpha + + + org.codehaus.jsr166-mirror jsr166y diff --git a/src/clj/clojure/core/specs.clj b/src/clj/clojure/core/specs.clj deleted file mode 100644 index 8cea00e27a..0000000000 --- a/src/clj/clojure/core/specs.clj +++ /dev/null @@ -1,225 +0,0 @@ -(ns ^{:skip-wiki true} clojure.core.specs - (:require [clojure.spec :as s])) - -;;;; destructure - -(s/def ::local-name (s/and simple-symbol? #(not= '& %))) - -(s/def ::binding-form - (s/or :sym ::local-name - :seq ::seq-binding-form - :map ::map-binding-form)) - -;; sequential destructuring - -(s/def ::seq-binding-form - (s/and vector? - (s/cat :elems (s/* ::binding-form) - :rest (s/? (s/cat :amp #{'&} :form ::binding-form)) - :as (s/? (s/cat :as #{:as} :sym ::local-name))))) - -;; map destructuring - -(s/def ::keys (s/coll-of ident? :kind vector?)) -(s/def ::syms (s/coll-of symbol? :kind vector?)) -(s/def ::strs (s/coll-of simple-symbol? :kind vector?)) -(s/def ::or (s/map-of simple-symbol? any?)) -(s/def ::as ::local-name) - -(s/def ::map-special-binding - (s/keys :opt-un [::as ::or ::keys ::syms ::strs])) - -(s/def ::map-binding (s/tuple ::binding-form any?)) - -(s/def ::ns-keys - (s/tuple - (s/and qualified-keyword? #(-> % name #{"keys" "syms"})) - (s/coll-of simple-symbol? :kind vector?))) - -(s/def ::map-bindings - (s/every (s/or :mb ::map-binding - :nsk ::ns-keys - :msb (s/tuple #{:as :or :keys :syms :strs} any?)) :into {})) - -(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding)) - -;; bindings - -(s/def ::binding (s/cat :binding ::binding-form :init-expr any?)) -(s/def ::bindings (s/and vector? (s/* ::binding))) - -;; let, if-let, when-let - -(s/fdef clojure.core/let - :args (s/cat :bindings ::bindings - :body (s/* any?))) - -(s/fdef clojure.core/if-let - :args (s/cat :bindings (s/and vector? ::binding) - :then any? - :else (s/? any?))) - -(s/fdef clojure.core/when-let - :args (s/cat :bindings (s/and vector? ::binding) - :body (s/* any?))) - -;; defn, defn-, fn - -(s/def ::arg-list - (s/and - vector? - (s/cat :args (s/* ::binding-form) - :varargs (s/? (s/cat :amp #{'&} :form ::binding-form))))) - -(s/def ::args+body - (s/cat :args ::arg-list - :body (s/alt :prepost+body (s/cat :prepost map? - :body (s/+ any?)) - :body (s/* any?)))) - -(s/def ::defn-args - (s/cat :name simple-symbol? - :docstring (s/? string?) - :meta (s/? map?) - :bs (s/alt :arity-1 ::args+body - :arity-n (s/cat :bodies (s/+ (s/spec ::args+body)) - :attr (s/? map?))))) - -(s/fdef clojure.core/defn - :args ::defn-args - :ret any?) - -(s/fdef clojure.core/defn- - :args ::defn-args - :ret any?) - -(s/fdef clojure.core/fn - :args (s/cat :name (s/? simple-symbol?) - :bs (s/alt :arity-1 ::args+body - :arity-n (s/+ (s/spec ::args+body)))) - :ret any?) - -;;;; ns - -(s/def ::exclude (s/coll-of simple-symbol?)) -(s/def ::only (s/coll-of simple-symbol?)) -(s/def ::rename (s/map-of simple-symbol? simple-symbol?)) -(s/def ::filters (s/keys* :opt-un [::exclude ::only ::rename])) - -(s/def ::ns-refer-clojure - (s/spec (s/cat :clause #{:refer-clojure} - :filters ::filters))) - -(s/def ::refer (s/or :all #{:all} - :syms (s/coll-of simple-symbol?))) - -(s/def ::prefix-list - (s/spec - (s/cat :prefix simple-symbol? - :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list)) - :refer (s/keys* :opt-un [::as ::refer])))) - -(s/def ::ns-require - (s/spec (s/cat :clause #{:require} - :libs (s/* (s/alt :lib simple-symbol? - :prefix-list ::prefix-list - :flag #{:reload :reload-all :verbose}))))) - -(s/def ::package-list - (s/spec - (s/cat :package simple-symbol? - :classes (s/* simple-symbol?)))) - -(s/def ::import-list - (s/* (s/alt :class simple-symbol? - :package-list ::package-list))) - -(s/def ::ns-import - (s/spec - (s/cat :clause #{:import} - :classes ::import-list))) - -(s/def ::ns-refer - (s/spec (s/cat :clause #{:refer} - :lib simple-symbol? - :filters ::filters))) - -(s/def ::use-prefix-list - (s/spec - (s/cat :prefix simple-symbol? - :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::use-prefix-list)) - :filters ::filters))) - -(s/def ::ns-use - (s/spec (s/cat :clause #{:use} - :libs (s/* (s/alt :lib simple-symbol? - :prefix-list ::use-prefix-list - :flag #{:reload :reload-all :verbose}))))) - -(s/def ::ns-load - (s/spec (s/cat :clause #{:load} - :libs (s/* string?)))) - -(s/def ::name simple-symbol?) -(s/def ::extends simple-symbol?) -(s/def ::implements (s/coll-of simple-symbol? :kind vector?)) -(s/def ::init symbol?) -(s/def ::class-ident (s/or :class simple-symbol? :class-name string?)) -(s/def ::signature (s/coll-of ::class-ident :kind vector?)) -(s/def ::constructors (s/map-of ::signature ::signature)) -(s/def ::post-init symbol?) -(s/def ::method (s/and vector? - (s/cat :name simple-symbol? - :param-types ::signature - :return-type simple-symbol?))) -(s/def ::methods (s/coll-of ::method :kind vector?)) -(s/def ::main boolean?) -(s/def ::factory simple-symbol?) -(s/def ::state simple-symbol?) -(s/def ::get simple-symbol?) -(s/def ::set simple-symbol?) -(s/def ::expose (s/keys :opt-un [::get ::set])) -(s/def ::exposes (s/map-of simple-symbol? ::expose)) -(s/def ::prefix string?) -(s/def ::impl-ns simple-symbol?) -(s/def ::load-impl-ns boolean?) - -(s/def ::ns-gen-class - (s/spec (s/cat :clause #{:gen-class} - :options (s/keys* :opt-un [::name ::extends ::implements - ::init ::constructors ::post-init - ::methods ::main ::factory ::state - ::exposes ::prefix ::impl-ns ::load-impl-ns])))) - -(s/def ::ns-clauses - (s/* (s/alt :refer-clojure ::ns-refer-clojure - :require ::ns-require - :import ::ns-import - :use ::ns-use - :refer ::ns-refer - :load ::ns-load - :gen-class ::ns-gen-class))) - -(s/fdef clojure.core/ns - :args (s/cat :name simple-symbol? - :docstring (s/? string?) - :attr-map (s/? map?) - :clauses ::ns-clauses)) - -(defmacro ^:private quotable - "Returns a spec that accepts both the spec and a (quote ...) form of the spec" - [spec] - `(s/or :spec ~spec :quoted-spec (s/cat :quote #{'quote} :spec ~spec))) - -(s/def ::quotable-import-list - (s/* (s/alt :class (quotable simple-symbol?) - :package-list (quotable ::package-list)))) - -(s/fdef clojure.core/import - :args ::quotable-import-list) - -(s/fdef clojure.core/refer-clojure - :args (s/* (s/alt - :exclude (s/cat :op (quotable #{:exclude}) :arg (quotable ::exclude)) - :only (s/cat :op (quotable #{:only}) :arg (quotable ::only)) - :rename (s/cat :op (quotable #{:rename}) :arg (quotable ::rename))))) \ No newline at end of file diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj index c023f1f863..3394f6beec 100644 --- a/src/clj/clojure/main.clj +++ b/src/clj/clojure/main.clj @@ -81,7 +81,7 @@ *command-line-args* *command-line-args* *unchecked-math* *unchecked-math* *assert* *assert* - clojure.spec/*explain-out* clojure.spec/*explain-out* + clojure.spec.alpha/*explain-out* clojure.spec.alpha/*explain-out* *1 nil *2 nil *3 nil diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index 2dec1f3f02..0852382267 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -12,7 +12,7 @@ ^{:author "Chris Houser, Christophe Grand, Stephen Gilardi, Michel Salim" :doc "Utilities meant to be used interactively at the REPL"} clojure.repl - (:require [clojure.spec :as spec]) + (:require [clojure.spec.alpha :as spec]) (:import (java.io LineNumberReader InputStreamReader PushbackReader) (clojure.lang RT Reflector))) diff --git a/src/clj/clojure/spec.clj b/src/clj/clojure/spec.clj deleted file mode 100644 index 2d12e19cc2..0000000000 --- a/src/clj/clojure/spec.clj +++ /dev/null @@ -1,1936 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns clojure.spec - (:refer-clojure :exclude [+ * and assert or cat def keys merge]) - (:require [clojure.walk :as walk] - [clojure.spec.gen :as gen] - [clojure.string :as str])) - -(alias 'c 'clojure.core) - -(set! *warn-on-reflection* true) - -(def ^:dynamic *recursion-limit* - "A soft limit on how many times a branching spec (or/alt/*/opt-keys/multi-spec) - can be recursed through during generation. After this a - non-recursive branch will be chosen." - 4) - -(def ^:dynamic *fspec-iterations* - "The number of times an anonymous fn specified by fspec will be (generatively) tested during conform" - 21) - -(def ^:dynamic *coll-check-limit* - "The number of elements validated in a collection spec'ed with 'every'" - 101) - -(def ^:dynamic *coll-error-limit* - "The number of errors reported by explain in a collection spec'ed with 'every'" - 20) - -(defprotocol Spec - (conform* [spec x]) - (unform* [spec y]) - (explain* [spec path via in x]) - (gen* [spec overrides path rmap]) - (with-gen* [spec gfn]) - (describe* [spec])) - -(defonce ^:private registry-ref (atom {})) - -(defn- deep-resolve [reg k] - (loop [spec k] - (if (ident? spec) - (recur (get reg spec)) - spec))) - -(defn- reg-resolve - "returns the spec/regex at end of alias chain starting with k, nil if not found, k if k not ident" - [k] - (if (ident? k) - (let [reg @registry-ref - spec (get reg k)] - (if-not (ident? spec) - spec - (deep-resolve reg spec))) - k)) - -(defn- reg-resolve! - "returns the spec/regex at end of alias chain starting with k, throws if not found, k if k not ident" - [k] - (if (ident? k) - (c/or (reg-resolve k) - (throw (Exception. (str "Unable to resolve spec: " k)))) - k)) - -(defn spec? - "returns x if x is a spec object, else logical false" - [x] - (when (instance? clojure.spec.Spec x) - x)) - -(defn regex? - "returns x if x is a (clojure.spec) regex op, else logical false" - [x] - (c/and (::op x) x)) - -(defn- with-name [spec name] - (cond - (ident? spec) spec - (regex? spec) (assoc spec ::name name) - - (instance? clojure.lang.IObj spec) - (with-meta spec (assoc (meta spec) ::name name)))) - -(defn- spec-name [spec] - (cond - (ident? spec) spec - - (regex? spec) (::name spec) - - (instance? clojure.lang.IObj spec) - (-> (meta spec) ::name))) - -(declare spec-impl) -(declare regex-spec-impl) - -(defn- maybe-spec - "spec-or-k must be a spec, regex or resolvable kw/sym, else returns nil." - [spec-or-k] - (let [s (c/or (c/and (ident? spec-or-k) (reg-resolve spec-or-k)) - (spec? spec-or-k) - (regex? spec-or-k) - nil)] - (if (regex? s) - (with-name (regex-spec-impl s nil) (spec-name s)) - s))) - -(defn- the-spec - "spec-or-k must be a spec, regex or kw/sym, else returns nil. Throws if unresolvable kw/sym" - [spec-or-k] - (c/or (maybe-spec spec-or-k) - (when (ident? spec-or-k) - (throw (Exception. (str "Unable to resolve spec: " spec-or-k)))))) - -(defprotocol Specize - (specize* [_] [_ form])) - -(extend-protocol Specize - clojure.lang.Keyword - (specize* ([k] (specize* (reg-resolve! k))) - ([k _] (specize* (reg-resolve! k)))) - - clojure.lang.Symbol - (specize* ([s] (specize* (reg-resolve! s))) - ([s _] (specize* (reg-resolve! s)))) - - Object - (specize* ([o] (spec-impl ::unknown o nil nil)) - ([o form] (spec-impl form o nil nil)))) - -(defn- specize - ([s] (c/or (spec? s) (specize* s))) - ([s form] (c/or (spec? s) (specize* s form)))) - -(defn invalid? - "tests the validity of a conform return value" - [ret] - (identical? ::invalid ret)) - -(defn conform - "Given a spec and a value, returns :clojure.spec/invalid if value does not match spec, - else the (possibly destructured) value." - [spec x] - (conform* (specize spec) x)) - -(defn unform - "Given a spec and a value created by or compliant with a call to - 'conform' with the same spec, returns a value with all conform - destructuring undone." - [spec x] - (unform* (specize spec) x)) - -(defn form - "returns the spec as data" - [spec] - ;;TODO - incorporate gens - (describe* (specize spec))) - -(defn abbrev [form] - (cond - (seq? form) - (walk/postwalk (fn [form] - (cond - (c/and (symbol? form) (namespace form)) - (-> form name symbol) - - (c/and (seq? form) (= 'fn (first form)) (= '[%] (second form))) - (last form) - - :else form)) - form) - - (c/and (symbol? form) (namespace form)) - (-> form name symbol) - - :else form)) - -(defn describe - "returns an abbreviated description of the spec as data" - [spec] - (abbrev (form spec))) - -(defn with-gen - "Takes a spec and a no-arg, generator-returning fn and returns a version of that spec that uses that generator" - [spec gen-fn] - (let [spec (reg-resolve spec)] - (if (regex? spec) - (assoc spec ::gfn gen-fn) - (with-gen* (specize spec) gen-fn)))) - -(defn explain-data* [spec path via in x] - (let [probs (explain* (specize spec) path via in x)] - (when-not (empty? probs) - {::problems probs}))) - -(defn explain-data - "Given a spec and a value x which ought to conform, returns nil if x - conforms, else a map with at least the key ::problems whose value is - a collection of problem-maps, where problem-map has at least :path :pred and :val - keys describing the predicate and the value that failed at that - path." - [spec x] - (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) [] x)) - -(defn explain-printer - "Default printer for explain-data. nil indicates a successful validation." - [ed] - (if ed - (do - ;;(prn {:ed ed}) - (doseq [{:keys [path pred val reason via in] :as prob} (::problems ed)] - (when-not (empty? in) - (print "In:" (pr-str in) "")) - (print "val: ") - (pr val) - (print " fails") - (when-not (empty? via) - (print " spec:" (pr-str (last via)))) - (when-not (empty? path) - (print " at:" (pr-str path))) - (print " predicate: ") - (pr (abbrev pred)) - (when reason (print ", " reason)) - (doseq [[k v] prob] - (when-not (#{:path :pred :val :reason :via :in} k) - (print "\n\t" (pr-str k) " ") - (pr v))) - (newline)) - (doseq [[k v] ed] - (when-not (#{::problems} k) - (print (pr-str k) " ") - (pr v) - (newline)))) - (println "Success!"))) - -(def ^:dynamic *explain-out* explain-printer) - -(defn explain-out - "Prints explanation data (per 'explain-data') to *out* using the printer in *explain-out*, - by default explain-printer." - [ed] - (*explain-out* ed)) - -(defn explain - "Given a spec and a value that fails to conform, prints an explanation to *out*." - [spec x] - (explain-out (explain-data spec x))) - -(defn explain-str - "Given a spec and a value that fails to conform, returns an explanation as a string." - [spec x] - (with-out-str (explain spec x))) - -(declare valid?) - -(defn- gensub - [spec overrides path rmap form] - ;;(prn {:spec spec :over overrides :path path :form form}) - (let [spec (specize spec)] - (if-let [g (c/or (when-let [gfn (c/or (get overrides (c/or (spec-name spec) spec)) - (get overrides path))] - (gfn)) - (gen* spec overrides path rmap))] - (gen/such-that #(valid? spec %) g 100) - (let [abbr (abbrev form)] - (throw (ex-info (str "Unable to construct gen at: " path " for: " abbr) - {::path path ::form form ::failure :no-gen})))))) - -(defn gen - "Given a spec, returns the generator for it, or throws if none can - be constructed. Optionally an overrides map can be provided which - should map spec names or paths (vectors of keywords) to no-arg - generator-creating fns. These will be used instead of the generators at those - names/paths. Note that parent generator (in the spec or overrides - map) will supersede those of any subtrees. A generator for a regex - op must always return a sequential collection (i.e. a generator for - s/? should return either an empty sequence/vector or a - sequence/vector with one item in it)" - ([spec] (gen spec nil)) - ([spec overrides] (gensub spec overrides [] {::recursion-limit *recursion-limit*} spec))) - -(defn- ->sym - "Returns a symbol from a symbol or var" - [x] - (if (var? x) - (let [^clojure.lang.Var v x] - (symbol (str (.name (.ns v))) - (str (.sym v)))) - x)) - -(defn- unfn [expr] - (if (c/and (seq? expr) - (symbol? (first expr)) - (= "fn*" (name (first expr)))) - (let [[[s] & form] (rest expr)] - (conj (walk/postwalk-replace {s '%} form) '[%] 'fn)) - expr)) - -(defn- res [form] - (cond - (keyword? form) form - (symbol? form) (c/or (-> form resolve ->sym) form) - (sequential? form) (walk/postwalk #(if (symbol? %) (res %) %) (unfn form)) - :else form)) - -(defn ^:skip-wiki def-impl - "Do not call this directly, use 'def'" - [k form spec] - (c/assert (c/and (ident? k) (namespace k)) "k must be namespaced keyword or resolvable symbol") - (let [spec (if (c/or (spec? spec) (regex? spec) (get @registry-ref spec)) - spec - (spec-impl form spec nil nil))] - (swap! registry-ref assoc k (with-name spec k)) - k)) - -(defn- ns-qualify - "Qualify symbol s by resolving it or using the current *ns*." - [s] - (if-let [ns-sym (some-> s namespace symbol)] - (c/or (some-> (get (ns-aliases *ns*) ns-sym) str (symbol (name s))) - s) - (symbol (str (.name *ns*)) (str s)))) - -(defmacro def - "Given a namespace-qualified keyword or resolvable symbol k, and a - spec, spec-name, predicate or regex-op makes an entry in the - registry mapping k to the spec" - [k spec-form] - (let [k (if (symbol? k) (ns-qualify k) k)] - `(def-impl '~k '~(res spec-form) ~spec-form))) - -(defn registry - "returns the registry map, prefer 'get-spec' to lookup a spec by name" - [] - @registry-ref) - -(defn get-spec - "Returns spec registered for keyword/symbol/var k, or nil." - [k] - (get (registry) (if (keyword? k) k (->sym k)))) - -(declare map-spec) - -(defmacro spec - "Takes a single predicate form, e.g. can be the name of a predicate, - like even?, or a fn literal like #(< % 42). Note that it is not - generally necessary to wrap predicates in spec when using the rest - of the spec macros, only to attach a unique generator - - Can also be passed the result of one of the regex ops - - cat, alt, *, +, ?, in which case it will return a regex-conforming - spec, useful when nesting an independent regex. - --- - - Optionally takes :gen generator-fn, which must be a fn of no args that - returns a test.check generator. - - Returns a spec." - [form & {:keys [gen]}] - (when form - `(spec-impl '~(res form) ~form ~gen nil))) - -(defmacro multi-spec - "Takes the name of a spec/predicate-returning multimethod and a - tag-restoring keyword or fn (retag). Returns a spec that when - conforming or explaining data will pass it to the multimethod to get - an appropriate spec. You can e.g. use multi-spec to dynamically and - extensibly associate specs with 'tagged' data (i.e. data where one - of the fields indicates the shape of the rest of the structure). - - (defmulti mspec :tag) - - The methods should ignore their argument and return a predicate/spec: - (defmethod mspec :int [_] (s/keys :req-un [::tag ::i])) - - retag is used during generation to retag generated values with - matching tags. retag can either be a keyword, at which key the - dispatch-tag will be assoc'ed, or a fn of generated value and - dispatch-tag that should return an appropriately retagged value. - - Note that because the tags themselves comprise an open set, - the tag key spec cannot enumerate the values, but can e.g. - test for keyword?. - - Note also that the dispatch values of the multimethod will be - included in the path, i.e. in reporting and gen overrides, even - though those values are not evident in the spec. -" - [mm retag] - `(multi-spec-impl '~(res mm) (var ~mm) ~retag)) - -(defmacro keys - "Creates and returns a map validating spec. :req and :opt are both - vectors of namespaced-qualified keywords. The validator will ensure - the :req keys are present. The :opt keys serve as documentation and - may be used by the generator. - - The :req key vector supports 'and' and 'or' for key groups: - - (s/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z]) - - There are also -un versions of :req and :opt. These allow - you to connect unqualified keys to specs. In each case, fully - qualfied keywords are passed, which name the specs, but unqualified - keys (with the same name component) are expected and checked at - conform-time, and generated during gen: - - (s/keys :req-un [:my.ns/x :my.ns/y]) - - The above says keys :x and :y are required, and will be validated - and generated by specs (if they exist) named :my.ns/x :my.ns/y - respectively. - - In addition, the values of *all* namespace-qualified keys will be validated - (and possibly destructured) by any registered specs. Note: there is - no support for inline value specification, by design. - - Optionally takes :gen generator-fn, which must be a fn of no args that - returns a test.check generator." - [& {:keys [req req-un opt opt-un gen]}] - (let [unk #(-> % name keyword) - req-keys (filterv keyword? (flatten req)) - req-un-specs (filterv keyword? (flatten req-un)) - _ (c/assert (every? #(c/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un)) - "all keys must be namespace-qualified keywords") - req-specs (into req-keys req-un-specs) - req-keys (into req-keys (map unk req-un-specs)) - opt-keys (into (vec opt) (map unk opt-un)) - opt-specs (into (vec opt) opt-un) - gx (gensym) - parse-req (fn [rk f] - (map (fn [x] - (if (keyword? x) - `(contains? ~gx ~(f x)) - (walk/postwalk - (fn [y] (if (keyword? y) `(contains? ~gx ~(f y)) y)) - x))) - rk)) - pred-exprs [`(map? ~gx)] - pred-exprs (into pred-exprs (parse-req req identity)) - pred-exprs (into pred-exprs (parse-req req-un unk)) - keys-pred `(fn* [~gx] (c/and ~@pred-exprs)) - pred-exprs (mapv (fn [e] `(fn* [~gx] ~e)) pred-exprs) - pred-forms (walk/postwalk res pred-exprs)] - ;; `(map-spec-impl ~req-keys '~req ~opt '~pred-forms ~pred-exprs ~gen) - `(map-spec-impl {:req '~req :opt '~opt :req-un '~req-un :opt-un '~opt-un - :req-keys '~req-keys :req-specs '~req-specs - :opt-keys '~opt-keys :opt-specs '~opt-specs - :pred-forms '~pred-forms - :pred-exprs ~pred-exprs - :keys-pred ~keys-pred - :gfn ~gen}))) - -(defmacro or - "Takes key+pred pairs, e.g. - - (s/or :even even? :small #(< % 42)) - - Returns a destructuring spec that returns a map entry containing the - key of the first matching pred and the corresponding value. Thus the - 'key' and 'val' functions can be used to refer generically to the - components of the tagged return." - [& key-pred-forms] - (let [pairs (partition 2 key-pred-forms) - keys (mapv first pairs) - pred-forms (mapv second pairs) - pf (mapv res pred-forms)] - (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords") - `(or-spec-impl ~keys '~pf ~pred-forms nil))) - -(defmacro and - "Takes predicate/spec-forms, e.g. - - (s/and even? #(< % 42)) - - Returns a spec that returns the conformed value. Successive - conformed values propagate through rest of predicates." - [& pred-forms] - `(and-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) - -(defmacro merge - "Takes map-validating specs (e.g. 'keys' specs) and - returns a spec that returns a conformed map satisfying all of the - specs. Unlike 'and', merge can generate maps satisfying the - union of the predicates." - [& pred-forms] - `(merge-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil)) - -(defn- res-kind - [opts] - (let [{kind :kind :as mopts} opts] - (->> - (if kind - (assoc mopts :kind `~(res kind)) - mopts) - (mapcat identity)))) - -(defmacro every - "takes a pred and validates collection elements against that pred. - - Note that 'every' does not do exhaustive checking, rather it samples - *coll-check-limit* elements. Nor (as a result) does it do any - conforming of elements. 'explain' will report at most *coll-error-limit* - problems. Thus 'every' should be suitable for potentially large - collections. - - Takes several kwargs options that further constrain the collection: - - :kind - a pred/spec that the collection type must satisfy, e.g. vector? - (default nil) Note that if :kind is specified and :into is - not, this pred must generate in order for every to generate. - :count - specifies coll has exactly this count (default nil) - :min-count, :max-count - coll has count (<= min-count count max-count) (defaults nil) - :distinct - all the elements are distinct (default nil) - - And additional args that control gen - - :gen-max - the maximum coll size to generate (default 20) - :into - one of [], (), {}, #{} - the default collection to generate into - (default: empty coll as generated by :kind pred if supplied, else []) - - Optionally takes :gen generator-fn, which must be a fn of no args that - returns a test.check generator - - See also - coll-of, every-kv -" - [pred & {:keys [into kind count max-count min-count distinct gen-max gen] :as opts}] - (let [desc (::describe opts) - nopts (-> opts - (dissoc :gen ::describe) - (assoc ::kind-form `'~(res (:kind opts)) - ::describe (c/or desc `'(every ~(res pred) ~@(res-kind opts))))) - gx (gensym) - cpreds (cond-> [(list (c/or kind `coll?) gx)] - count (conj `(= ~count (bounded-count ~count ~gx))) - - (c/or min-count max-count) - (conj `(<= (c/or ~min-count 0) - (bounded-count (if ~max-count (inc ~max-count) ~min-count) ~gx) - (c/or ~max-count Integer/MAX_VALUE))) - - distinct - (conj `(c/or (empty? ~gx) (apply distinct? ~gx))))] - `(every-impl '~pred ~pred ~(assoc nopts ::cpred `(fn* [~gx] (c/and ~@cpreds))) ~gen))) - -(defmacro every-kv - "like 'every' but takes separate key and val preds and works on associative collections. - - Same options as 'every', :into defaults to {} - - See also - map-of" - - [kpred vpred & opts] - (let [desc `(every-kv ~(res kpred) ~(res vpred) ~@(res-kind opts))] - `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :into {} ::describe '~desc ~@opts))) - -(defmacro coll-of - "Returns a spec for a collection of items satisfying pred. Unlike - 'every', coll-of will exhaustively conform every value. - - Same options as 'every'. conform will produce a collection - corresponding to :into if supplied, else will match the input collection, - avoiding rebuilding when possible. - - See also - every, map-of" - [pred & opts] - (let [desc `(coll-of ~(res pred) ~@(res-kind opts))] - `(every ~pred ::conform-all true ::describe '~desc ~@opts))) - -(defmacro map-of - "Returns a spec for a map whose keys satisfy kpred and vals satisfy - vpred. Unlike 'every-kv', map-of will exhaustively conform every - value. - - Same options as 'every', :kind defaults to map?, with the addition of: - - :conform-keys - conform keys as well as values (default false) - - See also - every-kv" - [kpred vpred & opts] - (let [desc `(map-of ~(res kpred) ~(res vpred) ~@(res-kind opts))] - `(every-kv ~kpred ~vpred ::conform-all true :kind map? ::describe '~desc ~@opts))) - - -(defmacro * - "Returns a regex op that matches zero or more values matching - pred. Produces a vector of matches iff there is at least one match" - [pred-form] - `(rep-impl '~(res pred-form) ~pred-form)) - -(defmacro + - "Returns a regex op that matches one or more values matching - pred. Produces a vector of matches" - [pred-form] - `(rep+impl '~(res pred-form) ~pred-form)) - -(defmacro ? - "Returns a regex op that matches zero or one value matching - pred. Produces a single value (not a collection) if matched." - [pred-form] - `(maybe-impl ~pred-form '~pred-form)) - -(defmacro alt - "Takes key+pred pairs, e.g. - - (s/alt :even even? :small #(< % 42)) - - Returns a regex op that returns a map entry containing the key of the - first matching pred and the corresponding value. Thus the - 'key' and 'val' functions can be used to refer generically to the - components of the tagged return" - [& key-pred-forms] - (let [pairs (partition 2 key-pred-forms) - keys (mapv first pairs) - pred-forms (mapv second pairs) - pf (mapv res pred-forms)] - (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords") - `(alt-impl ~keys ~pred-forms '~pf))) - -(defmacro cat - "Takes key+pred pairs, e.g. - - (s/cat :e even? :o odd?) - - Returns a regex op that matches (all) values in sequence, returning a map - containing the keys of each pred and the corresponding value." - [& key-pred-forms] - (let [pairs (partition 2 key-pred-forms) - keys (mapv first pairs) - pred-forms (mapv second pairs) - pf (mapv res pred-forms)] - ;;(prn key-pred-forms) - (c/assert (c/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords") - `(cat-impl ~keys ~pred-forms '~pf))) - -(defmacro & - "takes a regex op re, and predicates. Returns a regex-op that consumes - input as per re but subjects the resulting value to the - conjunction of the predicates, and any conforming they might perform." - [re & preds] - (let [pv (vec preds)] - `(amp-impl ~re ~pv '~(mapv res pv)))) - -(defmacro conformer - "takes a predicate function with the semantics of conform i.e. it should return either a - (possibly converted) value or :clojure.spec/invalid, and returns a - spec that uses it as a predicate/conformer. Optionally takes a - second fn that does unform of result of first" - ([f] `(spec-impl '(conformer ~(res f)) ~f nil true)) - ([f unf] `(spec-impl '(conformer ~(res f) ~(res unf)) ~f nil true ~unf))) - -(defmacro fspec - "takes :args :ret and (optional) :fn kwargs whose values are preds - and returns a spec whose conform/explain take a fn and validates it - using generative testing. The conformed value is always the fn itself. - - See 'fdef' for a single operation that creates an fspec and - registers it, as well as a full description of :args, :ret and :fn - - fspecs can generate functions that validate the arguments and - fabricate a return value compliant with the :ret spec, ignoring - the :fn spec if present. - - Optionally takes :gen generator-fn, which must be a fn of no args - that returns a test.check generator." - - [& {:keys [args ret fn gen]}] - `(fspec-impl (spec ~args) '~(res args) - (spec ~ret) '~(res ret) - (spec ~fn) '~(res fn) ~gen)) - -(defmacro tuple - "takes one or more preds and returns a spec for a tuple, a vector - where each element conforms to the corresponding pred. Each element - will be referred to in paths using its ordinal." - [& preds] - (c/assert (not (empty? preds))) - `(tuple-impl '~(mapv res preds) ~(vec preds))) - -(defn- macroexpand-check - [v args] - (let [fn-spec (get-spec v)] - (when-let [arg-spec (:args fn-spec)] - (when (invalid? (conform arg-spec args)) - (let [ed (assoc (explain-data* arg-spec [:args] - (if-let [name (spec-name arg-spec)] [name] []) [] args) - ::args args)] - (throw (ex-info - (str - "Call to " (->sym v) " did not conform to spec:\n" - (with-out-str (explain-out ed))) - ed))))))) - -(defmacro fdef - "Takes a symbol naming a function, and one or more of the following: - - :args A regex spec for the function arguments as they were a list to be - passed to apply - in this way, a single spec can handle functions with - multiple arities - :ret A spec for the function's return value - :fn A spec of the relationship between args and ret - the - value passed is {:args conformed-args :ret conformed-ret} and is - expected to contain predicates that relate those values - - Qualifies fn-sym with resolve, or using *ns* if no resolution found. - Registers an fspec in the global registry, where it can be retrieved - by calling get-spec with the var or fully-qualified symbol. - - Once registered, function specs are included in doc, checked by - instrument, tested by the runner clojure.spec.test/check, and (if - a macro) used to explain errors during macroexpansion. - - Note that :fn specs require the presence of :args and :ret specs to - conform values, and so :fn specs will be ignored if :args or :ret - are missing. - - Returns the qualified fn-sym. - - For example, to register function specs for the symbol function: - - (s/fdef clojure.core/symbol - :args (s/alt :separate (s/cat :ns string? :n string?) - :str string? - :sym symbol?) - :ret symbol?)" - [fn-sym & specs] - `(clojure.spec/def ~fn-sym (clojure.spec/fspec ~@specs))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- recur-limit? [rmap id path k] - (c/and (> (get rmap id) (::recursion-limit rmap)) - (contains? (set path) k))) - -(defn- inck [m k] - (assoc m k (inc (c/or (get m k) 0)))) - -(defn- dt - ([pred x form] (dt pred x form nil)) - ([pred x form cpred?] - (if pred - (if-let [spec (the-spec pred)] - (conform spec x) - (if (ifn? pred) - (if cpred? - (pred x) - (if (pred x) x ::invalid)) - (throw (Exception. (str (pr-str form) " is not a fn, expected predicate fn"))))) - x))) - -(defn valid? - "Helper function that returns true when x is valid for spec." - ([spec x] - (let [spec (specize spec)] - (not (invalid? (conform* spec x))))) - ([spec x form] - (let [spec (specize spec form)] - (not (invalid? (conform* spec x)))))) - -(defn- pvalid? - "internal helper function that returns true when x is valid for spec." - ([pred x] - (not (invalid? (dt pred x ::unknown)))) - ([pred x form] - (not (invalid? (dt pred x form))))) - -(defn- explain-1 [form pred path via in v] - ;;(prn {:form form :pred pred :path path :in in :v v}) - (let [pred (maybe-spec pred)] - (if (spec? pred) - (explain* pred path (if-let [name (spec-name pred)] (conj via name) via) in v) - [{:path path :pred (abbrev form) :val v :via via :in in}]))) - -(defn ^:skip-wiki map-spec-impl - "Do not call this directly, use 'spec' with a map argument" - [{:keys [req-un opt-un keys-pred pred-exprs opt-keys req-specs req req-keys opt-specs pred-forms opt gfn] - :as argm}] - (let [k->s (zipmap (concat req-keys opt-keys) (concat req-specs opt-specs)) - keys->specnames #(c/or (k->s %) %) - id (java.util.UUID/randomUUID)] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ m] - (if (keys-pred m) - (let [reg (registry)] - (loop [ret m, [[k v] & ks :as keys] m] - (if keys - (let [sname (keys->specnames k)] - (if-let [s (get reg sname)] - (let [cv (conform s v)] - (if (invalid? cv) - ::invalid - (recur (if (identical? cv v) ret (assoc ret k cv)) - ks))) - (recur ret ks))) - ret))) - ::invalid)) - (unform* [_ m] - (let [reg (registry)] - (loop [ret m, [k & ks :as keys] (c/keys m)] - (if keys - (if (contains? reg (keys->specnames k)) - (let [cv (get m k) - v (unform (keys->specnames k) cv)] - (recur (if (identical? cv v) ret (assoc ret k v)) - ks)) - (recur ret ks)) - ret)))) - (explain* [_ path via in x] - (if-not (map? x) - [{:path path :pred 'map? :val x :via via :in in}] - (let [reg (registry)] - (apply concat - (when-let [probs (->> (map (fn [pred form] (when-not (pred x) (abbrev form))) - pred-exprs pred-forms) - (keep identity) - seq)] - (map - #(identity {:path path :pred % :val x :via via :in in}) - probs)) - (map (fn [[k v]] - (when-not (c/or (not (contains? reg (keys->specnames k))) - (pvalid? (keys->specnames k) v k)) - (explain-1 (keys->specnames k) (keys->specnames k) (conj path k) via (conj in k) v))) - (seq x)))))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (let [rmap (inck rmap id) - gen (fn [k s] (gensub s overrides (conj path k) rmap k)) - ogen (fn [k s] - (when-not (recur-limit? rmap id path k) - [k (gen/delay (gensub s overrides (conj path k) rmap k))])) - req-gens (map gen req-keys req-specs) - opt-gens (remove nil? (map ogen opt-keys opt-specs))] - (when (every? identity (concat req-gens opt-gens)) - (let [reqs (zipmap req-keys req-gens) - opts (into {} opt-gens)] - (gen/bind (gen/choose 0 (count opts)) - #(let [args (concat (seq reqs) (when (seq opts) (shuffle (seq opts))))] - (->> args - (take (c/+ % (count reqs))) - (apply concat) - (apply gen/hash-map))))))))) - (with-gen* [_ gfn] (map-spec-impl (assoc argm :gfn gfn))) - (describe* [_] (cons `keys - (cond-> [] - req (conj :req req) - opt (conj :opt opt) - req-un (conj :req-un req-un) - opt-un (conj :opt-un opt-un))))))) - - - - -(defn ^:skip-wiki spec-impl - "Do not call this directly, use 'spec'" - ([form pred gfn cpred?] (spec-impl form pred gfn cpred? nil)) - ([form pred gfn cpred? unc] - (cond - (spec? pred) (cond-> pred gfn (with-gen gfn)) - (regex? pred) (regex-spec-impl pred gfn) - (ident? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) - :else - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] (let [ret (pred x)] - (if cpred? - ret - (if ret x ::invalid)))) - (unform* [_ x] (if cpred? - (if unc - (unc x) - (throw (IllegalStateException. "no unform fn for conformer"))) - x)) - (explain* [_ path via in x] - (when (invalid? (dt pred x form cpred?)) - [{:path path :pred (abbrev form) :val x :via via :in in}])) - (gen* [_ _ _ _] (if gfn - (gfn) - (gen/gen-for-pred pred))) - (with-gen* [_ gfn] (spec-impl form pred gfn cpred? unc)) - (describe* [_] form))))) - -(defn ^:skip-wiki multi-spec-impl - "Do not call this directly, use 'multi-spec'" - ([form mmvar retag] (multi-spec-impl form mmvar retag nil)) - ([form mmvar retag gfn] - (let [id (java.util.UUID/randomUUID) - predx #(let [^clojure.lang.MultiFn mm @mmvar] - (c/and (.getMethod mm ((.dispatchFn mm) %)) - (mm %))) - dval #((.dispatchFn ^clojure.lang.MultiFn @mmvar) %) - tag (if (keyword? retag) - #(assoc %1 retag %2) - retag)] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] (if-let [pred (predx x)] - (dt pred x form) - ::invalid)) - (unform* [_ x] (if-let [pred (predx x)] - (unform pred x) - (throw (IllegalStateException. (str "No method of: " form " for dispatch value: " (dval x)))))) - (explain* [_ path via in x] - (let [dv (dval x) - path (conj path dv)] - (if-let [pred (predx x)] - (explain-1 form pred path via in x) - [{:path path :pred (abbrev form) :val x :reason "no method" :via via :in in}]))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (let [gen (fn [[k f]] - (let [p (f nil)] - (let [rmap (inck rmap id)] - (when-not (recur-limit? rmap id path k) - (gen/delay - (gen/fmap - #(tag % k) - (gensub p overrides (conj path k) rmap (list 'method form k)))))))) - gs (->> (methods @mmvar) - (remove (fn [[k]] (invalid? k))) - (map gen) - (remove nil?))] - (when (every? identity gs) - (gen/one-of gs))))) - (with-gen* [_ gfn] (multi-spec-impl form mmvar retag gfn)) - (describe* [_] `(multi-spec ~form ~retag)))))) - -(defn ^:skip-wiki tuple-impl - "Do not call this directly, use 'tuple'" - ([forms preds] (tuple-impl forms preds nil)) - ([forms preds gfn] - (let [specs (delay (mapv specize preds forms)) - cnt (count preds)] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] - (let [specs @specs] - (if-not (c/and (vector? x) - (= (count x) cnt)) - ::invalid - (loop [ret x, i 0] - (if (= i cnt) - ret - (let [v (x i) - cv (conform* (specs i) v)] - (if (invalid? cv) - ::invalid - (recur (if (identical? cv v) ret (assoc ret i cv)) - (inc i))))))))) - (unform* [_ x] - (c/assert (c/and (vector? x) - (= (count x) (count preds)))) - (loop [ret x, i 0] - (if (= i (count x)) - ret - (let [cv (x i) - v (unform (preds i) cv)] - (recur (if (identical? cv v) ret (assoc ret i v)) - (inc i)))))) - (explain* [_ path via in x] - (cond - (not (vector? x)) - [{:path path :pred 'vector? :val x :via via :in in}] - - (not= (count x) (count preds)) - [{:path path :pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}] - - :else - (apply concat - (map (fn [i form pred] - (let [v (x i)] - (when-not (pvalid? pred v) - (explain-1 form pred (conj path i) via (conj in i) v)))) - (range (count preds)) forms preds)))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (let [gen (fn [i p f] - (gensub p overrides (conj path i) rmap f)) - gs (map gen (range (count preds)) preds forms)] - (when (every? identity gs) - (apply gen/tuple gs))))) - (with-gen* [_ gfn] (tuple-impl forms preds gfn)) - (describe* [_] `(tuple ~@forms)))))) - -(defn- tagged-ret [tag ret] - (clojure.lang.MapEntry. tag ret)) - -(defn ^:skip-wiki or-spec-impl - "Do not call this directly, use 'or'" - [keys forms preds gfn] - (let [id (java.util.UUID/randomUUID) - kps (zipmap keys preds) - specs (delay (mapv specize preds forms)) - cform (case (count preds) - 2 (fn [x] - (let [specs @specs - ret (conform* (specs 0) x)] - (if (invalid? ret) - (let [ret (conform* (specs 1) x)] - (if (invalid? ret) - ::invalid - (tagged-ret (keys 1) ret))) - (tagged-ret (keys 0) ret)))) - 3 (fn [x] - (let [specs @specs - ret (conform* (specs 0) x)] - (if (invalid? ret) - (let [ret (conform* (specs 1) x)] - (if (invalid? ret) - (let [ret (conform* (specs 2) x)] - (if (invalid? ret) - ::invalid - (tagged-ret (keys 2) ret))) - (tagged-ret (keys 1) ret))) - (tagged-ret (keys 0) ret)))) - (fn [x] - (let [specs @specs] - (loop [i 0] - (if (< i (count specs)) - (let [spec (specs i)] - (let [ret (conform* spec x)] - (if (invalid? ret) - (recur (inc i)) - (tagged-ret (keys i) ret)))) - ::invalid)))))] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] (cform x)) - (unform* [_ [k x]] (unform (kps k) x)) - (explain* [this path via in x] - (when-not (pvalid? this x) - (apply concat - (map (fn [k form pred] - (when-not (pvalid? pred x) - (explain-1 form pred (conj path k) via in x))) - keys forms preds)))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (let [gen (fn [k p f] - (let [rmap (inck rmap id)] - (when-not (recur-limit? rmap id path k) - (gen/delay - (gensub p overrides (conj path k) rmap f))))) - gs (remove nil? (map gen keys preds forms))] - (when-not (empty? gs) - (gen/one-of gs))))) - (with-gen* [_ gfn] (or-spec-impl keys forms preds gfn)) - (describe* [_] `(or ~@(mapcat vector keys forms)))))) - -(defn- and-preds [x preds forms] - (loop [ret x - [pred & preds] preds - [form & forms] forms] - (if pred - (let [nret (dt pred ret form)] - (if (invalid? nret) - ::invalid - ;;propagate conformed values - (recur nret preds forms))) - ret))) - -(defn- explain-pred-list - [forms preds path via in x] - (loop [ret x - [form & forms] forms - [pred & preds] preds] - (when pred - (let [nret (dt pred ret form)] - (if (invalid? nret) - (explain-1 form pred path via in ret) - (recur nret forms preds)))))) - -(defn ^:skip-wiki and-spec-impl - "Do not call this directly, use 'and'" - [forms preds gfn] - (let [specs (delay (mapv specize preds forms)) - cform - (case (count preds) - 2 (fn [x] - (let [specs @specs - ret (conform* (specs 0) x)] - (if (invalid? ret) - ::invalid - (conform* (specs 1) ret)))) - 3 (fn [x] - (let [specs @specs - ret (conform* (specs 0) x)] - (if (invalid? ret) - ::invalid - (let [ret (conform* (specs 1) ret)] - (if (invalid? ret) - ::invalid - (conform* (specs 2) ret)))))) - (fn [x] - (let [specs @specs] - (loop [ret x i 0] - (if (< i (count specs)) - (let [nret (conform* (specs i) ret)] - (if (invalid? nret) - ::invalid - ;;propagate conformed values - (recur nret (inc i)))) - ret)))))] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] (cform x)) - (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) - (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) - (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) - (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) - (describe* [_] `(and ~@forms))))) - -(defn ^:skip-wiki merge-spec-impl - "Do not call this directly, use 'merge'" - [forms preds gfn] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] (let [ms (map #(dt %1 x %2) preds forms)] - (if (some invalid? ms) - ::invalid - (apply c/merge ms)))) - (unform* [_ x] (apply c/merge (map #(unform % x) (reverse preds)))) - (explain* [_ path via in x] - (apply concat - (map #(explain-1 %1 %2 path via in x) - forms preds))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (gen/fmap - #(apply c/merge %) - (apply gen/tuple (map #(gensub %1 overrides path rmap %2) - preds forms))))) - (with-gen* [_ gfn] (merge-spec-impl forms preds gfn)) - (describe* [_] `(merge ~@forms)))) - -(defn- coll-prob [x kfn kform distinct count min-count max-count - path via in] - (let [pred (c/or kfn coll?) - kform (c/or kform `coll?)] - (cond - (not (pvalid? pred x)) - (explain-1 kform pred path via in x) - - (c/and count (not= count (bounded-count count x))) - [{:path path :pred `(= ~count (c/count ~'%)) :val x :via via :in in}] - - (c/and (c/or min-count max-count) - (not (<= (c/or min-count 0) - (bounded-count (if max-count (inc max-count) min-count) x) - (c/or max-count Integer/MAX_VALUE)))) - [{:path path :pred `(<= ~(c/or min-count 0) (c/count ~'%) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}] - - (c/and distinct (not (empty? x)) (not (apply distinct? x))) - [{:path path :pred 'distinct? :val x :via via :in in}]))) - -(defn ^:skip-wiki every-impl - "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" - ([form pred opts] (every-impl form pred opts nil)) - ([form pred {gen-into :into - describe-form ::describe - :keys [kind ::kind-form count max-count min-count distinct gen-max ::kfn ::cpred - conform-keys ::conform-all] - :or {gen-max 20} - :as opts} - gfn] - (let [conform-into gen-into - spec (delay (specize pred)) - check? #(valid? @spec %) - kfn (c/or kfn (fn [i v] i)) - addcv (fn [ret i v cv] (conj ret cv)) - cfns (fn [x] - ;;returns a tuple of [init add complete] fns - (cond - (c/and (vector? x) (c/or (not conform-into) (vector? conform-into))) - [identity - (fn [ret i v cv] - (if (identical? v cv) - ret - (assoc ret i cv))) - identity] - - (c/and (map? x) (c/or (c/and kind (not conform-into)) (map? conform-into))) - [(if conform-keys empty identity) - (fn [ret i v cv] - (if (c/and (identical? v cv) (not conform-keys)) - ret - (assoc ret (nth (if conform-keys cv v) 0) (nth cv 1)))) - identity] - - (c/or (list? conform-into) (seq? conform-into) (c/and (not conform-into) (c/or (list? x) (seq? x)))) - [(constantly ()) addcv reverse] - - :else [#(empty (c/or conform-into %)) addcv identity]))] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] - (let [spec @spec] - (cond - (not (cpred x)) ::invalid - - conform-all - (let [[init add complete] (cfns x)] - (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] - (if vseq - (let [cv (conform* spec v)] - (if (invalid? cv) - ::invalid - (recur (add ret i v cv) (inc i) vs))) - (complete ret)))) - - - :else - (if (indexed? x) - (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] - (loop [i 0] - (if (>= i (c/count x)) - x - (if (valid? spec (nth x i)) - (recur (c/+ i step)) - ::invalid)))) - (let [limit *coll-check-limit*] - (loop [i 0 [v & vs :as vseq] (seq x)] - (cond - (c/or (nil? vseq) (= i limit)) x - (valid? spec v) (recur (inc i) vs) - :else ::invalid))))))) - (unform* [_ x] x) - (explain* [_ path via in x] - (c/or (coll-prob x kind kind-form distinct count min-count max-count - path via in) - (apply concat - ((if conform-all identity (partial take *coll-error-limit*)) - (keep identity - (map (fn [i v] - (let [k (kfn i v)] - (when-not (check? v) - (let [prob (explain-1 form pred path via (conj in k) v)] - prob)))) - (range) x)))))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (let [pgen (gensub pred overrides path rmap form)] - (gen/bind - (cond - gen-into (gen/return (empty gen-into)) - kind (gen/fmap #(if (empty? %) % (empty %)) - (gensub kind overrides path rmap form)) - :else (gen/return [])) - (fn [init] - (gen/fmap - #(if (vector? init) % (into init %)) - (cond - distinct - (if count - (gen/vector-distinct pgen {:num-elements count :max-tries 100}) - (gen/vector-distinct pgen {:min-elements (c/or min-count 0) - :max-elements (c/or max-count (max gen-max (c/* 2 (c/or min-count 0)))) - :max-tries 100})) - - count - (gen/vector pgen count) - - (c/or min-count max-count) - (gen/vector pgen (c/or min-count 0) (c/or max-count (max gen-max (c/* 2 (c/or min-count 0))))) - - :else - (gen/vector pgen 0 gen-max)))))))) - - (with-gen* [_ gfn] (every-impl form pred opts gfn)) - (describe* [_] (c/or describe-form `(every ~(res form) ~@(mapcat identity opts)))))))) - -;;;;;;;;;;;;;;;;;;;;;;; regex ;;;;;;;;;;;;;;;;;;; -;;See: -;; http://matt.might.net/articles/implementation-of-regular-expression-matching-in-scheme-with-derivatives/ -;; http://www.ccs.neu.edu/home/turon/re-deriv.pdf - -;;ctors -(defn- accept [x] {::op ::accept :ret x}) - -(defn- accept? [{:keys [::op]}] - (= ::accept op)) - -(defn- pcat* [{[p1 & pr :as ps] :ps, [k1 & kr :as ks] :ks, [f1 & fr :as forms] :forms, ret :ret, rep+ :rep+}] - (when (every? identity ps) - (if (accept? p1) - (let [rp (:ret p1) - ret (conj ret (if ks {k1 rp} rp))] - (if pr - (pcat* {:ps pr :ks kr :forms fr :ret ret}) - (accept ret))) - {::op ::pcat, :ps ps, :ret ret, :ks ks, :forms forms :rep+ rep+}))) - -(defn- pcat [& ps] (pcat* {:ps ps :ret []})) - -(defn ^:skip-wiki cat-impl - "Do not call this directly, use 'cat'" - [ks ps forms] - (pcat* {:ks ks, :ps ps, :forms forms, :ret {}})) - -(defn- rep* [p1 p2 ret splice form] - (when p1 - (let [r {::op ::rep, :p2 p2, :splice splice, :forms form :id (java.util.UUID/randomUUID)}] - (if (accept? p1) - (assoc r :p1 p2 :ret (conj ret (:ret p1))) - (assoc r :p1 p1, :ret ret))))) - -(defn ^:skip-wiki rep-impl - "Do not call this directly, use '*'" - [form p] (rep* p p [] false form)) - -(defn ^:skip-wiki rep+impl - "Do not call this directly, use '+'" - [form p] - (pcat* {:ps [p (rep* p p [] true form)] :forms `[~form (* ~form)] :ret [] :rep+ form})) - -(defn ^:skip-wiki amp-impl - "Do not call this directly, use '&'" - [re preds pred-forms] - {::op ::amp :p1 re :ps preds :forms pred-forms}) - -(defn- filter-alt [ps ks forms f] - (if (c/or ks forms) - (let [pks (->> (map vector ps - (c/or (seq ks) (repeat nil)) - (c/or (seq forms) (repeat nil))) - (filter #(-> % first f)))] - [(seq (map first pks)) (when ks (seq (map second pks))) (when forms (seq (map #(nth % 2) pks)))]) - [(seq (filter f ps)) ks forms])) - -(defn- alt* [ps ks forms] - (let [[[p1 & pr :as ps] [k1 :as ks] forms] (filter-alt ps ks forms identity)] - (when ps - (let [ret {::op ::alt, :ps ps, :ks ks :forms forms}] - (if (nil? pr) - (if k1 - (if (accept? p1) - (accept (tagged-ret k1 (:ret p1))) - ret) - p1) - ret))))) - -(defn- alts [& ps] (alt* ps nil nil)) -(defn- alt2 [p1 p2] (if (c/and p1 p2) (alts p1 p2) (c/or p1 p2))) - -(defn ^:skip-wiki alt-impl - "Do not call this directly, use 'alt'" - [ks ps forms] (assoc (alt* ps ks forms) :id (java.util.UUID/randomUUID))) - -(defn ^:skip-wiki maybe-impl - "Do not call this directly, use '?'" - [p form] (assoc (alt* [p (accept ::nil)] nil [form ::nil]) :maybe form)) - -(defn- noret? [p1 pret] - (c/or (= pret ::nil) - (c/and (#{::rep ::pcat} (::op (reg-resolve! p1))) ;;hrm, shouldn't know these - (empty? pret)) - nil)) - -(declare preturn) - -(defn- accept-nil? [p] - (let [{:keys [::op ps p1 p2 forms] :as p} (reg-resolve! p)] - (case op - ::accept true - nil nil - ::amp (c/and (accept-nil? p1) - (c/or (noret? p1 (preturn p1)) - (let [ret (-> (preturn p1) (and-preds ps (next forms)))] - (not (invalid? ret))))) - ::rep (c/or (identical? p1 p2) (accept-nil? p1)) - ::pcat (every? accept-nil? ps) - ::alt (c/some accept-nil? ps)))) - -(declare add-ret) - -(defn- preturn [p] - (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms] :as p} (reg-resolve! p)] - (case op - ::accept ret - nil nil - ::amp (let [pret (preturn p1)] - (if (noret? p1 pret) - ::nil - (and-preds pret ps forms))) - ::rep (add-ret p1 ret k) - ::pcat (add-ret p0 ret k) - ::alt (let [[[p0] [k0]] (filter-alt ps ks forms accept-nil?) - r (if (nil? p0) ::nil (preturn p0))] - (if k0 (tagged-ret k0 r) r))))) - -(defn- op-unform [p x] - ;;(prn {:p p :x x}) - (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms rep+ maybe] :as p} (reg-resolve! p) - kps (zipmap ks ps)] - (case op - ::accept [ret] - nil [(unform p x)] - ::amp (let [px (reduce #(unform %2 %1) x (reverse ps))] - (op-unform p1 px)) - ::rep (mapcat #(op-unform p1 %) x) - ::pcat (if rep+ - (mapcat #(op-unform p0 %) x) - (mapcat (fn [k] - (when (contains? x k) - (op-unform (kps k) (get x k)))) - ks)) - ::alt (if maybe - [(unform p0 x)] - (let [[k v] x] - (op-unform (kps k) v)))))) - -(defn- add-ret [p r k] - (let [{:keys [::op ps splice] :as p} (reg-resolve! p) - prop #(let [ret (preturn p)] - (if (empty? ret) r ((if splice into conj) r (if k {k ret} ret))))] - (case op - nil r - (::alt ::accept ::amp) - (let [ret (preturn p)] - ;;(prn {:ret ret}) - (if (= ret ::nil) r (conj r (if k {k ret} ret)))) - - (::rep ::pcat) (prop)))) - -(defn- deriv - [p x] - (let [{[p0 & pr :as ps] :ps, [k0 & kr :as ks] :ks, :keys [::op p1 p2 ret splice forms] :as p} (reg-resolve! p)] - (when p - (case op - ::accept nil - nil (let [ret (dt p x p)] - (when-not (invalid? ret) (accept ret))) - ::amp (when-let [p1 (deriv p1 x)] - (if (= ::accept (::op p1)) - (let [ret (-> (preturn p1) (and-preds ps (next forms)))] - (when-not (invalid? ret) - (accept ret))) - (amp-impl p1 ps forms))) - ::pcat (alt2 (pcat* {:ps (cons (deriv p0 x) pr), :ks ks, :forms forms, :ret ret}) - (when (accept-nil? p0) (deriv (pcat* {:ps pr, :ks kr, :forms (next forms), :ret (add-ret p0 ret k0)}) x))) - ::alt (alt* (map #(deriv % x) ps) ks forms) - ::rep (alt2 (rep* (deriv p1 x) p2 ret splice forms) - (when (accept-nil? p1) (deriv (rep* p2 p2 (add-ret p1 ret nil) splice forms) x))))))) - -(defn- op-describe [p] - (let [{:keys [::op ps ks forms splice p1 rep+ maybe] :as p} (reg-resolve! p)] - ;;(prn {:op op :ks ks :forms forms :p p}) - (when p - (case op - ::accept nil - nil p - ::amp (list* 'clojure.spec/& (op-describe p1) forms) - ::pcat (if rep+ - (list `+ rep+) - (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) forms))) - ::alt (if maybe - (list `? (res maybe)) - (cons `alt (mapcat vector ks forms))) - ::rep (list (if splice `+ `*) forms))))) - -(defn- op-explain [form p path via in input] - ;;(prn {:form form :p p :path path :input input}) - (let [[x :as input] input - {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve! p) - via (if-let [name (spec-name p)] (conj via name) via) - insufficient (fn [path form] - [{:path path - :reason "Insufficient input" - :pred (abbrev form) - :val () - :via via - :in in}])] - (when p - (case op - ::accept nil - nil (if (empty? input) - (insufficient path form) - (explain-1 form p path via in x)) - ::amp (if (empty? input) - (if (accept-nil? p1) - (explain-pred-list forms ps path via in (preturn p1)) - (insufficient path (op-describe p1))) - (if-let [p1 (deriv p1 x)] - (explain-pred-list forms ps path via in (preturn p1)) - (op-explain (op-describe p1) p1 path via in input))) - ::pcat (let [pkfs (map vector - ps - (c/or (seq ks) (repeat nil)) - (c/or (seq forms) (repeat nil))) - [pred k form] (if (= 1 (count pkfs)) - (first pkfs) - (first (remove (fn [[p]] (accept-nil? p)) pkfs))) - path (if k (conj path k) path) - form (c/or form (op-describe pred))] - (if (c/and (empty? input) (not pred)) - (insufficient path form) - (op-explain form pred path via in input))) - ::alt (if (empty? input) - (insufficient path (op-describe p)) - (apply concat - (map (fn [k form pred] - (op-explain (c/or form (op-describe pred)) - pred - (if k (conj path k) path) - via - in - input)) - (c/or (seq ks) (repeat nil)) - (c/or (seq forms) (repeat nil)) - ps))) - ::rep (op-explain (if (identical? p1 p2) - forms - (op-describe p1)) - p1 path via in input))))) - -(defn- re-gen [p overrides path rmap f] - ;;(prn {:op op :ks ks :forms forms}) - (let [origp p - {:keys [::op ps ks p1 p2 forms splice ret id ::gfn] :as p} (reg-resolve! p) - rmap (if id (inck rmap id) rmap) - ggens (fn [ps ks forms] - (let [gen (fn [p k f] - ;;(prn {:k k :path path :rmap rmap :op op :id id}) - (when-not (c/and rmap id k (recur-limit? rmap id path k)) - (if id - (gen/delay (re-gen p overrides (if k (conj path k) path) rmap (c/or f p))) - (re-gen p overrides (if k (conj path k) path) rmap (c/or f p)))))] - (map gen ps (c/or (seq ks) (repeat nil)) (c/or (seq forms) (repeat nil)))))] - (c/or (when-let [gfn (c/or (get overrides (spec-name origp)) - (get overrides (spec-name p) ) - (get overrides path))] - (case op - (:accept nil) (gen/fmap vector (gfn)) - (gfn))) - (when gfn - (gfn)) - (when p - (case op - ::accept (if (= ret ::nil) - (gen/return []) - (gen/return [ret])) - nil (when-let [g (gensub p overrides path rmap f)] - (gen/fmap vector g)) - ::amp (re-gen p1 overrides path rmap (op-describe p1)) - ::pcat (let [gens (ggens ps ks forms)] - (when (every? identity gens) - (apply gen/cat gens))) - ::alt (let [gens (remove nil? (ggens ps ks forms))] - (when-not (empty? gens) - (gen/one-of gens))) - ::rep (if (recur-limit? rmap id [id] id) - (gen/return []) - (when-let [g (re-gen p2 overrides path rmap forms)] - (gen/fmap #(apply concat %) - (gen/vector g))))))))) - -(defn- re-conform [p [x & xs :as data]] - ;;(prn {:p p :x x :xs xs}) - (if (empty? data) - (if (accept-nil? p) - (let [ret (preturn p)] - (if (= ret ::nil) - nil - ret)) - ::invalid) - (if-let [dp (deriv p x)] - (recur dp xs) - ::invalid))) - -(defn- re-explain [path via in re input] - (loop [p re [x & xs :as data] input i 0] - ;;(prn {:p p :x x :xs xs :re re}) (prn) - (if (empty? data) - (if (accept-nil? p) - nil ;;success - (op-explain (op-describe p) p path via in nil)) - (if-let [dp (deriv p x)] - (recur dp xs (inc i)) - (if (accept? p) - (if (= (::op p) ::pcat) - (op-explain (op-describe p) p path via (conj in i) (seq data)) - [{:path path - :reason "Extra input" - :pred (abbrev (op-describe re)) - :val data - :via via - :in (conj in i)}]) - (c/or (op-explain (op-describe p) p path via (conj in i) (seq data)) - [{:path path - :reason "Extra input" - :pred (abbrev (op-describe p)) - :val data - :via via - :in (conj in i)}])))))) - -(defn ^:skip-wiki regex-spec-impl - "Do not call this directly, use 'spec' with a regex op argument" - [re gfn] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] - (if (c/or (nil? x) (coll? x)) - (re-conform re (seq x)) - ::invalid)) - (unform* [_ x] (op-unform re x)) - (explain* [_ path via in x] - (if (c/or (nil? x) (coll? x)) - (re-explain path via in re (seq x)) - [{:path path :pred (abbrev (op-describe re)) :val x :via via :in in}])) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (re-gen re overrides path rmap (op-describe re)))) - (with-gen* [_ gfn] (regex-spec-impl re gfn)) - (describe* [_] (op-describe re)))) - -;;;;;;;;;;;;;;;;; HOFs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- call-valid? - [f specs args] - (let [cargs (conform (:args specs) args)] - (when-not (invalid? cargs) - (let [ret (apply f args) - cret (conform (:ret specs) ret)] - (c/and (not (invalid? cret)) - (if (:fn specs) - (pvalid? (:fn specs) {:args cargs :ret cret}) - true)))))) - -(defn- validate-fn - "returns f if valid, else smallest" - [f specs iters] - (let [g (gen (:args specs)) - prop (gen/for-all* [g] #(call-valid? f specs %))] - (let [ret (gen/quick-check iters prop)] - (if-let [[smallest] (-> ret :shrunk :smallest)] - smallest - f)))) - -(defn ^:skip-wiki fspec-impl - "Do not call this directly, use 'fspec'" - [argspec aform retspec rform fnspec fform gfn] - (let [specs {:args argspec :ret retspec :fn fnspec}] - (reify - clojure.lang.ILookup - (valAt [this k] (get specs k)) - (valAt [_ k not-found] (get specs k not-found)) - - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [this f] (if argspec - (if (ifn? f) - (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) - ::invalid) - (throw (Exception. (str "Can't conform fspec without args spec: " (pr-str (describe this))))))) - (unform* [_ f] f) - (explain* [_ path via in f] - (if (ifn? f) - (let [args (validate-fn f specs 100)] - (if (identical? f args) ;;hrm, we might not be able to reproduce - nil - (let [ret (try (apply f args) (catch Throwable t t))] - (if (instance? Throwable ret) - ;;TODO add exception data - [{:path path :pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via :in in}] - - (let [cret (dt retspec ret rform)] - (if (invalid? cret) - (explain-1 rform retspec (conj path :ret) via in ret) - (when fnspec - (let [cargs (conform argspec args)] - (explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret}))))))))) - [{:path path :pred 'ifn? :val f :via via :in in}])) - (gen* [_ overrides _ _] (if gfn - (gfn) - (gen/return - (fn [& args] - (c/assert (pvalid? argspec args) (with-out-str (explain argspec args))) - (gen/generate (gen retspec overrides)))))) - (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) - (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; non-primitives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(clojure.spec/def ::kvs->map (conformer #(zipmap (map ::k %) (map ::v %)) #(map (fn [[k v]] {::k k ::v v}) %))) - -(defmacro keys* - "takes the same arguments as spec/keys and returns a regex op that matches sequences of key/values, - converts them into a map, and conforms that map with a corresponding - spec/keys call: - - user=> (s/conform (s/keys :req-un [::a ::c]) {:a 1 :c 2}) - {:a 1, :c 2} - user=> (s/conform (s/keys* :req-un [::a ::c]) [:a 1 :c 2]) - {:a 1, :c 2} - - the resulting regex op can be composed into a larger regex: - - user=> (s/conform (s/cat :i1 integer? :m (s/keys* :req-un [::a ::c]) :i2 integer?) [42 :a 1 :c 2 :d 4 99]) - {:i1 42, :m {:a 1, :c 2, :d 4}, :i2 99}" - [& kspecs] - `(let [mspec# (keys ~@kspecs)] - (with-gen (clojure.spec/& (* (cat ::k keyword? ::v any?)) ::kvs->map mspec#) - (fn [] (gen/fmap (fn [m#] (apply concat m#)) (gen mspec#)))))) - -(defn ^:skip-wiki nonconforming - "takes a spec and returns a spec that has the same properties except - 'conform' returns the original (not the conformed) value. Note, will specize regex ops." - [spec] - (let [spec (delay (specize spec))] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] (let [ret (conform* @spec x)] - (if (invalid? ret) - ::invalid - x))) - (unform* [_ x] x) - (explain* [_ path via in x] (explain* @spec path via in x)) - (gen* [_ overrides path rmap] (gen* @spec overrides path rmap)) - (with-gen* [_ gfn] (nonconforming (with-gen* @spec gfn))) - (describe* [_] `(nonconforming ~(describe* @spec)))))) - -(defn ^:skip-wiki nilable-impl - "Do not call this directly, use 'nilable'" - [form pred gfn] - (let [spec (delay (specize pred form))] - (reify - Specize - (specize* [s] s) - (specize* [s _] s) - - Spec - (conform* [_ x] (if (nil? x) nil (conform* @spec x))) - (unform* [_ x] (if (nil? x) nil (unform* @spec x))) - (explain* [_ path via in x] - (when-not (c/or (pvalid? @spec x) (nil? x)) - (conj - (explain-1 form pred (conj path ::pred) via in x) - {:path (conj path ::nil) :pred 'nil? :val x :via via :in in}))) - (gen* [_ overrides path rmap] - (if gfn - (gfn) - (gen/frequency - [[1 (gen/delay (gen/return nil))] - [9 (gen/delay (gensub pred overrides (conj path ::pred) rmap form))]]))) - (with-gen* [_ gfn] (nilable-impl form pred gfn)) - (describe* [_] `(nilable ~(res form)))))) - -(defmacro nilable - "returns a spec that accepts nil and values satisfying pred" - [pred] - (let [pf (res pred)] - `(nilable-impl '~pf ~pred nil))) - -(defn exercise - "generates a number (default 10) of values compatible with spec and maps conform over them, - returning a sequence of [val conformed-val] tuples. Optionally takes - a generator overrides map as per gen" - ([spec] (exercise spec 10)) - ([spec n] (exercise spec n nil)) - ([spec n overrides] - (map #(vector % (conform spec %)) (gen/sample (gen spec overrides) n)))) - -(defn exercise-fn - "exercises the fn named by sym (a symbol) by applying it to - n (default 10) generated samples of its args spec. When fspec is - supplied its arg spec is used, and sym-or-f can be a fn. Returns a - sequence of tuples of [args ret]. " - ([sym] (exercise-fn sym 10)) - ([sym n] (exercise-fn sym n (get-spec sym))) - ([sym-or-f n fspec] - (let [f (if (symbol? sym-or-f) (resolve sym-or-f) sym-or-f)] - (for [args (gen/sample (gen (:args fspec)) n)] - [args (apply f args)])))) - -(defn inst-in-range? - "Return true if inst at or after start and before end" - [start end inst] - (c/and (inst? inst) - (let [t (inst-ms inst)] - (c/and (<= (inst-ms start) t) (< t (inst-ms end)))))) - -(defmacro inst-in - "Returns a spec that validates insts in the range from start -(inclusive) to end (exclusive)." - [start end] - `(let [st# (inst-ms ~start) - et# (inst-ms ~end) - mkdate# (fn [d#] (java.util.Date. ^{:tag ~'long} d#))] - (spec (and inst? #(inst-in-range? ~start ~end %)) - :gen (fn [] - (gen/fmap mkdate# - (gen/large-integer* {:min st# :max et#})))))) - -(defn int-in-range? - "Return true if start <= val and val < end" - [start end val] - (c/and int? (<= start val) (< val end))) - -(defmacro int-in - "Returns a spec that validates ints in the range from start -(inclusive) to end (exclusive)." - [start end] - `(spec (and int? #(int-in-range? ~start ~end %)) - :gen #(gen/large-integer* {:min ~start :max (dec ~end)}))) - -(defmacro double-in - "Specs a 64-bit floating point number. Options: - - :infinite? - whether +/- infinity allowed (default true) - :NaN? - whether NaN allowed (default true) - :min - minimum value (inclusive, default none) - :max - maximum value (inclusive, default none)" - [& {:keys [infinite? NaN? min max] - :or {infinite? true NaN? true} - :as m}] - `(spec (and c/double? - ~@(when-not infinite? '[#(not (Double/isInfinite %))]) - ~@(when-not NaN? '[#(not (Double/isNaN %))]) - ~@(when max `[#(<= % ~max)]) - ~@(when min `[#(<= ~min %)])) - :gen #(gen/double* ~m))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; assert ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defonce - ^{:dynamic true - :doc "If true, compiler will enable spec asserts, which are then -subject to runtime control via check-asserts? If false, compiler -will eliminate all spec assert overhead. See 'assert'. - -Initially set to boolean value of clojure.spec.compile-asserts -system property. Defaults to true."} - *compile-asserts* - (not= "false" (System/getProperty "clojure.spec.compile-asserts"))) - -(defn check-asserts? - "Returns the value set by check-asserts." - [] - clojure.lang.RT/checkSpecAsserts) - -(defn check-asserts - "Enable or disable spec asserts that have been compiled -with '*compile-asserts*' true. See 'assert'. - -Initially set to boolean value of clojure.spec.check-asserts -system property. Defaults to false." - [flag] - (set! (. clojure.lang.RT checkSpecAsserts) flag)) - -(defn assert* - "Do not call this directly, use 'assert'." - [spec x] - (if (valid? spec x) - x - (let [ed (c/merge (assoc (explain-data* spec [] [] [] x) - ::failure :assertion-failed))] - (throw (ex-info - (str "Spec assertion failed\n" (with-out-str (explain-out ed))) - ed))))) - -(defmacro assert - "spec-checking assert expression. Returns x if x is valid? according -to spec, else throws an ex-info with explain-data plus ::failure of -:assertion-failed. - -Can be disabled at either compile time or runtime: - -If *compile-asserts* is false at compile time, compiles to x. Defaults -to value of 'clojure.spec.compile-asserts' system property, or true if -not set. - -If (check-asserts?) is false at runtime, always returns x. Defaults to -value of 'clojure.spec.check-asserts' system property, or false if not -set. You can toggle check-asserts? with (check-asserts bool)." - [spec x] - (if *compile-asserts* - `(if clojure.lang.RT/checkSpecAsserts - (assert* ~spec ~x) - ~x) - x)) - - diff --git a/src/clj/clojure/spec/gen.clj b/src/clj/clojure/spec/gen.clj deleted file mode 100644 index 6d8e2388ed..0000000000 --- a/src/clj/clojure/spec/gen.clj +++ /dev/null @@ -1,224 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns clojure.spec.gen - (:refer-clojure :exclude [boolean bytes cat hash-map list map not-empty set vector - char double int keyword symbol string uuid delay])) - -(alias 'c 'clojure.core) - -(defn- dynaload - [s] - (let [ns (namespace s)] - (assert ns) - (require (c/symbol ns)) - (let [v (resolve s)] - (if v - @v - (throw (RuntimeException. (str "Var " s " is not on the classpath"))))))) - -(def ^:private quick-check-ref - (c/delay (dynaload 'clojure.test.check/quick-check))) -(defn quick-check - [& args] - (apply @quick-check-ref args)) - -(def ^:private for-all*-ref - (c/delay (dynaload 'clojure.test.check.properties/for-all*))) -(defn for-all* - "Dynamically loaded clojure.test.check.properties/for-all*." - [& args] - (apply @for-all*-ref args)) - -(let [g? (c/delay (dynaload 'clojure.test.check.generators/generator?)) - g (c/delay (dynaload 'clojure.test.check.generators/generate)) - mkg (c/delay (dynaload 'clojure.test.check.generators/->Generator))] - (defn- generator? - [x] - (@g? x)) - (defn- generator - [gfn] - (@mkg gfn)) - (defn generate - "Generate a single value using generator." - [generator] - (@g generator))) - -(defn ^:skip-wiki delay-impl - [gfnd] - ;;N.B. depends on test.check impl details - (generator (fn [rnd size] - ((:gen @gfnd) rnd size)))) - -(defmacro delay - "given body that returns a generator, returns a - generator that delegates to that, but delays - creation until used." - [& body] - `(delay-impl (c/delay ~@body))) - -(defn gen-for-name - "Dynamically loads test.check generator named s." - [s] - (let [g (dynaload s)] - (if (generator? g) - g - (throw (RuntimeException. (str "Var " s " is not a generator")))))) - -(defmacro ^:skip-wiki lazy-combinator - "Implementation macro, do not call directly." - [s] - (let [fqn (c/symbol "clojure.test.check.generators" (name s)) - doc (str "Lazy loaded version of " fqn)] - `(let [g# (c/delay (dynaload '~fqn))] - (defn ~s - ~doc - [& ~'args] - (apply @g# ~'args))))) - -(defmacro ^:skip-wiki lazy-combinators - "Implementation macro, do not call directly." - [& syms] - `(do - ~@(c/map - (fn [s] (c/list 'lazy-combinator s)) - syms))) - -(lazy-combinators hash-map list map not-empty set vector vector-distinct fmap elements - bind choose fmap one-of such-that tuple sample return - large-integer* double* frequency) - -(defmacro ^:skip-wiki lazy-prim - "Implementation macro, do not call directly." - [s] - (let [fqn (c/symbol "clojure.test.check.generators" (name s)) - doc (str "Fn returning " fqn)] - `(let [g# (c/delay (dynaload '~fqn))] - (defn ~s - ~doc - [& ~'args] - @g#)))) - -(defmacro ^:skip-wiki lazy-prims - "Implementation macro, do not call directly." - [& syms] - `(do - ~@(c/map - (fn [s] (c/list 'lazy-prim s)) - syms))) - -(lazy-prims any any-printable boolean bytes char char-alpha char-alphanumeric char-ascii double - int keyword keyword-ns large-integer ratio simple-type simple-type-printable - string string-ascii string-alphanumeric symbol symbol-ns uuid) - -(defn cat - "Returns a generator of a sequence catenated from results of -gens, each of which should generate something sequential." - [& gens] - (fmap #(apply concat %) - (apply tuple gens))) - -(defn- qualified? [ident] (not (nil? (namespace ident)))) - -(def ^:private - gen-builtins - (c/delay - (let [simple (simple-type-printable)] - {any? (one-of [(return nil) (any-printable)]) - some? (such-that some? (any-printable)) - number? (one-of [(large-integer) (double)]) - integer? (large-integer) - int? (large-integer) - pos-int? (large-integer* {:min 1}) - neg-int? (large-integer* {:max -1}) - nat-int? (large-integer* {:min 0}) - float? (double) - double? (double) - boolean? (boolean) - string? (string-alphanumeric) - ident? (one-of [(keyword-ns) (symbol-ns)]) - simple-ident? (one-of [(keyword) (symbol)]) - qualified-ident? (such-that qualified? (one-of [(keyword-ns) (symbol-ns)])) - keyword? (keyword-ns) - simple-keyword? (keyword) - qualified-keyword? (such-that qualified? (keyword-ns)) - symbol? (symbol-ns) - simple-symbol? (symbol) - qualified-symbol? (such-that qualified? (symbol-ns)) - uuid? (uuid) - uri? (fmap #(java.net.URI/create (str "http://" % ".com")) (uuid)) - bigdec? (fmap #(BigDecimal/valueOf %) - (double* {:infinite? false :NaN? false})) - inst? (fmap #(java.util.Date. %) - (large-integer)) - seqable? (one-of [(return nil) - (list simple) - (vector simple) - (map simple simple) - (set simple) - (string-alphanumeric)]) - indexed? (vector simple) - map? (map simple simple) - vector? (vector simple) - list? (list simple) - seq? (list simple) - char? (char) - set? (set simple) - nil? (return nil) - false? (return false) - true? (return true) - zero? (return 0) - rational? (one-of [(large-integer) (ratio)]) - coll? (one-of [(map simple simple) - (list simple) - (vector simple) - (set simple)]) - empty? (elements [nil '() [] {} #{}]) - associative? (one-of [(map simple simple) (vector simple)]) - sequential? (one-of [(list simple) (vector simple)]) - ratio? (such-that ratio? (ratio)) - bytes? (bytes)}))) - -(defn gen-for-pred - "Given a predicate, returns a built-in generator if one exists." - [pred] - (if (set? pred) - (elements pred) - (get @gen-builtins pred))) - -(comment - (require :reload 'clojure.spec.gen) - (in-ns 'clojure.spec.gen) - - ;; combinators, see call to lazy-combinators above for complete list - (generate (one-of [(gen-for-pred integer?) (gen-for-pred string?)])) - (generate (such-that #(< 10000 %) (gen-for-pred integer?))) - (let [reqs {:a (gen-for-pred number?) - :b (gen-for-pred ratio?)} - opts {:c (gen-for-pred string?)}] - (generate (bind (choose 0 (count opts)) - #(let [args (concat (seq reqs) (shuffle (seq opts)))] - (->> args - (take (+ % (count reqs))) - (mapcat identity) - (apply hash-map)))))) - (generate (cat (list (gen-for-pred string?)) - (list (gen-for-pred ratio?)))) - - ;; load your own generator - (gen-for-name 'clojure.test.check.generators/int) - - ;; failure modes - (gen-for-name 'unqualified) - (gen-for-name 'clojure.core/+) - (gen-for-name 'clojure.core/name-does-not-exist) - (gen-for-name 'ns.does.not.exist/f) - - ) - - diff --git a/src/clj/clojure/spec/test.clj b/src/clj/clojure/spec/test.clj deleted file mode 100644 index 587f441e3c..0000000000 --- a/src/clj/clojure/spec/test.clj +++ /dev/null @@ -1,466 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns clojure.spec.test - (:refer-clojure :exclude [test]) - (:require - [clojure.pprint :as pp] - [clojure.spec :as s] - [clojure.spec.gen :as gen] - [clojure.string :as str])) - -(in-ns 'clojure.spec.test.check) -(in-ns 'clojure.spec.test) -(alias 'stc 'clojure.spec.test.check) - -(defn- throwable? - [x] - (instance? Throwable x)) - -(defn ->sym - [x] - (@#'s/->sym x)) - -(defn- ->var - [s-or-v] - (if (var? s-or-v) - s-or-v - (let [v (and (symbol? s-or-v) (resolve s-or-v))] - (if (var? v) - v - (throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var"))))))) - -(defn- collectionize - [x] - (if (symbol? x) - (list x) - x)) - -(defn enumerate-namespace - "Given a symbol naming an ns, or a collection of such symbols, -returns the set of all symbols naming vars in those nses." - [ns-sym-or-syms] - (into - #{} - (mapcat (fn [ns-sym] - (map - (fn [name-sym] - (symbol (name ns-sym) (name name-sym))) - (keys (ns-interns ns-sym))))) - (collectionize ns-sym-or-syms))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def ^:private ^:dynamic *instrument-enabled* - "if false, instrumented fns call straight through" - true) - -(defn- fn-spec? - "Fn-spec must include at least :args or :ret specs." - [m] - (or (:args m) (:ret m))) - -(defmacro with-instrument-disabled - "Disables instrument's checking of calls, within a scope." - [& body] - `(binding [*instrument-enabled* nil] - ~@body)) - -(defn- interpret-stack-trace-element - "Given the vector-of-syms form of a stacktrace element produced -by e.g. Throwable->map, returns a map form that adds some keys -guessing the original Clojure names. Returns a map with - - :class class name symbol from stack trace - :method method symbol from stack trace - :file filename from stack trace - :line line number from stack trace - :var-scope optional Clojure var symbol scoping fn def - :local-fn optional local Clojure symbol scoping fn def - -For non-Clojure fns, :scope and :local-fn will be absent." - [[cls method file line]] - (let [clojure? (contains? '#{invoke invokeStatic} method) - demunge #(clojure.lang.Compiler/demunge %) - degensym #(str/replace % #"--.*" "") - [ns-sym name-sym local] (when clojure? - (->> (str/split (str cls) #"\$" 3) - (map demunge)))] - (merge {:file file - :line line - :method method - :class cls} - (when (and ns-sym name-sym) - {:var-scope (symbol ns-sym name-sym)}) - (when local - {:local-fn (symbol (degensym local))})))) - -(defn- stacktrace-relevant-to-instrument - "Takes a coll of stack trace elements (as returned by -StackTraceElement->vec) and returns a coll of maps as per -interpret-stack-trace-element that are relevant to a -failure in instrument." - [elems] - (let [plumbing? (fn [{:keys [var-scope]}] - (contains? '#{clojure.spec.test/spec-checking-fn} var-scope))] - (sequence (comp (map StackTraceElement->vec) - (map interpret-stack-trace-element) - (filter :var-scope) - (drop-while plumbing?)) - elems))) - -(defn- spec-checking-fn - [v f fn-spec] - (let [fn-spec (@#'s/maybe-spec fn-spec) - conform! (fn [v role spec data args] - (let [conformed (s/conform spec data)] - (if (= ::s/invalid conformed) - (let [caller (->> (.getStackTrace (Thread/currentThread)) - stacktrace-relevant-to-instrument - first) - ed (merge (assoc (s/explain-data* spec [role] [] [] data) - ::s/args args - ::s/failure :instrument) - (when caller - {::caller (dissoc caller :class :method)}))] - (throw (ex-info - (str "Call to " v " did not conform to spec:\n" (with-out-str (s/explain-out ed))) - ed))) - conformed)))] - (fn - [& args] - (if *instrument-enabled* - (with-instrument-disabled - (when (:args fn-spec) (conform! v :args (:args fn-spec) args args)) - (binding [*instrument-enabled* true] - (.applyTo ^clojure.lang.IFn f args))) - (.applyTo ^clojure.lang.IFn f args))))) - -(defn- no-fspec - [v spec] - (ex-info (str "Fn at " v " is not spec'ed.") - {:var v :spec spec ::s/failure :no-fspec})) - -(defonce ^:private instrumented-vars (atom {})) - -(defn- instrument-choose-fn - "Helper for instrument." - [f spec sym {over :gen :keys [stub replace]}] - (if (some #{sym} stub) - (-> spec (s/gen over) gen/generate) - (get replace sym f))) - -(defn- instrument-choose-spec - "Helper for instrument" - [spec sym {overrides :spec}] - (get overrides sym spec)) - -(defn- instrument-1 - [s opts] - (when-let [v (resolve s)] - (when-not (-> v meta :macro) - (let [spec (s/get-spec v) - {:keys [raw wrapped]} (get @instrumented-vars v) - current @v - to-wrap (if (= wrapped current) raw current) - ospec (or (instrument-choose-spec spec s opts) - (throw (no-fspec v spec))) - ofn (instrument-choose-fn to-wrap ospec s opts) - checked (spec-checking-fn v ofn ospec)] - (alter-var-root v (constantly checked)) - (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked}) - (->sym v))))) - -(defn- unstrument-1 - [s] - (when-let [v (resolve s)] - (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] - (swap! instrumented-vars dissoc v) - (let [current @v] - (when (= wrapped current) - (alter-var-root v (constantly raw)) - (->sym v)))))) - -(defn- opt-syms - "Returns set of symbols referenced by 'instrument' opts map" - [opts] - (reduce into #{} [(:stub opts) (keys (:replace opts)) (keys (:spec opts))])) - -(defn- fn-spec-name? - [s] - (and (symbol? s) - (not (some-> (resolve s) meta :macro)))) - -(defn instrumentable-syms - "Given an opts map as per instrument, returns the set of syms -that can be instrumented." - ([] (instrumentable-syms nil)) - ([opts] - (assert (every? ident? (keys (:gen opts))) "instrument :gen expects ident keys") - (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) - (keys (:spec opts)) - (:stub opts) - (keys (:replace opts))]))) - -(defn instrument - "Instruments the vars named by sym-or-syms, a symbol or collection -of symbols, or all instrumentable vars if sym-or-syms is not -specified. - -If a var has an :args fn-spec, sets the var's root binding to a -fn that checks arg conformance (throwing an exception on failure) -before delegating to the original fn. - -The opts map can be used to override registered specs, and/or to -replace fn implementations entirely. Opts for symbols not included -in sym-or-syms are ignored. This facilitates sharing a common -options map across many different calls to instrument. - -The opts map may have the following keys: - - :spec a map from var-name symbols to override specs - :stub a set of var-name symbols to be replaced by stubs - :gen a map from spec names to generator overrides - :replace a map from var-name symbols to replacement fns - -:spec overrides registered fn-specs with specs your provide. Use -:spec overrides to provide specs for libraries that do not have -them, or to constrain your own use of a fn to a subset of its -spec'ed contract. - -:stub replaces a fn with a stub that checks :args, then uses the -:ret spec to generate a return value. - -:gen overrides are used only for :stub generation. - -:replace replaces a fn with a fn that checks args conformance, then -invokes the fn you provide, enabling arbitrary stubbing and mocking. - -:spec can be used in combination with :stub or :replace. - -Returns a collection of syms naming the vars instrumented." - ([] (instrument (instrumentable-syms))) - ([sym-or-syms] (instrument sym-or-syms nil)) - ([sym-or-syms opts] - (locking instrumented-vars - (into - [] - (comp (filter (instrumentable-syms opts)) - (distinct) - (map #(instrument-1 % opts)) - (remove nil?)) - (collectionize sym-or-syms))))) - -(defn unstrument - "Undoes instrument on the vars named by sym-or-syms, specified -as in instrument. With no args, unstruments all instrumented vars. -Returns a collection of syms naming the vars unstrumented." - ([] (unstrument (map ->sym (keys @instrumented-vars)))) - ([sym-or-syms] - (locking instrumented-vars - (into - [] - (comp (filter symbol?) - (map unstrument-1) - (remove nil?)) - (collectionize sym-or-syms))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; testing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- explain-check - [args spec v role] - (ex-info - "Specification-based check failed" - (when-not (s/valid? spec v nil) - (assoc (s/explain-data* spec [role] [] [] v) - ::args args - ::val v - ::s/failure :check-failed)))) - -(defn- check-call - "Returns true if call passes specs, otherwise *returns* an exception -with explain-data + ::s/failure." - [f specs args] - (let [cargs (when (:args specs) (s/conform (:args specs) args))] - (if (= cargs ::s/invalid) - (explain-check args (:args specs) args :args) - (let [ret (apply f args) - cret (when (:ret specs) (s/conform (:ret specs) ret))] - (if (= cret ::s/invalid) - (explain-check args (:ret specs) ret :ret) - (if (and (:args specs) (:ret specs) (:fn specs)) - (if (s/valid? (:fn specs) {:args cargs :ret cret}) - true - (explain-check args (:fn specs) {:args cargs :ret cret} :fn)) - true)))))) - -(defn- quick-check - [f specs {gen :gen opts ::stc/opts}] - (let [{:keys [num-tests] :or {num-tests 1000}} opts - g (try (s/gen (:args specs) gen) (catch Throwable t t))] - (if (throwable? g) - {:result g} - (let [prop (gen/for-all* [g] #(check-call f specs %))] - (apply gen/quick-check num-tests prop (mapcat identity opts)))))) - -(defn- make-check-result - "Builds spec result map." - [check-sym spec test-check-ret] - (merge {:spec spec - ::stc/ret test-check-ret} - (when check-sym - {:sym check-sym}) - (when-let [result (-> test-check-ret :result)] - (when-not (true? result) {:failure result})) - (when-let [shrunk (-> test-check-ret :shrunk)] - {:failure (:result shrunk)}))) - -(defn- check-1 - [{:keys [s f v spec]} opts] - (let [re-inst? (and v (seq (unstrument s)) true) - f (or f (when v @v)) - specd (s/spec spec)] - (try - (cond - (or (nil? f) (some-> v meta :macro)) - {:failure (ex-info "No fn to spec" {::s/failure :no-fn}) - :sym s :spec spec} - - (:args specd) - (let [tcret (quick-check f specd opts)] - (make-check-result s spec tcret)) - - :default - {:failure (ex-info "No :args spec" {::s/failure :no-args-spec}) - :sym s :spec spec}) - (finally - (when re-inst? (instrument s)))))) - -(defn- sym->check-map - [s] - (let [v (resolve s)] - {:s s - :v v - :spec (when v (s/get-spec v))})) - -(defn- validate-check-opts - [opts] - (assert (every? ident? (keys (:gen opts))) "check :gen expects ident keys")) - -(defn check-fn - "Runs generative tests for fn f using spec and opts. See -'check' for options and return." - ([f spec] (check-fn f spec nil)) - ([f spec opts] - (validate-check-opts opts) - (check-1 {:f f :spec spec} opts))) - -(defn checkable-syms - "Given an opts map as per check, returns the set of syms that -can be checked." - ([] (checkable-syms nil)) - ([opts] - (validate-check-opts opts) - (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) - (keys (:spec opts))]))) - -(defn check - "Run generative tests for spec conformance on vars named by -sym-or-syms, a symbol or collection of symbols. If sym-or-syms -is not specified, check all checkable vars. - -The opts map includes the following optional keys, where stc -aliases clojure.spec.test.check: - -::stc/opts opts to flow through test.check/quick-check -:gen map from spec names to generator overrides - -The ::stc/opts include :num-tests in addition to the keys -documented by test.check. Generator overrides are passed to -spec/gen when generating function args. - -Returns a lazy sequence of check result maps with the following -keys - -:spec the spec tested -:sym optional symbol naming the var tested -:failure optional test failure -::stc/ret optional value returned by test.check/quick-check - -The value for :failure can be any exception. Exceptions thrown by -spec itself will have an ::s/failure value in ex-data: - -:check-failed at least one checked return did not conform -:no-args-spec no :args spec provided -:no-fn no fn provided -:no-fspec no fspec provided -:no-gen unable to generate :args -:instrument invalid args detected by instrument -" - ([] (check (checkable-syms))) - ([sym-or-syms] (check sym-or-syms nil)) - ([sym-or-syms opts] - (->> (collectionize sym-or-syms) - (filter (checkable-syms opts)) - (pmap - #(check-1 (sym->check-map %) opts))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; check reporting ;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- failure-type - [x] - (::s/failure (ex-data x))) - -(defn- unwrap-failure - [x] - (if (failure-type x) - (ex-data x) - x)) - -(defn- result-type - "Returns the type of the check result. This can be any of the -::s/failure keywords documented in 'check', or: - - :check-passed all checked fn returns conformed - :check-threw checked fn threw an exception" - [ret] - (let [failure (:failure ret)] - (cond - (nil? failure) :check-passed - (failure-type failure) (failure-type failure) - :default :check-threw))) - -(defn abbrev-result - "Given a check result, returns an abbreviated version -suitable for summary use." - [x] - (if (:failure x) - (-> (dissoc x ::stc/ret) - (update :spec s/describe) - (update :failure unwrap-failure)) - (dissoc x :spec ::stc/ret))) - -(defn summarize-results - "Given a collection of check-results, e.g. from 'check', pretty -prints the summary-result (default abbrev-result) of each. - -Returns a map with :total, the total number of results, plus a -key with a count for each different :type of result." - ([check-results] (summarize-results check-results abbrev-result)) - ([check-results summary-result] - (reduce - (fn [summary result] - (pp/pprint (summary-result result)) - (-> summary - (update :total inc) - (update (result-type result) (fnil inc 0)))) - {:total 0} - check-results))) - - - diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 7211e6c41f..4ebc85fa06 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6803,19 +6803,18 @@ public static Object macroexpand1(Object x) { Var v = isMacro(op); if(v != null) { - // Do not check specs while inside clojure.spec - if(! "clojure/spec.clj".equals(SOURCE_PATH.deref())) + // Do not check specs while inside clojure.spec.alpha + if(! "clojure/spec/alpha.clj".equals(SOURCE_PATH.deref())) { try { - final Namespace checkns = Namespace.find(Symbol.intern("clojure.spec")); + final Namespace checkns = Namespace.find(Symbol.intern("clojure.spec.alpha")); if (checkns != null) { - final Var check = Var.find(Symbol.intern("clojure.spec/macroexpand-check")); + final Var check = Var.find(Symbol.intern("clojure.spec.alpha/macroexpand-check")); if ((check != null) && (check.isBound())) check.applyTo(RT.cons(v, RT.list(form.next()))); } - Symbol.intern("clojure.spec"); } catch(IllegalArgumentException e) { diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 4a6d0a5791..a6552f74e4 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -461,8 +461,8 @@ else if(!loaded && failIfNotFound) static void doInit() throws ClassNotFoundException, IOException{ load("clojure/core"); - load("clojure/spec"); - load("clojure/core/specs"); + load("clojure/spec/alpha"); + load("clojure/core/specs/alpha"); Var.pushThreadBindings( RT.mapUniqueKeys(CURRENT_NS, CURRENT_NS.deref(), diff --git a/test/clojure/test_clojure/spec.clj b/test/clojure/test_clojure/spec.clj deleted file mode 100644 index 658017e984..0000000000 --- a/test/clojure/test_clojure/spec.clj +++ /dev/null @@ -1,201 +0,0 @@ -(ns clojure.test-clojure.spec - (:require [clojure.spec :as s] - [clojure.spec.gen :as gen] - [clojure.spec.test :as stest] - [clojure.test :refer :all])) - -(set! *warn-on-reflection* true) - -(defmacro result-or-ex [x] - `(try - ~x - (catch Throwable t# - (.getName (class t#))))) - -(def even-count? #(even? (count %))) - -(defn submap? - "Is m1 a subset of m2?" - [m1 m2] - (if (and (map? m1) (map? m2)) - (every? (fn [[k v]] (and (contains? m2 k) - (submap? v (get m2 k)))) - m1) - (= m1 m2))) - -(deftest conform-explain - (let [a (s/and #(> % 5) #(< % 10)) - o (s/or :s string? :k keyword?) - c (s/cat :a string? :b keyword?) - either (s/alt :a string? :b keyword?) - star (s/* keyword?) - plus (s/+ keyword?) - opt (s/? keyword?) - andre (s/& (s/* keyword?) even-count?) - m (s/map-of keyword? string?) - mkeys (s/map-of (s/and keyword? (s/conformer name)) any?) - mkeys2 (s/map-of (s/and keyword? (s/conformer name)) any? :conform-keys true) - s (s/coll-of (s/spec (s/cat :tag keyword? :val any?)) :kind list?) - v (s/coll-of keyword? :kind vector?) - coll (s/coll-of keyword?) - lrange (s/int-in 7 42) - drange (s/double-in :infinite? false :NaN? false :min 3.1 :max 3.2) - irange (s/inst-in #inst "1939" #inst "1946") - ] - (are [spec x conformed ed] - (let [co (result-or-ex (s/conform spec x)) - e (result-or-ex (::s/problems (s/explain-data spec x)))] - (when (not= conformed co) (println "conform fail\n\texpect=" conformed "\n\tactual=" co)) - (when (not (every? true? (map submap? ed e))) - (println "explain failures\n\texpect=" ed "\n\tactual failures=" e "\n\tsubmap?=" (map submap? ed e))) - (and (= conformed co) (every? true? (map submap? ed e)))) - - lrange 7 7 nil - lrange 8 8 nil - lrange 42 ::s/invalid [{:pred '(int-in-range? 7 42 %), :val 42}] - - irange #inst "1938" ::s/invalid [{:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1938"}] - irange #inst "1942" #inst "1942" nil - irange #inst "1946" ::s/invalid [{:pred '(inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %), :val #inst "1946"}] - - drange 3.0 ::s/invalid [{:pred '(<= 3.1 %), :val 3.0}] - drange 3.1 3.1 nil - drange 3.2 3.2 nil - drange Double/POSITIVE_INFINITY ::s/invalid [{:pred '(not (isInfinite %)), :val Double/POSITIVE_INFINITY}] - ;; can't use equality-based test for Double/NaN - ;; drange Double/NaN ::s/invalid {[] {:pred '(not (isNaN %)), :val Double/NaN}} - - keyword? :k :k nil - keyword? nil ::s/invalid [{:pred ::s/unknown :val nil}] - keyword? "abc" ::s/invalid [{:pred ::s/unknown :val "abc"}] - - a 6 6 nil - a 3 ::s/invalid '[{:pred (> % 5), :val 3}] - a 20 ::s/invalid '[{:pred (< % 10), :val 20}] - a nil "java.lang.NullPointerException" "java.lang.NullPointerException" - a :k "java.lang.ClassCastException" "java.lang.ClassCastException" - - o "a" [:s "a"] nil - o :a [:k :a] nil - o 'a ::s/invalid '[{:pred string?, :val a, :path [:s]} {:pred keyword?, :val a :path [:k]}] - - c nil ::s/invalid '[{:reason "Insufficient input", :pred string?, :val (), :path [:a]}] - c [] ::s/invalid '[{:reason "Insufficient input", :pred string?, :val (), :path [:a]}] - c [:a] ::s/invalid '[{:pred string?, :val :a, :path [:a], :in [0]}] - c ["a"] ::s/invalid '[{:reason "Insufficient input", :pred keyword?, :val (), :path [:b]}] - c ["s" :k] '{:a "s" :b :k} nil - c ["s" :k 5] ::s/invalid '[{:reason "Extra input", :pred (cat :a string? :b keyword?), :val (5)}] - (s/cat) nil {} nil - (s/cat) [5] ::s/invalid '[{:reason "Extra input", :pred (cat), :val (5), :in [0]}] - - either nil ::s/invalid '[{:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}] - either [] ::s/invalid '[{:reason "Insufficient input", :pred (alt :a string? :b keyword?), :val () :via []}] - either [:k] [:b :k] nil - either ["s"] [:a "s"] nil - either [:b "s"] ::s/invalid '[{:reason "Extra input", :pred (alt :a string? :b keyword?), :val ("s") :via []}] - - star nil [] nil - star [] [] nil - star [:k] [:k] nil - star [:k1 :k2] [:k1 :k2] nil - star [:k1 :k2 "x"] ::s/invalid '[{:pred keyword?, :val "x" :via []}] - star ["a"] ::s/invalid '[{:pred keyword?, :val "a" :via []}] - - plus nil ::s/invalid '[{:reason "Insufficient input", :pred keyword?, :val () :via []}] - plus [] ::s/invalid '[{:reason "Insufficient input", :pred keyword?, :val () :via []}] - plus [:k] [:k] nil - plus [:k1 :k2] [:k1 :k2] nil - plus [:k1 :k2 "x"] ::s/invalid '[{:pred keyword?, :val "x", :in [2]}] - plus ["a"] ::s/invalid '[{:pred keyword?, :val "a" :via []}] - - opt nil nil nil - opt [] nil nil - opt :k ::s/invalid '[{:pred (? keyword?), :val :k}] - opt [:k] :k nil - opt [:k1 :k2] ::s/invalid '[{:reason "Extra input", :pred (? keyword?), :val (:k2)}] - opt [:k1 :k2 "x"] ::s/invalid '[{:reason "Extra input", :pred (? keyword?), :val (:k2 "x")}] - opt ["a"] ::s/invalid '[{:pred keyword?, :val "a"}] - - andre nil nil nil - andre [] nil nil - andre :k :clojure.spec/invalid '[{:pred (& (* keyword?) even-count?), :val :k}] - andre [:k] ::s/invalid '[{:pred even-count?, :val [:k]}] - andre [:j :k] [:j :k] nil - - m nil ::s/invalid '[{:pred map?, :val nil}] - m {} {} nil - m {:a "b"} {:a "b"} nil - - mkeys nil ::s/invalid '[{:pred map?, :val nil}] - mkeys {} {} nil - mkeys {:a 1 :b 2} {:a 1 :b 2} nil - - mkeys2 nil ::s/invalid '[{:pred map?, :val nil}] - mkeys2 {} {} nil - mkeys2 {:a 1 :b 2} {"a" 1 "b" 2} nil - - s '([:a 1] [:b "2"]) '({:tag :a :val 1} {:tag :b :val "2"}) nil - - v [:a :b] [:a :b] nil - v '(:a :b) ::s/invalid '[{:pred vector? :val (:a :b)}] - - coll nil ::s/invalid '[{:path [], :pred coll?, :val nil, :via [], :in []}] - coll [] [] nil - coll [:a] [:a] nil - coll [:a :b] [:a :b] nil - coll (map identity [:a :b]) '(:a :b) nil - ;;coll [:a "b"] ::s/invalid '[{:pred (coll-checker keyword?), :val [:a b]}] - ))) - -(defn check-conform-unform [spec vals expected-conforms] - (let [actual-conforms (map #(s/conform spec %) vals) - unforms (map #(s/unform spec %) actual-conforms)] - (is (= actual-conforms expected-conforms)) - (is (= vals unforms)))) - -(deftest nilable-conform-unform - (check-conform-unform - (s/nilable int?) - [5 nil] - [5 nil]) - (check-conform-unform - (s/nilable (s/or :i int? :s string?)) - [5 "x" nil] - [[:i 5] [:s "x"] nil])) - -(deftest nonconforming-conform-unform - (check-conform-unform - (s/nonconforming (s/or :i int? :s string?)) - [5 "x"] - [5 "x"])) - -(deftest coll-form - (are [spec form] - (= (s/form spec) form) - (s/map-of int? any?) - '(clojure.spec/map-of clojure.core/int? clojure.core/any?) - - (s/coll-of int?) - '(clojure.spec/coll-of clojure.core/int?) - - (s/every-kv int? int?) - '(clojure.spec/every-kv clojure.core/int? clojure.core/int?) - - (s/every int?) - '(clojure.spec/every clojure.core/int?) - - (s/coll-of (s/tuple (s/tuple int?))) - '(clojure.spec/coll-of (clojure.spec/tuple (clojure.spec/tuple clojure.core/int?))) - - (s/coll-of int? :kind vector?) - '(clojure.spec/coll-of clojure.core/int? :kind clojure.core/vector?) - - (s/coll-of int? :gen #(gen/return [1 2])) - '(clojure.spec/coll-of clojure.core/int? :gen (fn* [] (gen/return [1 2]))))) - -(comment - (require '[clojure.test :refer (run-tests)]) - (in-ns 'clojure.test-clojure.spec) - (run-tests) - - ) From be17c7c1d2777139c178a4d3dab4b4517385a8bd Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 27 Apr 2017 09:22:14 -0500 Subject: [PATCH 326/854] Use maven test classpath for compiling tests Signed-off-by: Stuart Halloway --- build.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.xml b/build.xml index 41790f5039..07628534c8 100644 --- a/build.xml +++ b/build.xml @@ -91,9 +91,8 @@ Direct linking = ${directlinking} - ${test-classes}:${test}:${build}:${cljsrc}:${maven.compile.classpath} From 843583cb2fb444996375f5ee88a655cd1362d61e Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 27 Apr 2017 09:42:26 -0500 Subject: [PATCH 327/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha16 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 566b8df6fd..6512dea190 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha16 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-alpha16 From 21a7d5192d7503c58dc97256617beae7c330b7bd Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 27 Apr 2017 09:42:26 -0500 Subject: [PATCH 328/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6512dea190..566b8df6fd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha16 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-alpha16 + HEAD From 72594111ef5390bdb18f239f8cf72a8237fd94e4 Mon Sep 17 00:00:00 2001 From: Ghadi Shayban Date: Thu, 8 Sep 2016 17:13:03 -0500 Subject: [PATCH 329/854] CLJ-1793 - Clear 'this' before calls in tail position The criteria for when a tail call is a safe point to clear 'this': 1) Must be in return position 2) Not in a try block (might need 'this' during catch/finally) 3) When not direct linked Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 125 +++++++++++++--------- test/clojure/test_clojure/compilation.clj | 27 +++++ test/clojure/test_clojure/reducers.clj | 4 + 3 files changed, 108 insertions(+), 48 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 4ebc85fa06..03342325ae 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -226,6 +226,8 @@ public class Compiler implements Opcodes{ //null or not static final public Var IN_CATCH_FINALLY = Var.create(null).setDynamic(); +static final public Var METHOD_RETURN_CONTEXT = Var.create(null).setDynamic(); + static final public Var NO_RECUR = Var.create(null).setDynamic(); //DynamicClassLoader @@ -373,6 +375,10 @@ static boolean isSpecial(Object sym){ return specials.containsKey(sym); } +static boolean inTailCall(C context) { + return (context == C.RETURN) && (METHOD_RETURN_CONTEXT.deref() != null) && (IN_CATCH_FINALLY.deref() == null); +} + static Symbol resolveSymbol(Symbol sym){ //already qualified or classname? if(sym.name.indexOf('.') > 0) @@ -1005,12 +1011,13 @@ else if(instance != null && instance.hasJavaClass() && instance.getJavaClass() ! Symbol sym = (Symbol) RT.first(call); Symbol tag = tagOf(form); PersistentVector args = PersistentVector.EMPTY; + boolean tailPosition = inTailCall(context); for(ISeq s = RT.next(call); s != null; s = s.next()) args = args.cons(analyze(context == C.EVAL ? context : C.EXPRESSION, s.first())); if(c != null) - return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args); + return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args, tailPosition); else - return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args); + return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args, tailPosition); } } } @@ -1440,13 +1447,15 @@ static class InstanceMethodExpr extends MethodExpr{ public final int line; public final int column; public final Symbol tag; + public final boolean tailPosition; public final java.lang.reflect.Method method; final static Method invokeInstanceMethodMethod = Method.getMethod("Object invokeInstanceMethod(Object,String,Object[])"); - public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, String methodName, IPersistentVector args) + public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, + String methodName, IPersistentVector args, boolean tailPosition) { this.source = source; this.line = line; @@ -1455,6 +1464,7 @@ public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr this.methodName = methodName; this.target = target; this.tag = tag; + this.tailPosition = tailPosition; if(target.hasJavaClass() && target.getJavaClass() != null) { List methods = Reflector.getMethods(target.getJavaClass(), args.count(), methodName, false); @@ -1548,10 +1558,10 @@ public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){ gen.checkCast(type); MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args); gen.visitLineNumber(line, gen.mark()); - if(context == C.RETURN) + if(tailPosition && !objx.canBeDirect) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); + method.emitClearThis(gen); } Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method)); if(method.getDeclaringClass().isInterface()) @@ -1622,12 +1632,14 @@ static class StaticMethodExpr extends MethodExpr{ public final int column; public final java.lang.reflect.Method method; public final Symbol tag; + public final boolean tailPosition; final static Method forNameMethod = Method.getMethod("Class classForName(String)"); final static Method invokeStaticMethodMethod = Method.getMethod("Object invokeStaticMethod(Class,String,Object[])"); final static Keyword warnOnBoxedKeyword = Keyword.intern("warn-on-boxed"); - public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, String methodName, IPersistentVector args) + public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, + String methodName, IPersistentVector args, boolean tailPosition) { this.c = c; this.methodName = methodName; @@ -1636,6 +1648,7 @@ public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c this.line = line; this.column = column; this.tag = tag; + this.tailPosition = tailPosition; List methods = Reflector.getMethods(c, args.count(), methodName, true); if(methods.isEmpty()) @@ -1774,10 +1787,10 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args); gen.visitLineNumber(line, gen.mark()); //Type type = Type.getObjectType(className.replace('.', '/')); - if(context == C.RETURN) + if(tailPosition && !objx.canBeDirect) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); + method.emitClearThis(gen); } Type type = Type.getType(c); Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method)); @@ -2271,13 +2284,14 @@ public Expr parse(C context, Object frm) { } else { - if(bodyExpr == null) - try { - Var.pushThreadBindings(RT.map(NO_RECUR, true)); - bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); - } finally { - Var.popThreadBindings(); - } + if(bodyExpr == null) + try { + Var.pushThreadBindings(RT.map(NO_RECUR, true, METHOD_RETURN_CONTEXT, null)); + bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); + } finally { + Var.popThreadBindings(); + } + if(Util.equals(op, CATCH)) { Class c = HostExpr.maybeClass(RT.second(f), false); @@ -2325,17 +2339,21 @@ public Expr parse(C context, Object frm) { } } } - if(bodyExpr == null) { - try - { - Var.pushThreadBindings(RT.map(NO_RECUR, true)); - bodyExpr = (new BodyExpr.Parser()).parse(C.EXPRESSION, RT.seq(body)); - } - finally - { - Var.popThreadBindings(); - } - } + if(bodyExpr == null) + { + // this codepath is hit when there is neither catch or finally, e.g. (try (expr)) + // return a body expr directly + try + { + Var.pushThreadBindings(RT.map(NO_RECUR, true)); + bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body)); + } + finally + { + Var.popThreadBindings(); + } + return bodyExpr; + } return new TryExpr(bodyExpr, catches, finallyExpr, retLocal, finallyLocal); @@ -2587,11 +2605,6 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ gen.newInstance(type); gen.dup(); MethodExpr.emitTypedArgs(objx, gen, ctor.getParameterTypes(), args); - if(context == C.RETURN) - { - ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); - } gen.invokeConstructor(type, new Method("", Type.getConstructorDescriptor(ctor))); } else @@ -2599,11 +2612,6 @@ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ gen.push(destubClassName(c.getName())); gen.invokeStatic(RT_TYPE, forNameMethod); MethodExpr.emitArgsAsArray(args, objx, gen); - if(context == C.RETURN) - { - ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); - } gen.invokeStatic(REFLECTOR_TYPE, invokeConstructorMethod); } if(context == C.STATEMENT) @@ -3431,16 +3439,18 @@ static class StaticInvokeExpr implements Expr, MaybePrimitiveExpr{ public final Type[] paramtypes; public final IPersistentVector args; public final boolean variadic; + public final boolean tailPosition; public final Object tag; StaticInvokeExpr(Type target, Class retClass, Class[] paramclasses, Type[] paramtypes, boolean variadic, - IPersistentVector args,Object tag){ + IPersistentVector args,Object tag, boolean tailPosition){ this.target = target; this.retClass = retClass; this.paramclasses = paramclasses; this.paramtypes = paramtypes; this.args = args; this.variadic = variadic; + this.tailPosition = tailPosition; this.tag = tag; } @@ -3497,6 +3507,12 @@ public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){ else MethodExpr.emitTypedArgs(objx, gen, paramclasses, args); + if(tailPosition && !objx.canBeDirect) + { + ObjMethod method = (ObjMethod) METHOD.deref(); + method.emitClearThis(gen); + } + gen.invokeStatic(target, ms); } @@ -3504,7 +3520,7 @@ private Type getReturnType(){ return Type.getType(retClass); } - public static Expr parse(Var v, ISeq args, Object tag) { + public static Expr parse(Var v, ISeq args, Object tag, boolean tailPosition) { if(!v.isBound() || v.get() == null) { // System.out.println("Not bound: " + v); @@ -3560,7 +3576,7 @@ else if(argcount > params.length for(ISeq s = RT.seq(args); s != null; s = s.next()) argv = argv.cons(analyze(C.EXPRESSION, s.first())); - return new StaticInvokeExpr(target,retClass,paramClasses, paramTypes,variadic, argv, tag); + return new StaticInvokeExpr(target,retClass,paramClasses, paramTypes,variadic, argv, tag, tailPosition); } } @@ -3571,6 +3587,7 @@ static class InvokeExpr implements Expr{ public final IPersistentVector args; public final int line; public final int column; + public final boolean tailPosition; public final String source; public boolean isProtocol = false; public boolean isDirect = false; @@ -3593,12 +3610,14 @@ static Object sigTag(int argcount, Var v){ return null; } - public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args) { + public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args, boolean tailPosition) { this.source = source; this.fexpr = fexpr; this.args = args; this.line = line; this.column = column; + this.tailPosition = tailPosition; + if(fexpr instanceof VarExpr) { Var fvar = ((VarExpr)fexpr).var; @@ -3743,10 +3762,10 @@ void emitArgsAndCall(int firstArgToEmit, C context, ObjExpr objx, GeneratorAdapt } gen.visitLineNumber(line, gen.mark()); - if(context == C.RETURN) + if(tailPosition && !objx.canBeDirect) { ObjMethod method = (ObjMethod) METHOD.deref(); - method.emitClearLocals(gen); + method.emitClearThis(gen); } gen.invokeInterface(IFN_TYPE, new Method("invoke", OBJECT_TYPE, ARG_TYPES[Math.min(MAX_POSITIONAL_ARITY + 1, @@ -3762,6 +3781,7 @@ public Class getJavaClass() { } static public Expr parse(C context, ISeq form) { + boolean tailPosition = inTailCall(context); if(context != C.EVAL) context = C.EXPRESSION; Expr fexpr = analyze(context, form.first()); @@ -3791,7 +3811,7 @@ static public Expr parse(C context, ISeq form) { Object sigtag = sigTag(arity, v); Object vtag = RT.get(RT.meta(v), RT.TAG_KEY); Expr ret = StaticInvokeExpr - .parse(v, RT.next(form), formtag != null ? formtag : sigtag != null ? sigtag : vtag); + .parse(v, RT.next(form), formtag != null ? formtag : sigtag != null ? sigtag : vtag, tailPosition); if(ret != null) { // System.out.println("invoke direct: " + v); @@ -3838,7 +3858,7 @@ static public Expr parse(C context, ISeq form) { // throw new IllegalArgumentException( // String.format("No more than %d args supported", MAX_POSITIONAL_ARITY)); - return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args); + return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args, tailPosition); } } @@ -5296,6 +5316,7 @@ static FnMethod parse(ObjExpr objx, ISeq form, Object rettag) { ,CLEAR_PATH, pnode ,CLEAR_ROOT, pnode ,CLEAR_SITES, PersistentHashMap.EMPTY + ,METHOD_RETURN_CONTEXT, RT.T )); method.prim = primInterface(parms); @@ -5873,6 +5894,11 @@ void emitClearLocalsOld(GeneratorAdapter gen){ } } } + + void emitClearThis(GeneratorAdapter gen) { + gen.visitInsn(Opcodes.ACONST_NULL); + gen.visitVarInsn(Opcodes.ASTORE, 0); + } } public static class LocalBinding{ @@ -6300,14 +6326,14 @@ public Expr parse(C context, Object frm) { { if(recurMismatches != null && RT.booleanCast(recurMismatches.nth(i/2))) { - init = new StaticMethodExpr("", 0, 0, null, RT.class, "box", RT.vector(init)); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "box", RT.vector(init), false); if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref())) RT.errPrintWriter().println("Auto-boxing loop arg: " + sym); } else if(maybePrimitiveType(init) == int.class) - init = new StaticMethodExpr("", 0, 0, null, RT.class, "longCast", RT.vector(init)); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "longCast", RT.vector(init), false); else if(maybePrimitiveType(init) == float.class) - init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init)); + init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init), false); } //sequential enhancement of env (like Lisp let*) try @@ -6339,10 +6365,12 @@ else if(maybePrimitiveType(init) == float.class) try { if(isLoop) { + Object methodReturnContext = context == C.RETURN ? METHOD_RETURN_CONTEXT.deref() : null; Var.pushThreadBindings( RT.map(CLEAR_PATH, clearpath, CLEAR_ROOT, clearroot, - NO_RECUR, null)); + NO_RECUR, null, + METHOD_RETURN_CONTEXT, methodReturnContext)); } bodyExpr = (new BodyExpr.Parser()).parse(isLoop ? C.RETURN : context, body); @@ -8247,6 +8275,7 @@ static NewInstanceMethod parse(ObjExpr objx, ISeq form, Symbol thistag, ,CLEAR_PATH, pnode ,CLEAR_ROOT, pnode ,CLEAR_SITES, PersistentHashMap.EMPTY + ,METHOD_RETURN_CONTEXT, RT.T )); //register 'this' as local 0 diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index a730b89c0a..df0f995d15 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -354,6 +354,33 @@ ;; throws an exception on failure (is (eval `(fn [] ~(CLJ1399. 1))))) +(deftest CLJ-1250-this-clearing + (testing "clearing during try/catch/finally" + (let [closed-over-in-catch (let [x :foo] + (fn [] + (try + (throw (Exception. "boom")) + (catch Exception e + x)))) ;; x should remain accessible to the fn + + a (atom nil) + closed-over-in-finally (fn [] + (try + :ret + (finally + (reset! a :run))))] + (is (= :foo (closed-over-in-catch))) + (is (= :ret (closed-over-in-finally))) + (is (= :run @a)))) + (testing "no clearing when loop not in return context" + (let [x (atom 5) + bad (fn [] + (loop [] (System/getProperties)) + (swap! x dec) + (when (pos? @x) + (recur)))] + (is (nil? (bad)))))) + (deftest CLJ-1586-lazyseq-literals-preserve-metadata (should-not-reflect (eval (list '.substring (with-meta (concat '(identity) '("foo")) {:tag 'String}) 0)))) diff --git a/test/clojure/test_clojure/reducers.clj b/test/clojure/test_clojure/reducers.clj index c2852ccb2d..a884c85179 100644 --- a/test/clojure/test_clojure/reducers.clj +++ b/test/clojure/test_clojure/reducers.clj @@ -89,3 +89,7 @@ ([ret k v] (when (= k k-fail) (throw (IndexOutOfBoundsException.))))) (zipmap (range test-map-count) (repeat :dummy))))))) + +(deftest test-closed-over-clearing + ;; this will throw OutOfMemory without proper reference clearing + (is (number? (reduce + 0 (r/map identity (range 1e8)))))) From ebfdbca535b81808b3ddc369d4a4e98b8f1524c7 Mon Sep 17 00:00:00 2001 From: thurston nabe Date: Fri, 6 Jan 2017 21:15:27 -0800 Subject: [PATCH 330/854] CLJ-2091 Squashed commit of the following: commit 528884a36a98e852984e5968f6e26932855ceef8 Author: thurston nabe Date: Fri Jan 6 19:55:39 2017 -0800 CLJ-2091 Replaced the inline intialization of #_hash and #_hasheq with default intialization; and then check for zero in the corresponding methods. The 4 persistent classes are now multi thread-safe even if unsafely published, commit fd4eb4176da710fda229a3005d19ee091fc34d11 Author: thurston nabe Date: Tue Jan 3 18:36:51 2017 -0800 CLJ-2091 Refactored persistent classes so that #hashCode() and #hashequals() do only a single read Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/APersistentMap.java | 18 ++++++++++-------- src/jvm/clojure/lang/APersistentSet.java | 17 +++++++++-------- src/jvm/clojure/lang/APersistentVector.java | 20 +++++++++++--------- src/jvm/clojure/lang/PersistentQueue.java | 20 +++++++++++--------- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/jvm/clojure/lang/APersistentMap.java b/src/jvm/clojure/lang/APersistentMap.java index 6e01ddc2b5..09ad42cc7f 100644 --- a/src/jvm/clojure/lang/APersistentMap.java +++ b/src/jvm/clojure/lang/APersistentMap.java @@ -14,8 +14,8 @@ import java.util.*; public abstract class APersistentMap extends AFn implements IPersistentMap, Map, Iterable, Serializable, MapEquivalence, IHashEq { -int _hash = -1; -int _hasheq = -1; +int _hash; +int _hasheq; public String toString(){ return RT.printString(this); @@ -93,11 +93,12 @@ public boolean equiv(Object obj){ return true; } public int hashCode(){ - if(_hash == -1) + int cached = this._hash; + if(cached == 0) { - this._hash = mapHash(this); + this._hash = cached = mapHash(this); } - return _hash; + return cached; } static public int mapHash(IPersistentMap m){ @@ -112,12 +113,13 @@ static public int mapHash(IPersistentMap m){ } public int hasheq(){ - if(_hasheq == -1) + int cached = this._hasheq; + if(cached == 0) { //this._hasheq = mapHasheq(this); - _hasheq = Murmur3.hashUnordered(this); + this._hasheq = cached = Murmur3.hashUnordered(this); } - return _hasheq; + return cached; } static public int mapHasheq(IPersistentMap m) { diff --git a/src/jvm/clojure/lang/APersistentSet.java b/src/jvm/clojure/lang/APersistentSet.java index c71eb84cb8..1c2ce8f46c 100644 --- a/src/jvm/clojure/lang/APersistentSet.java +++ b/src/jvm/clojure/lang/APersistentSet.java @@ -18,8 +18,8 @@ import java.util.Set; public abstract class APersistentSet extends AFn implements IPersistentSet, Collection, Set, Serializable, IHashEq { -int _hash = -1; -int _hasheq = -1; +int _hash; +int _hasheq; final IPersistentMap impl; protected APersistentSet(IPersistentMap impl){ @@ -91,10 +91,10 @@ public boolean equiv(Object obj){ } public int hashCode(){ - if(_hash == -1) + int hash = this._hash; + if(hash == 0) { //int hash = count(); - int hash = 0; for(ISeq s = seq(); s != null; s = s.next()) { Object e = s.first(); @@ -103,11 +103,12 @@ public int hashCode(){ } this._hash = hash; } - return _hash; + return hash; } public int hasheq(){ - if(_hasheq == -1){ + int cached = this._hasheq; + if(cached == 0){ // int hash = 0; // for(ISeq s = seq(); s != null; s = s.next()) // { @@ -115,9 +116,9 @@ public int hasheq(){ // hash += Util.hasheq(e); // } // this._hasheq = hash; - _hasheq = Murmur3.hashUnordered(this); + this._hasheq = cached = Murmur3.hashUnordered(this); } - return _hasheq; + return cached; } public Object[] toArray(){ diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java index b55ead8451..f87ad7a01f 100644 --- a/src/jvm/clojure/lang/APersistentVector.java +++ b/src/jvm/clojure/lang/APersistentVector.java @@ -19,8 +19,8 @@ public abstract class APersistentVector extends AFn implements IPersistentVector List, RandomAccess, Comparable, Serializable, IHashEq { -int _hash = -1; -int _hasheq = -1; +int _hash; +int _hasheq; public String toString(){ return RT.printString(this); @@ -139,9 +139,10 @@ public boolean equiv(Object obj){ } public int hashCode(){ - if(_hash == -1) + int hash = this._hash; + if(hash == 0) { - int hash = 1; + hash = 1; for(int i = 0;i Date: Fri, 10 Mar 2017 08:47:36 -0600 Subject: [PATCH 331/854] CLJ-1860 Make -0.0 hash consistent with 0.0 0.0 and -0.0 compared equal but hashed differently. The patch makes -0.0 (double and float) hash to 0, same as 0.0. Also, the patch restructures hasheq to cover just long and double and moves the rest to a helper function to improve inlining. Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Numbers.java | 46 +++++++++++++++++++-------- test/clojure/test_clojure/numbers.clj | 4 ++- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/jvm/clojure/lang/Numbers.java b/src/jvm/clojure/lang/Numbers.java index 623743bc40..ae42676dd6 100644 --- a/src/jvm/clojure/lang/Numbers.java +++ b/src/jvm/clojure/lang/Numbers.java @@ -1033,21 +1033,18 @@ else if(xc == BigDecimal.class) } @WarnBoxedMath(false) -static int hasheq(Number x){ - Class xc = x.getClass(); - - if(xc == Long.class - || xc == Integer.class - || xc == Short.class - || xc == Byte.class - || (xc == BigInteger.class && lte(x, Long.MAX_VALUE) && gte(x,Long.MIN_VALUE))) - { +static int hasheqFrom(Number x, Class xc){ + if(xc == Integer.class + || xc == Short.class + || xc == Byte.class + || (xc == BigInteger.class && lte(x, Long.MAX_VALUE) && gte(x,Long.MIN_VALUE))) + { long lpart = x.longValue(); return Murmur3.hashLong(lpart); //return (int) (lpart ^ (lpart >>> 32)); - } + } if(xc == BigDecimal.class) - { + { // stripTrailingZeros() to make all numerically equal // BigDecimal values come out the same before calling // hashCode. Special check for 0 because @@ -1056,14 +1053,37 @@ static int hasheq(Number x){ if (isZero(x)) return BigDecimal.ZERO.hashCode(); else - { + { BigDecimal tmp = ((BigDecimal) x).stripTrailingZeros(); return tmp.hashCode(); - } } + } + if(xc == Float.class && x.equals(-0.0f)) + { + return 0; // match 0.0f + } return x.hashCode(); } +@WarnBoxedMath(false) +static int hasheq(Number x){ + Class xc = x.getClass(); + + if(xc == Long.class) + { + long lpart = x.longValue(); + return Murmur3.hashLong(lpart); + //return (int) (lpart ^ (lpart >>> 32)); + } + if(xc == Double.class) + { + if(x.equals(-0.0)) + return 0; // match 0.0 + return x.hashCode(); + } + return hasheqFrom(x, xc); +} + static Category category(Object x){ Class xc = x.getClass(); diff --git a/test/clojure/test_clojure/numbers.clj b/test/clojure/test_clojure/numbers.clj index f09dd4bf85..b3d142355a 100644 --- a/test/clojure/test_clojure/numbers.clj +++ b/test/clojure/test_clojure/numbers.clj @@ -72,6 +72,7 @@ (all-pairs-equal #'= [(byte 2) (short 2) (int 2) (long 2) (bigint 2) (biginteger 2)]) (all-pairs-equal #'= [(float 2.0) (double 2.0)]) + (all-pairs-equal #'= [(float 0.0) (double 0.0) (float -0.0) (double -0.0)]) (all-pairs-equal #'= [2.0M 2.00M]) (all-pairs-equal #'= [(float 1.5) (double 1.5)]) (all-pairs-equal #'= [1.50M 1.500M]) @@ -85,12 +86,13 @@ (bigint 2) (double 2.0) 2.0M 2.00M]) (all-pairs-hash-consistent-with-= [(/ 3 2) (double 1.5) 1.50M 1.500M]) - (all-pairs-hash-consistent-with-= [(double 0.0) 0.0M 0.00M]) + (all-pairs-hash-consistent-with-= [(double -0.0) (double 0.0) -0.0M -0.00M 0.0M 0.00M (float -0.0) (float 0.0)]) ;; == tests for numerical equality, returning true even for numbers ;; in different categories. (all-pairs-equal #'== [(byte 0) (short 0) (int 0) (long 0) (bigint 0) (biginteger 0) + (float -0.0) (double -0.0) -0.0M -0.00M (float 0.0) (double 0.0) 0.0M 0.00M]) (all-pairs-equal #'== [(byte 2) (short 2) (int 2) (long 2) (bigint 2) (biginteger 2) From e9e57e4808b7700ccee2ea32e17051ba1063112e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 26 May 2017 08:17:56 -0500 Subject: [PATCH 332/854] CLJ-2141 Return only true/false from qualified-* predicates Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 72383d6b1b..177f8c1a58 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1599,6 +1599,13 @@ [^clojure.lang.Named x] (. x (getNamespace))) +(defn boolean + "Coerce to boolean" + { + :inline (fn [x] `(. clojure.lang.RT (booleanCast ~x))) + :added "1.0"} + [x] (clojure.lang.RT/booleanCast x)) + (defn ident? "Return true if x is a symbol or keyword" {:added "1.9"} @@ -1612,7 +1619,7 @@ (defn qualified-ident? "Return true if x is a symbol or keyword with a namespace" {:added "1.9"} - [x] (and (ident? x) (namespace x) true)) + [x] (boolean (and (ident? x) (namespace x) true))) (defn simple-symbol? "Return true if x is a symbol without a namespace" @@ -1622,7 +1629,7 @@ (defn qualified-symbol? "Return true if x is a symbol with a namespace" {:added "1.9"} - [x] (and (symbol? x) (namespace x) true)) + [x] (boolean (and (symbol? x) (namespace x) true))) (defn simple-keyword? "Return true if x is a keyword without a namespace" @@ -1632,7 +1639,7 @@ (defn qualified-keyword? "Return true if x is a keyword with a namespace" {:added "1.9"} - [x] (and (keyword? x) (namespace x) true)) + [x] (boolean (and (keyword? x) (namespace x) true))) (defmacro locking "Executes exprs in an implicit do, while holding the monitor of x. @@ -3486,13 +3493,6 @@ :added "1.1"} [x] (. clojure.lang.RT (charCast x))) -(defn boolean - "Coerce to boolean" - { - :inline (fn [x] `(. clojure.lang.RT (booleanCast ~x))) - :added "1.0"} - [x] (clojure.lang.RT/booleanCast x)) - (defn unchecked-byte "Coerce to byte. Subject to rounding or truncation." {:inline (fn [x] `(. clojure.lang.RT (uncheckedByteCast ~x))) From 964232c7bf442787740fa0200b289b3495b1ec09 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 3 Apr 2017 10:15:15 -0500 Subject: [PATCH 333/854] CLJ-2142 Check for duplicate keys with namespace map syntax Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/EdnReader.java | 22 +++++++++------------- src/jvm/clojure/lang/LispReader.java | 22 +++++++++------------- test/clojure/test_clojure/reader.cljc | 6 +++++- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/jvm/clojure/lang/EdnReader.java b/src/jvm/clojure/lang/EdnReader.java index 5c3bd104b0..c5c3665e27 100644 --- a/src/jvm/clojure/lang/EdnReader.java +++ b/src/jvm/clojure/lang/EdnReader.java @@ -505,35 +505,31 @@ public Object invoke(Object reader, Object colon, Object opts) { throw Util.runtimeException("Namespaced map literal must contain an even number of forms"); // Construct output map - IPersistentMap m = RT.map(); + Object[] a = new Object[kvs.size()]; Iterator iter = kvs.iterator(); - while(iter.hasNext()) { + for(int i = 0; iter.hasNext(); i += 2) { Object key = iter.next(); Object val = iter.next(); if(key instanceof Keyword) { Keyword kw = (Keyword) key; if (kw.getNamespace() == null) { - m = m.assoc(Keyword.intern(ns, kw.getName()), val); + key = Keyword.intern(ns, kw.getName()); } else if (kw.getNamespace().equals("_")) { - m = m.assoc(Keyword.intern(null, kw.getName()), val); - } else { - m = m.assoc(kw, val); + key = Keyword.intern(null, kw.getName()); } } else if(key instanceof Symbol) { Symbol s = (Symbol) key; if (s.getNamespace() == null) { - m = m.assoc(Symbol.intern(ns, s.getName()), val); + key = Symbol.intern(ns, s.getName()); } else if (s.getNamespace().equals("_")) { - m = m.assoc(Symbol.intern(null, s.getName()), val); - } else { - m = m.assoc(s, val); + key = Symbol.intern(null, s.getName()); } - } else { - m = m.assoc(key, val); } + a[i] = key; + a[i+1] = val; } - return m; + return RT.map(a); } } diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index a4afb847d2..35b77468f5 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -667,35 +667,31 @@ public Object invoke(Object reader, Object colon, Object opts, Object pendingFor throw Util.runtimeException("Namespaced map literal must contain an even number of forms"); // Construct output map - IPersistentMap m = RT.map(); + Object[] a = new Object[kvs.size()]; Iterator iter = kvs.iterator(); - while(iter.hasNext()) { + for(int i = 0; iter.hasNext(); i += 2) { Object key = iter.next(); Object val = iter.next(); if(key instanceof Keyword) { Keyword kw = (Keyword) key; if (kw.getNamespace() == null) { - m = m.assoc(Keyword.intern(ns, kw.getName()), val); + key = Keyword.intern(ns, kw.getName()); } else if (kw.getNamespace().equals("_")) { - m = m.assoc(Keyword.intern(null, kw.getName()), val); - } else { - m = m.assoc(kw, val); + key = Keyword.intern(null, kw.getName()); } } else if(key instanceof Symbol) { Symbol s = (Symbol) key; if (s.getNamespace() == null) { - m = m.assoc(Symbol.intern(ns, s.getName()), val); + key = Symbol.intern(ns, s.getName()); } else if (s.getNamespace().equals("_")) { - m = m.assoc(Symbol.intern(null, s.getName()), val); - } else { - m = m.assoc(s, val); + key = Symbol.intern(null, s.getName()); } - } else { - m = m.assoc(key, val); } + a[i] = key; + a[i+1] = val; } - return m; + return RT.map(a); } } diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc index 91ce25ec22..eb1e24de24 100644 --- a/test/clojure/test_clojure/reader.cljc +++ b/test/clojure/test_clojure/reader.cljc @@ -742,7 +742,11 @@ Exception #"Namespaced map must specify a valid namespace" "#::clojure.core/t{1 2}" Exception #"Unknown auto-resolved namespace alias" "#::BOGUS{1 2}" Exception #"Namespaced map must specify a namespace" "#:: clojure.core{:a 1}" - Exception #"Namespaced map must specify a namespace" "#: clojure.core{:a 1}")) + Exception #"Namespaced map must specify a namespace" "#: clojure.core{:a 1}" + Exception #"Duplicate key: :user/a" "#::{:a 1 :a 2}" + Exception #"Duplicate key: :clojure.core/a" "#::clojure.core{:a 1 :a 2}" + Exception #"Duplicate key: user/a" "#::{a 1 a 2}" + Exception #"Duplicate key: clojure.core/+" "#::clojure.core{+ 1 + 2}")) (deftest namespaced-map-edn (is (= {1 1, :a/b 2, :b/c 3, :d 4} From f2987665d00a579bf4efb169cf86ed141e0c1106 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Fri, 26 May 2017 11:39:24 -0400 Subject: [PATCH 334/854] CLJ-2128 spec failure during macroexpand should wrap in compiler exception with location info --- src/jvm/clojure/lang/Compiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 03342325ae..b5c881f2b4 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6844,7 +6844,7 @@ public static Object macroexpand1(Object x) { check.applyTo(RT.cons(v, RT.list(form.next()))); } } - catch(IllegalArgumentException e) + catch(Exception e) { throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e); } From 6a03b43d5aa66164ef45679916233c4c16fd9d3e Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Fri, 26 May 2017 12:20:12 -0400 Subject: [PATCH 335/854] bump spec.alpha --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 566b8df6fd..1b38eb9e46 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ org.clojure spec.alpha - 0.1.94 + 0.1.123 org.clojure From 3b246576f49c19e511d7027296139a6e45ccaf90 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 26 May 2017 15:03:47 -0500 Subject: [PATCH 336/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha17 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1b38eb9e46..b17a01831f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha17 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-alpha17 From d7e92e5d71ca2cf4503165e551859207ba709ddf Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 26 May 2017 15:03:47 -0500 Subject: [PATCH 337/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b17a01831f..1b38eb9e46 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha17 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-alpha17 + HEAD From 005692d9e21fa058c2c5d263899e2b78f33176e4 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 17 Aug 2017 07:40:14 -0400 Subject: [PATCH 338/854] move to latest specs --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b38eb9e46..3b52bbdb63 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ org.clojure core.specs.alpha - 0.1.10 + 0.1.24 org.clojure From dd851520f0c94727591b8b605176755b2a993b0e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 21 Aug 2017 10:54:23 -0400 Subject: [PATCH 339/854] accept only aliases after ::, pluggable read resolver --- src/jvm/clojure/lang/LispReader.java | 122 ++++++++++++++++++++------ src/jvm/clojure/lang/RT.java | 1 + test/clojure/test_clojure/reader.cljc | 13 +-- 3 files changed, 97 insertions(+), 39 deletions(-) diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index 35b77468f5..c331ac8dc3 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -119,6 +119,13 @@ public class LispReader{ dispatchMacros[':'] = new NamespaceMapReader(); } +public static interface Resolver{ + Symbol currentNS(); + Symbol resolveClass(Symbol sym); + Symbol resolveAlias(Symbol sym); + Symbol resolveVar(Symbol sym); +} + static boolean isWhitespace(int ch){ return Character.isWhitespace(ch) || ch == ','; } @@ -195,11 +202,11 @@ static public Object read(PushbackReader r, boolean eofIsError, Object eofValue, static public Object read(PushbackReader r, boolean eofIsError, Object eofValue, boolean isRecursive, Object opts) { // start with pendingForms null as reader conditional splicing is not allowed at top level - return read(r, eofIsError, eofValue, null, null, isRecursive, opts, null); + return read(r, eofIsError, eofValue, null, null, isRecursive, opts, null, (Resolver) RT.READER_RESOLVER.deref()); } static private Object read(PushbackReader r, boolean eofIsError, Object eofValue, boolean isRecursive, Object opts, Object pendingForms) { - return read(r, eofIsError, eofValue, null, null, isRecursive, opts, ensurePending(pendingForms)); + return read(r, eofIsError, eofValue, null, null, isRecursive, opts, ensurePending(pendingForms), (Resolver) RT.READER_RESOLVER.deref()); } static private Object ensurePending(Object pendingForms) { @@ -222,7 +229,9 @@ static private Object installPlatformFeature(Object opts) { } } -static private Object read(PushbackReader r, boolean eofIsError, Object eofValue, Character returnOn, Object returnOnValue, boolean isRecursive, Object opts, Object pendingForms) +static private Object read(PushbackReader r, boolean eofIsError, Object eofValue, Character returnOn, + Object returnOnValue, boolean isRecursive, Object opts, Object pendingForms, + Resolver resolver) { if(RT.READEVAL.deref() == UNKNOWN) throw Util.runtimeException("Reading disallowed - *read-eval* bound to :unknown"); @@ -282,7 +291,7 @@ static private Object read(PushbackReader r, boolean eofIsError, Object eofValue } String token = readToken(r, (char) ch); - return interpretToken(token); + return interpretToken(token, resolver); } } catch(Exception e) @@ -370,7 +379,7 @@ static private int readUnicodeChar(PushbackReader r, int initch, int base, int l return uc; } -static private Object interpretToken(String s) { +static private Object interpretToken(String s, Resolver resolver) { if(s.equals("nil")) { return null; @@ -385,7 +394,7 @@ else if(s.equals("false")) } Object ret = null; - ret = matchSymbol(s); + ret = matchSymbol(s, resolver); if(ret != null) return ret; @@ -393,7 +402,7 @@ else if(s.equals("false")) } -private static Object matchSymbol(String s){ +private static Object matchSymbol(String s, Resolver resolver){ Matcher m = symbolPat.matcher(s); if(m.matches()) { @@ -407,17 +416,33 @@ private static Object matchSymbol(String s){ if(s.startsWith("::")) { Symbol ks = Symbol.intern(s.substring(2)); - Namespace kns; - if(ks.ns != null) - kns = Compiler.namespaceFor(ks); - else - kns = Compiler.currentNS(); - //auto-resolving keyword - if (kns != null) - return Keyword.intern(kns.name.name,ks.name); - else - return null; - } + if(resolver != null) + { + Symbol nsym; + if(ks.ns != null) + nsym = resolver.resolveAlias(Symbol.intern(ks.ns)); + else + nsym = resolver.currentNS(); + //auto-resolving keyword + if(nsym != null) + return Keyword.intern(nsym.name, ks.name); + else + return null; + } + else + { + Namespace kns; + if(ks.ns != null) + kns = Compiler.currentNS().lookupAlias(Symbol.intern(ks.ns)); + else + kns = Compiler.currentNS(); + //auto-resolving keyword + if(kns != null) + return Keyword.intern(kns.name.name, ks.name); + else + return null; + } + } boolean isKeyword = s.charAt(0) == ':'; Symbol sym = Symbol.intern(s.substring(isKeyword ? 1 : 0)); if(isKeyword) @@ -640,19 +665,27 @@ public Object invoke(Object reader, Object colon, Object opts, Object pendingFor // Resolve autoresolved ns String ns; if (auto) { + Resolver resolver = (Resolver) RT.READER_RESOLVER.deref(); if (sym == null) { - ns = Compiler.currentNS().getName().getName(); + if(resolver != null) + ns = resolver.currentNS().name; + else + ns = Compiler.currentNS().getName().getName(); } else if (!(sym instanceof Symbol) || ((Symbol)sym).getNamespace() != null) { throw Util.runtimeException("Namespaced map must specify a valid namespace: " + sym); } else { - Namespace resolvedNS = Compiler.currentNS().lookupAlias((Symbol)sym); - if(resolvedNS == null) - resolvedNS = Namespace.find((Symbol)sym); + Symbol resolvedNS; + if (resolver != null) + resolvedNS = resolver.resolveAlias((Symbol) sym); + else{ + Namespace rns = Compiler.currentNS().lookupAlias((Symbol)sym); + resolvedNS = rns != null?rns.getName():null; + } if(resolvedNS == null) { throw Util.runtimeException("Unknown auto-resolved namespace alias: " + sym); } else { - ns = resolvedNS.getName().getName(); + ns = resolvedNS.getName(); } } } else if (!(sym instanceof Symbol) || ((Symbol)sym).getNamespace() != null) { @@ -858,7 +891,7 @@ public Object invoke(Object reader, Object pct, Object opts, Object pendingForms PushbackReader r = (PushbackReader) reader; if(ARG_ENV.deref() == null) { - return interpretToken(readToken(r, '%')); + return interpretToken(readToken(r, '%'), null); } int ch = read1(r); unread(r, ch); @@ -943,6 +976,7 @@ static Object syntaxQuote(Object form) { ret = RT.list(Compiler.QUOTE, form); else if(form instanceof Symbol) { + Resolver resolver = (Resolver) RT.READER_RESOLVER.deref(); Symbol sym = (Symbol) form; if(sym.ns == null && sym.name.endsWith("#")) { @@ -959,13 +993,41 @@ else if(form instanceof Symbol) else if(sym.ns == null && sym.name.endsWith(".")) { Symbol csym = Symbol.intern(null, sym.name.substring(0, sym.name.length() - 1)); - csym = Compiler.resolveSymbol(csym); + if(resolver != null){ + Symbol rc = resolver.resolveClass(csym); + if(rc != null) + csym = rc; + } + else + csym = Compiler.resolveSymbol(csym); sym = Symbol.intern(null, csym.name.concat(".")); } else if(sym.ns == null && sym.name.startsWith(".")) { // Simply quote method names. } + else if(resolver != null) + { + Symbol nsym = null; + if(sym.ns != null && sym.ns.indexOf('.') == -1){ + Symbol alias = Symbol.intern(null, sym.ns); + nsym = resolver.resolveClass(alias); + if(nsym == null) + nsym = resolver.resolveAlias(alias); + } + if(nsym != null){ + // Classname/foo -> package.qualified.Classname/foo + sym = Symbol.intern(nsym.name, sym.name); + } + else if(sym.ns == null){ + Symbol rsym = resolver.resolveClass(sym); + if(rsym == null) + rsym = resolver.resolveVar(sym); + if(rsym != null) + sym = rsym; + } + //leave alone if no resolution + } else { Object maybeClass = null; @@ -1292,10 +1354,12 @@ public static List readDelimitedList(char delim, PushbackReader r, boolean isRec ((LineNumberingPushbackReader) r).getLineNumber() : -1; ArrayList a = new ArrayList(); + Resolver resolver = (Resolver) RT.READER_RESOLVER.deref(); for(; ;) { - Object form = read(r, false, READ_EOF, delim, READ_FINISHED, isRecursive, opts, pendingForms); + Object form = read(r, false, READ_EOF, delim, READ_FINISHED, isRecursive, opts, pendingForms, + resolver); if (form == READ_EOF) { if (firstline < 0) @@ -1441,7 +1505,7 @@ public static Object readCondDelimited(PushbackReader r, boolean splicing, Objec for(; ;) { if(result == READ_STARTED) { // Read the next feature - form = read(r, false, READ_EOF, ')', READ_FINISHED, true, opts, pendingForms); + form = read(r, false, READ_EOF, ')', READ_FINISHED, true, opts, pendingForms, null); if (form == READ_EOF) { if (firstline < 0) @@ -1459,7 +1523,7 @@ public static Object readCondDelimited(PushbackReader r, boolean splicing, Objec //Read the form corresponding to the feature, and assign it to result if everything is kosher - form = read(r, false, READ_EOF, ')', READ_FINISHED, true, opts, pendingForms); + form = read(r, false, READ_EOF, ')', READ_FINISHED, true, opts, pendingForms, (Resolver) RT.READER_RESOLVER.deref()); if (form == READ_EOF) { if (firstline < 0) @@ -1480,7 +1544,7 @@ public static Object readCondDelimited(PushbackReader r, boolean splicing, Objec // When we already have a result, or when the feature didn't match, discard the next form in the reader try { Var.pushThreadBindings(RT.map(RT.SUPPRESS_READ, RT.T)); - form = read(r, false, READ_EOF, ')', READ_FINISHED, true, opts, pendingForms); + form = read(r, false, READ_EOF, ')', READ_FINISHED, true, opts, pendingForms, (Resolver) RT.READER_RESOLVER.deref()); if (form == READ_EOF) { if (firstline < 0) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index a6552f74e4..6723298c77 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -227,6 +227,7 @@ else if(s.equals("false")) final static Var PRINT_DUP = Var.intern(CLOJURE_NS, Symbol.intern("*print-dup*"), F).setDynamic(); final static Var WARN_ON_REFLECTION = Var.intern(CLOJURE_NS, Symbol.intern("*warn-on-reflection*"), F).setDynamic(); final static Var ALLOW_UNRESOLVED_VARS = Var.intern(CLOJURE_NS, Symbol.intern("*allow-unresolved-vars*"), F).setDynamic(); +final static Var READER_RESOLVER = Var.intern(CLOJURE_NS, Symbol.intern("*reader-resolver*"), null).setDynamic(); final static Var IN_NS_VAR = Var.intern(CLOJURE_NS, Symbol.intern("in-ns"), F); final static Var NS_VAR = Var.intern(CLOJURE_NS, Symbol.intern("ns"), F); diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc index eb1e24de24..2e64673366 100644 --- a/test/clojure/test_clojure/reader.cljc +++ b/test/clojure/test_clojure/reader.cljc @@ -727,26 +727,19 @@ (is (= #::s{1 nil, :a nil, :a/b nil, :_/d nil} #::s {1 nil, :a nil, :a/b nil, :_/d nil} {1 nil, :clojure.string/a nil, :a/b nil, :d nil})) - (is (= #::clojure.core{1 nil, :a nil, :a/b nil, :_/d nil} {1 nil, :clojure.core/a nil, :a/b nil, :d nil})) (is (= (read-string "#:a{b 1 b/c 2}") {'a/b 1, 'b/c 2})) (is (= (binding [*ns* (the-ns 'clojure.test-clojure.reader)] (read-string "#::{b 1, b/c 2, _/d 3}")) {'clojure.test-clojure.reader/b 1, 'b/c 2, 'd 3})) - (is (= (binding [*ns* (the-ns 'clojure.test-clojure.reader)] (read-string "#::s{b 1, b/c 2, _/d 3}")) {'clojure.string/b 1, 'b/c 2, 'd 3})) - (is (= (read-string "#::clojure.core{b 1, b/c 2, _/d 3}") {'clojure.core/b 1, 'b/c 2, 'd 3}))) + (is (= (binding [*ns* (the-ns 'clojure.test-clojure.reader)] (read-string "#::s{b 1, b/c 2, _/d 3}")) {'clojure.string/b 1, 'b/c 2, 'd 3}))) (deftest namespaced-map-errors (are [err msg form] (thrown-with-msg? err msg (read-string form)) Exception #"Invalid token" "#:::" Exception #"Namespaced map literal must contain an even number of forms" "#:s{1}" Exception #"Namespaced map must specify a valid namespace" "#:s/t{1 2}" - Exception #"Namespaced map literal must contain an even number of forms" "#::clojure.core{1}" - Exception #"Namespaced map must specify a valid namespace" "#::clojure.core/t{1 2}" Exception #"Unknown auto-resolved namespace alias" "#::BOGUS{1 2}" - Exception #"Namespaced map must specify a namespace" "#:: clojure.core{:a 1}" - Exception #"Namespaced map must specify a namespace" "#: clojure.core{:a 1}" + Exception #"Namespaced map must specify a namespace" "#: s{:a 1}" Exception #"Duplicate key: :user/a" "#::{:a 1 :a 2}" - Exception #"Duplicate key: :clojure.core/a" "#::clojure.core{:a 1 :a 2}" - Exception #"Duplicate key: user/a" "#::{a 1 a 2}" - Exception #"Duplicate key: clojure.core/+" "#::clojure.core{+ 1 + 2}")) + Exception #"Duplicate key: user/a" "#::{a 1 a 2}")) (deftest namespaced-map-edn (is (= {1 1, :a/b 2, :b/c 3, :d 4} From cfe6d14be41f47c91f63955910709c10f487f39e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 21 Aug 2017 11:01:26 -0400 Subject: [PATCH 340/854] default qualify with resovler.currentNS() in syntax quote --- src/jvm/clojure/lang/LispReader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index c331ac8dc3..82c1074705 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -1025,8 +1025,10 @@ else if(sym.ns == null){ rsym = resolver.resolveVar(sym); if(rsym != null) sym = rsym; + else + sym = Symbol.intern(resolver.currentNS().name,sym.name); } - //leave alone if no resolution + //leave alone if qualified } else { From c569e5874c298a18ca9ce5fbe5133c963bf0f6b3 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Mon, 21 Aug 2017 13:52:59 -0400 Subject: [PATCH 341/854] don't preclude '.' in alias --- src/jvm/clojure/lang/LispReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index 82c1074705..588dcd6353 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -1009,7 +1009,7 @@ else if(sym.ns == null && sym.name.startsWith(".")) else if(resolver != null) { Symbol nsym = null; - if(sym.ns != null && sym.ns.indexOf('.') == -1){ + if(sym.ns != null){ Symbol alias = Symbol.intern(null, sym.ns); nsym = resolver.resolveClass(alias); if(nsym == null) From fb2b62d763c6f7ce33f9edabb0aab015d7197a52 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 23 Aug 2017 08:48:21 -0500 Subject: [PATCH 342/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha18 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3b52bbdb63..a88e921af6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha18 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-alpha18 From 9696403a2976bd6d1fd241555fbc9ff78d924867 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 23 Aug 2017 08:48:21 -0500 Subject: [PATCH 343/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a88e921af6..3b52bbdb63 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha18 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-alpha18 + HEAD From b7756a64b0707b2a8f4ce23ef7d994410083faa9 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 24 Aug 2017 09:17:24 -0400 Subject: [PATCH 344/854] make default imports public --- src/jvm/clojure/lang/RT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 6723298c77..e044e88adf 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -36,7 +36,7 @@ public class RT{ static final public String LOADER_SUFFIX = "__init"; //simple-symbol->class -final static IPersistentMap DEFAULT_IMPORTS = map( +final static public IPersistentMap DEFAULT_IMPORTS = map( // Symbol.intern("RT"), "clojure.lang.RT", // Symbol.intern("Num"), "clojure.lang.Num", // Symbol.intern("Symbol"), "clojure.lang.Symbol", From c99ebfe98be6bbc4c44ed045927bfdf3e6b8dbac Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 24 Aug 2017 08:38:09 -0500 Subject: [PATCH 345/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha19 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3b52bbdb63..607168f17d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha19 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-alpha19 From 8a3f296dd3fa037852cfd4c201a5b2a6606260fc Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 24 Aug 2017 08:38:09 -0500 Subject: [PATCH 346/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 607168f17d..3b52bbdb63 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha19 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-alpha19 + HEAD From 9960566994cb22a3bb79d3e7a6dd459e9420838f Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Fri, 21 Jul 2017 11:06:36 +0100 Subject: [PATCH 347/854] CLJ-2210: cache non-trivial getJavaClass/hasJavaClass to avoid exponential compilation times Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 92 ++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index b5c881f2b4..f15de1effa 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -643,6 +643,8 @@ public static class VarExpr implements Expr, AssignableExpr{ final static Method getMethod = Method.getMethod("Object get()"); final static Method setMethod = Method.getMethod("Object set(Object)"); + Class jc; + public VarExpr(Var var, Symbol tag){ this.var = var; this.tag = tag != null ? tag : var.getTag(); @@ -665,7 +667,9 @@ public boolean hasJavaClass(){ } public Class getJavaClass() { - return HostExpr.tagToClass(tag); + if (jc == null) + jc = HostExpr.tagToClass(tag); + return jc; } public Object evalAssign(Expr val) { @@ -1141,6 +1145,7 @@ static class InstanceFieldExpr extends FieldExpr implements AssignableExpr{ final static Method invokeNoArgInstanceMember = Method.getMethod("Object invokeNoArgInstanceMember(Object,String,boolean)"); final static Method setInstanceFieldMethod = Method.getMethod("Object setInstanceField(Object,String,Object)"); + Class jc; public InstanceFieldExpr(int line, int column, Expr target, String fieldName, Symbol tag, boolean requireField) { this.target = target; @@ -1220,7 +1225,9 @@ public boolean hasJavaClass() { } public Class getJavaClass() { - return tag != null ? HostExpr.tagToClass(tag) : field.getType(); + if (jc == null) + jc = tag != null ? HostExpr.tagToClass(tag) : field.getType(); + return jc; } public Object evalAssign(Expr val) { @@ -1263,6 +1270,8 @@ static class StaticFieldExpr extends FieldExpr implements AssignableExpr{ final int line; final int column; + Class jc; + public StaticFieldExpr(int line, int column, Class c, String fieldName, Symbol tag) { //this.className = className; this.fieldName = fieldName; @@ -1316,7 +1325,9 @@ public boolean hasJavaClass(){ public Class getJavaClass() { //Class c = Class.forName(className); //java.lang.reflect.Field field = c.getField(fieldName); - return tag != null ? HostExpr.tagToClass(tag) : field.getType(); + if (jc == null) + jc =tag != null ? HostExpr.tagToClass(tag) : field.getType(); + return jc; } public Object evalAssign(Expr val) { @@ -1449,6 +1460,7 @@ static class InstanceMethodExpr extends MethodExpr{ public final Symbol tag; public final boolean tailPosition; public final java.lang.reflect.Method method; + Class jc; final static Method invokeInstanceMethodMethod = Method.getMethod("Object invokeInstanceMethod(Object,String,Object[])"); @@ -1617,7 +1629,9 @@ public boolean hasJavaClass(){ } public Class getJavaClass() { - return retType((tag!=null)?HostExpr.tagToClass(tag):null, (method!=null)?method.getReturnType():null); + if (jc == null) + jc = retType((tag!=null)?HostExpr.tagToClass(tag):null, (method!=null)?method.getReturnType():null); + return jc; } } @@ -1637,6 +1651,7 @@ static class StaticMethodExpr extends MethodExpr{ final static Method invokeStaticMethodMethod = Method.getMethod("Object invokeStaticMethod(Class,String,Object[])"); final static Keyword warnOnBoxedKeyword = Keyword.intern("warn-on-boxed"); + Class jc; public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, String methodName, IPersistentVector args, boolean tailPosition) @@ -1833,7 +1848,9 @@ public boolean hasJavaClass(){ } public Class getJavaClass() { - return retType((tag!=null)?HostExpr.tagToClass(tag):null, (method!=null)?method.getReturnType():null); + if (jc == null) + jc = retType((tag!=null)?HostExpr.tagToClass(tag):null, (method!=null)?method.getReturnType():null); + return jc; } } @@ -3268,6 +3285,7 @@ static class KeywordInvokeExpr implements Expr{ public final int siteIndex; public final String source; static Type ILOOKUP_TYPE = Type.getType(ILookup.class); + Class jc; public KeywordInvokeExpr(String source, int line, int column, Symbol tag, KeywordExpr kw, Expr target){ this.source = source; @@ -3332,7 +3350,9 @@ public boolean hasJavaClass() { } public Class getJavaClass() { - return HostExpr.tagToClass(tag); + if(jc == null) + jc = HostExpr.tagToClass(tag); + return jc; } } @@ -3441,6 +3461,7 @@ static class StaticInvokeExpr implements Expr, MaybePrimitiveExpr{ public final boolean variadic; public final boolean tailPosition; public final Object tag; + Class jc; StaticInvokeExpr(Type target, Class retClass, Class[] paramclasses, Type[] paramtypes, boolean variadic, IPersistentVector args,Object tag, boolean tailPosition){ @@ -3476,7 +3497,9 @@ public boolean hasJavaClass() { } public Class getJavaClass() { - return retType((tag!=null)?HostExpr.tagToClass(tag):null, retClass); + if(jc == null) + jc =retType((tag!=null)?HostExpr.tagToClass(tag):null, retClass); + return jc; } public boolean canEmitPrimitive(){ @@ -3596,6 +3619,7 @@ static class InvokeExpr implements Expr{ public java.lang.reflect.Method onMethod; static Keyword onKey = Keyword.intern("on"); static Keyword methodMapKey = Keyword.intern("method-map"); + Class jc; static Object sigTag(int argcount, Var v){ Object arglists = RT.get(RT.meta(v), arglistsKey); @@ -3777,7 +3801,9 @@ public boolean hasJavaClass() { } public Class getJavaClass() { - return HostExpr.tagToClass(tag); + if (jc == null) + jc = HostExpr.tagToClass(tag); + return jc; } static public Expr parse(C context, ISeq form) { @@ -3883,6 +3909,7 @@ static public class FnExpr extends ObjExpr{ private boolean hasMeta; private boolean hasEnclosingMethod; // String superName = null; + Class jc; public FnExpr(Object tag){ super(tag); @@ -3897,7 +3924,9 @@ boolean supportsMeta(){ } public Class getJavaClass() { - return tag != null ? HostExpr.tagToClass(tag) : AFunction.class; + if (jc == null) + jc = tag != null ? HostExpr.tagToClass(tag) : AFunction.class; + return jc; } protected void emitMethods(ClassVisitor cv){ @@ -5025,10 +5054,13 @@ public boolean hasJavaClass() { return true; } + Class jc; public Class getJavaClass() { - return (compiledClass != null) ? compiledClass - : (tag != null) ? HostExpr.tagToClass(tag) - : IFn.class; + if (jc == null) + jc = (compiledClass != null) ? compiledClass + : (tag != null) ? HostExpr.tagToClass(tag) + : IFn.class; + return jc; } public void emitAssignLocal(GeneratorAdapter gen, LocalBinding lb,Expr val){ @@ -5926,18 +5958,25 @@ public LocalBinding(int num, Symbol sym, Symbol tag, Expr init, boolean isArg,Pa name = munge(sym.name); } + Boolean hjc; + public boolean hasJavaClass() { - if(init != null && init.hasJavaClass() - && Util.isPrimitive(init.getJavaClass()) - && !(init instanceof MaybePrimitiveExpr)) - return false; - return tag != null - || (init != null && init.hasJavaClass()); - } + if (hjc == null) + { + if(init != null && init.hasJavaClass() && Util.isPrimitive(init.getJavaClass()) && !(init instanceof MaybePrimitiveExpr)) + hjc = false; + else + hjc = tag != null || (init != null && init.hasJavaClass()); + } + return hjc; + } + + Class jc; public Class getJavaClass() { - return tag != null ? HostExpr.tagToClass(tag) - : init.getJavaClass(); + if (jc == null) + jc = tag != null ? HostExpr.tagToClass(tag) : init.getJavaClass(); + return jc; } public Class getPrimitiveType(){ @@ -6025,10 +6064,15 @@ public boolean hasJavaClass() { return tag != null || b.hasJavaClass(); } + Class jc; public Class getJavaClass() { - if(tag != null) - return HostExpr.tagToClass(tag); - return b.getJavaClass(); + if (jc == null) { + if(tag != null) + jc = HostExpr.tagToClass(tag); + else + jc = b.getJavaClass(); + } + return jc; } From 271674c9b484d798484d134a5ac40a6df15d3ac3 Mon Sep 17 00:00:00 2001 From: Chouser Date: Mon, 17 Jul 2017 23:04:41 -0400 Subject: [PATCH 348/854] CLJ-2204 Disable serialization of proxy classes Signed-off-by: Stuart Halloway --- src/clj/clojure/core_proxy.clj | 20 +++++++++++++- test/clojure/test_clojure/java_interop.clj | 31 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj index 813c8bbe83..70d0528f27 100644 --- a/src/clj/clojure/core_proxy.clj +++ b/src/clj/clojure/core_proxy.clj @@ -13,6 +13,7 @@ (import '(clojure.asm ClassWriter ClassVisitor Opcodes Type) '(java.lang.reflect Modifier Constructor) + '(java.io Serializable NotSerializableException) '(clojure.asm.commons Method GeneratorAdapter) '(clojure.lang IProxy Reflector DynamicClassLoader IPersistentMap PersistentHashMap RT)) @@ -44,7 +45,8 @@ (defn- generate-proxy [^Class super interfaces] (let [cv (new ClassWriter (. ClassWriter COMPUTE_MAXS)) - cname (.replace (proxy-name super interfaces) \. \/) ;(str "clojure/lang/" (gensym "Proxy__")) + pname (proxy-name super interfaces) + cname (.replace pname \. \/) ;(str "clojure/lang/" (gensym "Proxy__")) ctype (. Type (getObjectType cname)) iname (fn [^Class c] (.. Type (getType c) (getInternalName))) fmap "__clojureFnMap" @@ -148,6 +150,22 @@ (. gen (returnValue)) (. gen (endMethod))))) + ;disable serialization + (when (some #(isa? % Serializable) (cons super interfaces)) + (let [m (. Method (getMethod "void writeObject(java.io.ObjectOutputStream)")) + gen (new GeneratorAdapter (. Opcodes ACC_PRIVATE) m nil nil cv)] + (. gen (visitCode)) + (. gen (loadThis)) + (. gen (loadArgs)) + (. gen (throwException (totype NotSerializableException) pname)) + (. gen (endMethod))) + (let [m (. Method (getMethod "void readObject(java.io.ObjectInputStream)")) + gen (new GeneratorAdapter (. Opcodes ACC_PRIVATE) m nil nil cv)] + (. gen (visitCode)) + (. gen (loadThis)) + (. gen (loadArgs)) + (. gen (throwException (totype NotSerializableException) pname)) + (. gen (endMethod)))) ;add IProxy methods (let [m (. Method (getMethod "void __initClojureFnMappings(clojure.lang.IPersistentMap)")) gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)] diff --git a/test/clojure/test_clojure/java_interop.clj b/test/clojure/test_clojure/java_interop.clj index 86ba5ca63e..44b5c70792 100644 --- a/test/clojure/test_clojure/java_interop.clj +++ b/test/clojure/test_clojure/java_interop.clj @@ -10,7 +10,8 @@ (ns clojure.test-clojure.java-interop - (:use clojure.test)) + (:use clojure.test) + (:require [clojure.inspector])) ; http://clojure.org/java_interop ; http://clojure.org/compilation @@ -171,6 +172,34 @@ "chain chain chain"))) +;; serialized-proxy can be regenerated using a modified version of +;; Clojure with the proxy serialization prohibition disabled and the +;; following code: +#_(let [baos (java.io.ByteArrayOutputStream.) ] + (with-open [baos baos] + (.writeObject (java.io.ObjectOutputStream. baos) (clojure.inspector/list-model nil))) + (println (apply str (for [c (String. (.toByteArray baos) "ISO-8859-1")] + (if (<= 32 (int c) (int \z)) c (format "\\%03o" (int c))))))) +(def serialized-proxy "\254\355\000\005sr\000Eclojure.inspector.proxy$javax.swing.table.AbstractTableModel$ff19274art\330\266_\010ME\002\000\001L\000\016__clojureFnMapt\000\035Lclojure/lang/IPersistentMap;xr\000$javax.swing.table.AbstractTableModelr\313\3538\256\001\377\276\002\000\001L\000\014listenerListt\000%Ljavax/swing/event/EventListenerList;xpsr\000#javax.swing.event.EventListenerList\2616\306\175\204\352\326D\003\000\000xppxsr\000\037clojure.lang.PersistentArrayMap\3437p\017\230\305\364\337\002\000\002L\000\005_metaq\000\176\000\001[\000\005arrayt\000\023[Ljava/lang/Object;xr\000\033clojure.lang.APersistentMap]\174/\003t r\173\002\000\002I\000\005_hashI\000\007_hasheqxp\000\000\000\000\000\000\000\000pur\000\023[Ljava.lang.Object;\220\316X\237\020s)l\002\000\000xp\000\000\000\006t\000\016getColumnCountsr\000%clojure.inspector$list_model$fn__8816H\252\320\325b\371!+\002\000\000xr\000\026clojure.lang.AFunction>\006p\234\236F\375\313\002\000\001L\000\021__methodImplCachet\000\036Lclojure/lang/MethodImplCache;xppt\000\013getRowCountsr\000%clojure.inspector$list_model$fn__8818-\037I\247\234/U\226\002\000\001L\000\005nrowst\000\022Ljava/lang/Object;xq\000\176\000\017ppt\000\012getValueAtsr\000%clojure.inspector$list_model$fn__8820\323\331\174ke\233\370\034\002\000\002L\000\011get_labelq\000\176\000\024L\000\011get_valueq\000\176\000\024xq\000\176\000\017ppp") + +(deftest test-proxy-non-serializable + (testing "That proxy classes refuse serialization and deserialization" + ;; Serializable listed directly in interface list: + (is (thrown? java.io.NotSerializableException + (-> (java.io.ByteArrayOutputStream.) + (java.io.ObjectOutputStream.) + (.writeObject (proxy [Object java.io.Serializable] []))))) + ;; Serializable included via inheritence: + (is (thrown? java.io.NotSerializableException + (-> (java.io.ByteArrayOutputStream.) + (java.io.ObjectOutputStream.) + (.writeObject (clojure.inspector/list-model nil))))) + ;; Deserialization also prohibited: + (is (thrown? java.io.NotSerializableException + (-> serialized-proxy (.getBytes "ISO-8859-1") + java.io.ByteArrayInputStream. java.io.ObjectInputStream. + .readObject))))) + (deftest test-bases (are [x y] (= x y) (bases java.lang.Math) From c3be1aab7f857cd2bc5ad8a8fac1603e87d9f021 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 30 Aug 2017 09:50:23 -0500 Subject: [PATCH 349/854] CLJ-2108 - delay loading of spec and core specs Signed-off-by: Stuart Halloway --- src/clj/clojure/main.clj | 1 + src/jvm/clojure/lang/Compile.java | 5 ++- src/jvm/clojure/lang/Compiler.java | 49 +++++++++++++++++++----------- src/jvm/clojure/lang/RT.java | 6 ++-- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj index 3394f6beec..53b1dc3fa0 100644 --- a/src/clj/clojure/main.clj +++ b/src/clj/clojure/main.clj @@ -12,6 +12,7 @@ :author "Stephen C. Gilardi and Rich Hickey"} clojure.main (:refer-clojure :exclude [with-bindings]) + (:require [clojure.spec.alpha]) (:import (clojure.lang Compiler Compiler$CompilerException LineNumberingPushbackReader RT)) ;;(:use [clojure.repl :only (demunge root-cause stack-element-str)]) diff --git a/src/jvm/clojure/lang/Compile.java b/src/jvm/clojure/lang/Compile.java index c99dae808c..1396be3b27 100644 --- a/src/jvm/clojure/lang/Compile.java +++ b/src/jvm/clojure/lang/Compile.java @@ -31,7 +31,7 @@ public class Compile{ private static final Var warn_on_reflection = RT.var("clojure.core", "*warn-on-reflection*"); private static final Var unchecked_math = RT.var("clojure.core", "*unchecked-math*"); -public static void main(String[] args) throws IOException{ +public static void main(String[] args) throws IOException, ClassNotFoundException{ OutputStreamWriter out = (OutputStreamWriter) RT.OUT.deref(); PrintWriter err = RT.errPrintWriter(); @@ -54,6 +54,9 @@ public static void main(String[] args) throws IOException{ else if("warn-on-boxed".equals(uncheckedMathProp)) uncheckedMath = Keyword.intern("warn-on-boxed"); + // force load to avoid transitive compilation during lazy load + RT.load("clojure/core/specs/alpha"); + try { Var.pushThreadBindings(RT.map(compile_path, path, diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index f15de1effa..db1a2a7f18 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -6864,6 +6864,35 @@ public static Object preserveTag(ISeq src, Object dst) { return dst; } +private static volatile Var MACRO_CHECK = null; +private static volatile boolean MACRO_CHECK_LOADING = false; +private static final Object MACRO_CHECK_LOCK = new Object(); + +private static Var ensureMacroCheck() throws ClassNotFoundException, IOException { + if(MACRO_CHECK == null) { + synchronized(MACRO_CHECK_LOCK) { + if(MACRO_CHECK == null) { + MACRO_CHECK_LOADING = true; + RT.load("clojure/spec/alpha"); + RT.load("clojure/core/specs/alpha"); + MACRO_CHECK = Var.find(Symbol.intern("clojure.spec.alpha", "macroexpand-check")); + MACRO_CHECK_LOADING = false; + } + } + } + return MACRO_CHECK; +} + +public static void checkSpecs(Var v, ISeq form) { + if(RT.CHECK_SPECS && !MACRO_CHECK_LOADING) { + try { + ensureMacroCheck().applyTo(RT.cons(v, RT.list(form.next()))); + } catch(Exception e) { + throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e); + } + } +} + public static Object macroexpand1(Object x) { if(x instanceof ISeq) { @@ -6875,24 +6904,8 @@ public static Object macroexpand1(Object x) { Var v = isMacro(op); if(v != null) { - // Do not check specs while inside clojure.spec.alpha - if(! "clojure/spec/alpha.clj".equals(SOURCE_PATH.deref())) - { - try - { - final Namespace checkns = Namespace.find(Symbol.intern("clojure.spec.alpha")); - if (checkns != null) - { - final Var check = Var.find(Symbol.intern("clojure.spec.alpha/macroexpand-check")); - if ((check != null) && (check.isBound())) - check.applyTo(RT.cons(v, RT.list(form.next()))); - } - } - catch(Exception e) - { - throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e); - } - } + checkSpecs(v, form); + try { ISeq args = RT.cons(form, RT.cons(Compiler.LOCAL_ENV.get(), form.next())); diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index e044e88adf..3c835e3f96 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -301,6 +301,8 @@ static public void addURL(Object url) throws MalformedURLException{ public static boolean checkSpecAsserts = Boolean.getBoolean("clojure.spec.check-asserts"); +static volatile boolean CHECK_SPECS = false; + static{ Keyword arglistskw = Keyword.intern(null, "arglists"); Symbol namesym = Symbol.intern("name"); @@ -336,6 +338,8 @@ public Object invoke(Object arg1) { catch(Exception e) { throw Util.sneakyThrow(e); } + + CHECK_SPECS = true; } static public Keyword keyword(String ns, String name){ @@ -462,8 +466,6 @@ else if(!loaded && failIfNotFound) static void doInit() throws ClassNotFoundException, IOException{ load("clojure/core"); - load("clojure/spec/alpha"); - load("clojure/core/specs/alpha"); Var.pushThreadBindings( RT.mapUniqueKeys(CURRENT_NS, CURRENT_NS.deref(), From 9048707079e2450ad7c3f672794926f401e2a0e8 Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Wed, 30 Nov 2016 00:13:04 +0800 Subject: [PATCH 350/854] CLJ-2070: faster clojure.core/delay Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Delay.java | 33 +++++++++++-------- test/clojure/test_clojure/delays.clj | 49 +++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/jvm/clojure/lang/Delay.java b/src/jvm/clojure/lang/Delay.java index ef8110b92c..262c9c1a43 100644 --- a/src/jvm/clojure/lang/Delay.java +++ b/src/jvm/clojure/lang/Delay.java @@ -13,9 +13,9 @@ package clojure.lang; public class Delay implements IDeref, IPending{ -Object val; -Throwable exception; -IFn fn; +volatile Object val; +volatile Throwable exception; +volatile IFn fn; public Delay(IFn fn){ this.fn = fn; @@ -29,18 +29,25 @@ static public Object force(Object x) { : x; } -synchronized public Object deref() { +public Object deref() { if(fn != null) { - try - { - val = fn.invoke(); - } - catch(Throwable t) - { - exception = t; - } - fn = null; + synchronized(this) + { + //double check + if(fn!=null) + { + try + { + val = fn.invoke(); + } + catch(Throwable t) + { + exception = t; + } + fn = null; + } + } } if(exception != null) throw Util.sneakyThrow(exception); diff --git a/test/clojure/test_clojure/delays.clj b/test/clojure/test_clojure/delays.clj index 0de3341001..0a2a1c99ff 100644 --- a/test/clojure/test_clojure/delays.clj +++ b/test/clojure/test_clojure/delays.clj @@ -1,5 +1,6 @@ (ns clojure.test-clojure.delays - (:use clojure.test)) + (:use clojure.test) + (:import [java.util.concurrent CyclicBarrier])) (deftest calls-once (let [a (atom 0) @@ -9,6 +10,27 @@ (is (= 1 @d)) (is (= 1 @a)))) +(deftest calls-once-in-parallel + (let [a (atom 0) + d (delay (swap! a inc)) + threads 100 + ^CyclicBarrier barrier (CyclicBarrier. (+ threads 1))] + (is (= 0 @a)) + (dotimes [_ threads] + (-> + (Thread. + (fn [] + (.await barrier) + (dotimes [_ 10000] + (is (= 1 @d))) + (.await barrier))) + (.start))) + (.await barrier) + (.await barrier) + (is (= 1 @d)) + (is (= 1 @d)) + (is (= 1 @a)))) + (deftest saves-exceptions (let [f #(do (throw (Exception. "broken")) 1) @@ -19,3 +41,28 @@ first-result (try-call)] (is (instance? Exception first-result)) (is (identical? first-result (try-call))))) + +(deftest saves-exceptions-in-parallel + (let [f #(do (throw (Exception. "broken")) + 1) + d (delay (f)) + try-call #(try + @d + (catch Exception e e)) + threads 100 + ^CyclicBarrier barrier (CyclicBarrier. (+ threads 1))] + (dotimes [_ threads] + (-> + (Thread. + (fn [] + (.await barrier) + (let [first-result (try-call)] + (dotimes [_ 10000] + (is (instance? Exception (try-call))) + (is (identical? first-result (try-call))))) + (.await barrier))) + (.start))) + (.await barrier) + (.await barrier) + (is (instance? Exception (try-call))) + (is (identical? (try-call) (try-call))))) From e32471325e715bcb3e4c56bbe66e897d6f3a88b8 Mon Sep 17 00:00:00 2001 From: Gerrit Jansen van Vuuren Date: Fri, 21 Oct 2016 16:09:00 +0200 Subject: [PATCH 351/854] CLJ-2048 add StackTraceElement to throw-if into-array to avoid classcastexception on [Object array when stack trace is nil Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 177f8c1a58..ab789fdad0 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5763,7 +5763,7 @@ exception (Exception. message) raw-trace (.getStackTrace exception) boring? #(not= (.getMethodName ^StackTraceElement %) "doInvoke") - trace (into-array (drop 2 (drop-while boring? raw-trace)))] + trace (into-array StackTraceElement (drop 2 (drop-while boring? raw-trace)))] (.setStackTrace exception trace) (throw (clojure.lang.Compiler$CompilerException. *file* @@ -7718,4 +7718,4 @@ (defn uri? "Return true if x is a java.net.URI" {:added "1.9"} - [x] (instance? java.net.URI x)) \ No newline at end of file + [x] (instance? java.net.URI x)) From 2b242f943b9a74e753b7ee1b951a8699966ea560 Mon Sep 17 00:00:00 2001 From: Jozef Wagner Date: Wed, 6 Sep 2017 09:25:33 -0500 Subject: [PATCH 352/854] CLJ-1917 Call String/length outside of a loop in the internal-reduce extended on StringSeq Signed-off-by: Stuart Halloway --- src/clj/clojure/core/protocols.clj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core/protocols.clj b/src/clj/clojure/core/protocols.clj index 5c68ba359d..e3bedb259f 100644 --- a/src/clj/clojure/core/protocols.clj +++ b/src/clj/clojure/core/protocols.clj @@ -145,10 +145,11 @@ clojure.lang.StringSeq (internal-reduce [str-seq f val] - (let [s (.s str-seq)] + (let [s (.s str-seq) + len (.length s)] (loop [i (.i str-seq) val val] - (if (< i (.length s)) + (if (< i len) (let [ret (f val (.charAt s i))] (if (reduced? ret) @ret From 244aec97eb32642977ffd7f8ed77f263918beed7 Mon Sep 17 00:00:00 2001 From: jimpil Date: Sun, 13 Mar 2016 14:35:55 +0000 Subject: [PATCH 353/854] amap calls alength once Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index ab789fdad0..d3168dacc4 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5157,10 +5157,10 @@ array ret." {:added "1.0"} [a idx ret expr] - `(let [a# ~a + `(let [a# ~a l# (alength a#) ~ret (aclone a#)] (loop [~idx 0] - (if (< ~idx (alength a#)) + (if (< ~idx l#) (do (aset ~ret ~idx ~expr) (recur (unchecked-inc ~idx))) From 46eb144b12d1b5dbf543384984a57a4dfa3d8531 Mon Sep 17 00:00:00 2001 From: Steffen Dienst Date: Tue, 26 Jan 2016 10:45:45 +0100 Subject: [PATCH 354/854] CLJ-1887 Implement missing IPersistentVector method .length Signed-off-by: Stuart Halloway --- src/clj/clojure/gvec.clj | 1 + test/clojure/test_clojure/vectors.clj | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/gvec.clj b/src/clj/clojure/gvec.clj index dbfe9282e7..b60f9a4824 100644 --- a/src/clj/clojure/gvec.clj +++ b/src/clj/clojure/gvec.clj @@ -249,6 +249,7 @@ (new Vec am cnt shift (.doAssoc this shift root i val) tail (meta this))) (= i cnt) (.cons this val) :else (throw (IndexOutOfBoundsException.)))) + (length [_] cnt) clojure.lang.Reversible (rseq [this] diff --git a/test/clojure/test_clojure/vectors.clj b/test/clojure/test_clojure/vectors.clj index d9faccb11d..232b2c9386 100644 --- a/test/clojure/test_clojure/vectors.clj +++ b/test/clojure/test_clojure/vectors.clj @@ -360,7 +360,15 @@ (vector-of :int #{1 2 3 4}) (vector-of :int (sorted-set 1 2 3 4)) (vector-of :int 1 2 "3") - (vector-of :int "1" "2" "3"))))) + (vector-of :int "1" "2" "3"))) + (testing "instances of IPersistentVector" + (are [gvec] (instance? clojure.lang.IPersistentVector gvec) + (vector-of :int 1 2 3) + (vector-of :double 1 2 3))) + (testing "fully implements IPersistentVector" + (are [gvec] (= 3 (.length gvec)) + (vector-of :int 1 2 3) + (vector-of :double 1 2 3))))) (deftest empty-vector-equality (let [colls [[] (vector-of :long) '()]] From 31b197110d1498d3523a698fa06975e89ecc6f30 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 22 Jan 2016 14:38:22 -0600 Subject: [PATCH 355/854] CLJ-1841 bean iterator was broken, now matches seq data Signed-off-by: Stuart Halloway --- src/clj/clojure/core_proxy.clj | 15 ++++++++------- test/clojure/test_clojure/java_interop.clj | 6 ++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj index 70d0528f27..efa2437b1a 100644 --- a/src/clj/clojure/core_proxy.clj +++ b/src/clj/clojure/core_proxy.clj @@ -413,10 +413,15 @@ snapshot (fn [] (reduce1 (fn [m e] (assoc m (key e) ((val e)))) - {} (seq pmap)))] + {} (seq pmap))) + thisfn (fn thisfn [plseq] + (lazy-seq + (when-let [pseq (seq plseq)] + (cons (clojure.lang.MapEntry/create (first pseq) (v (first pseq))) + (thisfn (rest pseq))))))] (proxy [clojure.lang.APersistentMap] [] - (iterator [] (.iterator ^Iterable pmap)) + (iterator [] (clojure.lang.SeqIterator. ^java.util.Iterator (thisfn (keys pmap)))) (containsKey [k] (contains? pmap k)) (entryAt [k] (when (contains? pmap k) (clojure.lang.MapEntry/create k (v k)))) (valAt ([k] (when (contains? pmap k) (v k))) @@ -425,11 +430,7 @@ (count [] (count pmap)) (assoc [k v] (assoc (snapshot) k v)) (without [k] (dissoc (snapshot) k)) - (seq [] ((fn thisfn [plseq] - (lazy-seq - (when-let [pseq (seq plseq)] - (cons (clojure.lang.MapEntry/create (first pseq) (v (first pseq))) - (thisfn (rest pseq)))))) (keys pmap)))))) + (seq [] (thisfn (keys pmap)))))) diff --git a/test/clojure/test_clojure/java_interop.clj b/test/clojure/test_clojure/java_interop.clj index 44b5c70792..223d6efd60 100644 --- a/test/clojure/test_clojure/java_interop.clj +++ b/test/clojure/test_clojure/java_interop.clj @@ -151,8 +151,10 @@ (:class b) java.awt.Color ))) (deftest test-iterable-bean - (is (.iterator ^Iterable (bean (java.util.Date.)))) - (is (hash (bean (java.util.Date.))))) + (let [b (bean (java.util.Date.))] + (is (.iterator ^Iterable b)) + (is (= (into [] b) (into [] (seq b)))) + (is (hash b)))) ; proxy, proxy-super From 9bca33911e130d3962dd75a13dbede9d49eea1f6 Mon Sep 17 00:00:00 2001 From: Adam Clements Date: Wed, 28 Sep 2016 14:37:12 -0500 Subject: [PATCH 356/854] CLJ-1714 Using a class in a type hint shouldn't cause the static initialiser to be executed Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Compiler.java | 6 +++--- test/clojure/test_clojure/compilation.clj | 6 ++++++ .../ClassWithFailingStaticInitialiser.java | 13 +++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 test/java/compilation/ClassWithFailingStaticInitialiser.java diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index db1a2a7f18..faa9f4b57c 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -1038,7 +1038,7 @@ public static Class maybeClass(Object form, boolean stringOk) { if(Util.equals(sym,COMPILE_STUB_SYM.get())) return (Class) COMPILE_STUB_CLASS.get(); if(sym.name.indexOf('.') > 0 || sym.name.charAt(0) == '[') - c = RT.classForName(sym.name); + c = RT.classForNameNonLoading(sym.name); else { Object o = currentNS().getMapping(sym); @@ -1049,7 +1049,7 @@ else if(LOCAL_ENV.deref() != null && ((java.util.Map)LOCAL_ENV.deref()).contains else { try{ - c = RT.classForName(sym.name); + c = RT.classForNameNonLoading(sym.name); } catch(Exception e){ // aargh @@ -1060,7 +1060,7 @@ else if(LOCAL_ENV.deref() != null && ((java.util.Map)LOCAL_ENV.deref()).contains } } else if(stringOk && form instanceof String) - c = RT.classForName((String) form); + c = RT.classForNameNonLoading((String) form); return c; } diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index df0f995d15..11a57372c6 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -422,3 +422,9 @@ ;; eventually call `load` and reset called?. (require 'clojure.repl :reload)) (is @called?))) + +(deftest clj-1714 + (testing "CLJ-1714 Classes shouldn't have their static initialisers called simply by type hinting or importing" + ;; ClassWithFailingStaticInitialiser will throw if its static initialiser is called + (is (eval '(fn [^compilation.ClassWithFailingStaticInitialiser c]))) + (is (eval '(import (compilation ClassWithFailingStaticInitialiser)))))) diff --git a/test/java/compilation/ClassWithFailingStaticInitialiser.java b/test/java/compilation/ClassWithFailingStaticInitialiser.java new file mode 100644 index 0000000000..6e6c8f7ea3 --- /dev/null +++ b/test/java/compilation/ClassWithFailingStaticInitialiser.java @@ -0,0 +1,13 @@ +package compilation; + +public class ClassWithFailingStaticInitialiser { + static { + // Static analysis refuses to compile a static initialiser + // which will always throw, so we pretend to branch. This may + // need updating if the static analysis gets cleverer in the + // future + if(true) { + throw new AssertionError("Static Initialiser was run when it shouldn't have been"); + } + } +} From 2dafcd3b11ad9d8a911a32c0a62b637c43ed0588 Mon Sep 17 00:00:00 2001 From: Eli Lindsey Date: Mon, 1 Feb 2016 06:08:14 -0600 Subject: [PATCH 357/854] CLJ-1398 update javadoc urls Signed-off-by: Stuart Halloway --- src/clj/clojure/java/javadoc.clj | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/clj/clojure/java/javadoc.clj b/src/clj/clojure/java/javadoc.clj index 36527097c0..4eea1ec2e1 100644 --- a/src/clj/clojure/java/javadoc.clj +++ b/src/clj/clojure/java/javadoc.clj @@ -20,20 +20,24 @@ (def ^:dynamic *core-java-api* (case (System/getProperty "java.specification.version") - "1.6" "http://java.sun.com/javase/6/docs/api/" - "http://java.sun.com/javase/7/docs/api/")) + "1.6" "http://docs.oracle.com/javase/6/docs/api/" + "1.7" "http://docs.oracle.com/javase/7/docs/api/" + "1.8" "http://docs.oracle.com/javase/8/docs/api/" + "http://docs.oracle.com/javase/8/docs/api/")) (def ^:dynamic *remote-javadocs* (ref (sorted-map + "com.google.common." "http://docs.guava-libraries.googlecode.com/git/javadoc/" "java." *core-java-api* "javax." *core-java-api* "org.ietf.jgss." *core-java-api* "org.omg." *core-java-api* "org.w3c.dom." *core-java-api* "org.xml.sax." *core-java-api* - "org.apache.commons.codec." "http://commons.apache.org/codec/api-release/" - "org.apache.commons.io." "http://commons.apache.org/io/api-release/" - "org.apache.commons.lang." "http://commons.apache.org/lang/api-release/"))) + "org.apache.commons.codec." "http://commons.apache.org/proper/commons-codec/apidocs/" + "org.apache.commons.io." "http://commons.apache.org/proper/commons-io/javadocs/api-release/" + "org.apache.commons.lang." "http://commons.apache.org/proper/commons-lang/javadocs/api-2.6/" + "org.apache.commons.lang3." "http://commons.apache.org/proper/commons-lang/javadocs/api-release/"))) (defn add-local-javadoc "Adds to the list of local Javadoc paths." From d10a9d36ef91e1f329528890d6fc70471d78485d Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Thu, 15 Nov 2012 19:33:47 -0800 Subject: [PATCH 358/854] CLJ-99: Make min-key and max-key evaluate k on each argument at most once Previous versions of these functions evaluated k on most argument twice if there were 3 or more. Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 22 +++++++++++++++++-- test/clojure/test_clojure/other_functions.clj | 10 +++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index d3168dacc4..8d90af4a71 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -4919,7 +4919,16 @@ ([k x] x) ([k x y] (if (> (k x) (k y)) x y)) ([k x y & more] - (reduce1 #(max-key k %1 %2) (max-key k x y) more))) + (let [kx (k x) ky (k y) + [v kv] (if (> kx ky) [x kx] [y ky])] + (loop [v v kv kv more more] + (if more + (let [w (first more) + kw (k w)] + (if (> kw kv) + (recur w kw (next more)) + (recur v kv (next more)))) + v))))) (defn min-key "Returns the x for which (k x), a number, is least." @@ -4928,7 +4937,16 @@ ([k x] x) ([k x y] (if (< (k x) (k y)) x y)) ([k x y & more] - (reduce1 #(min-key k %1 %2) (min-key k x y) more))) + (let [kx (k x) ky (k y) + [v kv] (if (< kx ky) [x kx] [y ky])] + (loop [v v kv kv more more] + (if more + (let [w (first more) + kw (k w)] + (if (< kw kv) + (recur w kw (next more)) + (recur v kv (next more)))) + v))))) (defn distinct "Returns a lazy sequence of the elements of coll with duplicates removed. diff --git a/test/clojure/test_clojure/other_functions.clj b/test/clojure/test_clojure/other_functions.clj index f5d438d971..94ce9d70d8 100644 --- a/test/clojure/test_clojure/other_functions.clj +++ b/test/clojure/test_clojure/other_functions.clj @@ -328,6 +328,16 @@ (apply (apply some-fn (repeat i (comp not boolean))) (range i)))) true)))) + +(deftest test-max-min-key + (are [k coll min-item max-item] (and (= min-item (apply min-key k coll)) + (= max-item (apply max-key k coll))) + count ["longest" "a" "xy" "foo" "bar"] "a" "longest" + - [5 10 15 20 25] 25 5 + #(if (neg? %) (- %) %) [-2 -1 0 1 2 3 4] 0 4 + {nil 1 false -1 true 0} [true true false nil] false nil)) + + ; Printing ; pr prn print println newline ; pr-str prn-str print-str println-str [with-out-str (vars.clj)] From 2a08ef40018f1b504602545603eec840b05b33b1 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 6 Sep 2017 09:11:15 -0500 Subject: [PATCH 359/854] CLJ-1371 Add checks in divide(Object, Object) to check for NaN Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/Numbers.java | 5 ++ test/clojure/test_clojure/numbers.clj | 70 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/jvm/clojure/lang/Numbers.java b/src/jvm/clojure/lang/Numbers.java index ae42676dd6..0440aebd48 100644 --- a/src/jvm/clojure/lang/Numbers.java +++ b/src/jvm/clojure/lang/Numbers.java @@ -153,6 +153,11 @@ static public Number multiplyP(Object x, Object y){ } static public Number divide(Object x, Object y){ + if (isNaN(x)){ + return (Number)x; + } else if(isNaN(y)){ + return (Number)y; + } Ops yops = ops(y); if(yops.isZero((Number)y)) throw new ArithmeticException("Divide by zero"); diff --git a/test/clojure/test_clojure/numbers.clj b/test/clojure/test_clojure/numbers.clj index b3d142355a..c315caa5aa 100644 --- a/test/clojure/test_clojure/numbers.clj +++ b/test/clojure/test_clojure/numbers.clj @@ -809,3 +809,73 @@ Math/pow overflows to Infinity." (<= 1000 Double/NaN) (<= 1000 (Double. Double/NaN)) (> 1000 Double/NaN) (> 1000 (Double. Double/NaN)) (>= 1000 Double/NaN) (>= 1000 (Double. Double/NaN)))) + +(deftest test-nan-as-operand + (testing "All numeric operations with NaN as an operand produce NaN as a result" + (let [nan Double/NaN + onan (cast Object Double/NaN)] + (are [x] (Double/isNaN x) + (+ nan 1) + (+ nan 0) + (+ nan 0.0) + (+ 1 nan) + (+ 0 nan) + (+ 0.0 nan) + (+ nan nan) + (- nan 1) + (- nan 0) + (- nan 0.0) + (- 1 nan) + (- 0 nan) + (- 0.0 nan) + (- nan nan) + (* nan 1) + (* nan 0) + (* nan 0.0) + (* 1 nan) + (* 0 nan) + (* 0.0 nan) + (* nan nan) + (/ nan 1) + (/ nan 0) + (/ nan 0.0) + (/ 1 nan) + (/ 0 nan) + (/ 0.0 nan) + (/ nan nan) + (+ onan 1) + (+ onan 0) + (+ onan 0.0) + (+ 1 onan) + (+ 0 onan) + (+ 0.0 onan) + (+ onan onan) + (- onan 1) + (- onan 0) + (- onan 0.0) + (- 1 onan) + (- 0 onan) + (- 0.0 onan) + (- onan onan) + (* onan 1) + (* onan 0) + (* onan 0.0) + (* 1 onan) + (* 0 onan) + (* 0.0 onan) + (* onan onan) + (/ onan 1) + (/ onan 0) + (/ onan 0.0) + (/ 1 onan) + (/ 0 onan) + (/ 0.0 onan) + (/ onan onan) + (+ nan onan) + (+ onan nan) + (- nan onan) + (- onan nan) + (* nan onan) + (* onan nan) + (/ nan onan) + (/ onan nan) )))) From eac8de58b0a552f6f34f2803bf8a3bbb4ec2e257 Mon Sep 17 00:00:00 2001 From: Cameron Desautels Date: Mon, 29 May 2017 22:17:44 +0800 Subject: [PATCH 360/854] Fix improperly located docstrings Signed-off-by: Stuart Halloway --- src/clj/clojure/core_proxy.clj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj index efa2437b1a..de08a730c8 100644 --- a/src/clj/clojure/core_proxy.clj +++ b/src/clj/clojure/core_proxy.clj @@ -24,8 +24,10 @@ (or (some (fn [t] (when (every? #(isa? t %) rtypes) t)) rtypes) (throw (Exception. "Incompatible return types")))) -(defn- group-by-sig [coll] - "takes a collection of [msig meth] and returns a seq of maps from return-types to meths." +(defn- group-by-sig + "Takes a collection of [msig meth] and returns a seq of maps from + return-types to meths." + [coll] (vals (reduce1 (fn [m [msig meth]] (let [rtype (peek msig) argsig (pop msig)] From c80d9b01ae58939e1bcf93a4a2a0089369cec2a1 Mon Sep 17 00:00:00 2001 From: Yegor Timoshenko Date: Sat, 22 Apr 2017 14:40:46 +0000 Subject: [PATCH 361/854] Document char[] input support in clojure.java.io/copy Signed-off-by: Stuart Halloway --- src/clj/clojure/java/io.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/java/io.clj b/src/clj/clojure/java/io.clj index 948561a0c3..d9e67f2e98 100644 --- a/src/clj/clojure/java/io.clj +++ b/src/clj/clojure/java/io.clj @@ -390,7 +390,7 @@ (defn copy "Copies input to output. Returns nil or throws IOException. - Input may be an InputStream, Reader, File, byte[], or String. + Input may be an InputStream, Reader, File, byte[], char[], or String. Output may be an OutputStream, Writer, or File. Options are key/value pairs and may be one of From b13c45dfaa4de1691284cc7f99daa1872f868367 Mon Sep 17 00:00:00 2001 From: Greg Leppert Date: Wed, 2 Nov 2016 19:03:54 -0400 Subject: [PATCH 362/854] Fix typo Signed-off-by: Stuart Halloway --- src/clj/clojure/instant.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/instant.clj b/src/clj/clojure/instant.clj index ddece22025..9c8eb5ecad 100644 --- a/src/clj/clojure/instant.clj +++ b/src/clj/clojure/instant.clj @@ -136,7 +136,7 @@ specified. ((if leap-year? dim-leap dim-norm) month)))) (defn validated - "Return a function which constructs and instant by calling constructor + "Return a function which constructs an instant by calling constructor after first validating that those arguments are in range and otherwise plausible. The resulting function will throw an exception if called with invalid arguments." From 26bd803a8a05f398fa1c036104db04a890cb5d80 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Sun, 29 Jan 2017 21:10:34 -0600 Subject: [PATCH 363/854] CLJ-2104 Fix typo in docstring Signed-off-by: Stuart Halloway --- src/clj/clojure/pprint.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/pprint.clj b/src/clj/clojure/pprint.clj index f09f4da0fa..53ff905745 100644 --- a/src/clj/clojure/pprint.clj +++ b/src/clj/clojure/pprint.clj @@ -32,7 +32,7 @@ cl-format, it supports very concise custom dispatch. It also provides a more powerful alternative to Clojure's standard format function. See documentation for pprint and cl-format for more information or -complete documentation on the the clojure web site on github.", +complete documentation on the clojure web site on github.", :added "1.2"} clojure.pprint (:refer-clojure :exclude (deftype)) From 6d08609c208ae49a3d411efbdc316ec102fdef1d Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Wed, 6 Sep 2017 20:41:16 -0400 Subject: [PATCH 364/854] finish partial application of previous commits --- src/clj/clojure/pprint.clj | 2 +- src/clj/clojure/pprint/pretty_writer.clj | 5 ++-- src/clj/clojure/pprint/utilities.clj | 37 ++++++++++++++---------- src/clj/clojure/set.clj | 7 +++-- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/clj/clojure/pprint.clj b/src/clj/clojure/pprint.clj index 53ff905745..42836ac854 100644 --- a/src/clj/clojure/pprint.clj +++ b/src/clj/clojure/pprint.clj @@ -32,7 +32,7 @@ cl-format, it supports very concise custom dispatch. It also provides a more powerful alternative to Clojure's standard format function. See documentation for pprint and cl-format for more information or -complete documentation on the clojure web site on github.", +complete documentation on the Clojure web site on GitHub.", :added "1.2"} clojure.pprint (:refer-clojure :exclude (deftype)) diff --git a/src/clj/clojure/pprint/pretty_writer.clj b/src/clj/clojure/pprint/pretty_writer.clj index e3a6e3382a..49023b8230 100644 --- a/src/clj/clojure/pprint/pretty_writer.clj +++ b/src/clj/clojure/pprint/pretty_writer.clj @@ -40,9 +40,10 @@ [sym] `(~sym @@~'this)) -(defmacro ^{:private true} - setf [sym new-val] +(defmacro ^{:private true} + setf "Set the value of the field SYM to NEW-VAL" + [sym new-val] `(alter @~'this assoc ~sym ~new-val)) (defmacro ^{:private true} diff --git a/src/clj/clojure/pprint/utilities.clj b/src/clj/clojure/pprint/utilities.clj index 53c4e97383..95655bd664 100644 --- a/src/clj/clojure/pprint/utilities.clj +++ b/src/clj/clojure/pprint/utilities.clj @@ -50,19 +50,22 @@ [acc context] (recur new-context (conj acc result)))))) -(defn- unzip-map [m] - "Take a map that has pairs in the value slots and produce a pair of maps, - the first having all the first elements of the pairs and the second all - the second elements of the pairs" +(defn- unzip-map + "Take a map that has pairs in the value slots and produce a pair of + maps, the first having all the first elements of the pairs and the + second all the second elements of the pairs" + [m] [(into {} (for [[k [v1 v2]] m] [k v1])) (into {} (for [[k [v1 v2]] m] [k v2]))]) -(defn- tuple-map [m v1] +(defn- tuple-map "For all the values, v, in the map, replace them with [v v1]" + [m v1] (into {} (for [[k v] m] [k [v v1]]))) -(defn- rtrim [s c] +(defn- rtrim "Trim all instances of c from the end of sequence s" + [s c] (let [len (count s)] (if (and (pos? len) (= (nth s (dec (count s))) c)) (loop [n (dec len)] @@ -72,8 +75,9 @@ true (recur (dec n)))) s))) -(defn- ltrim [s c] +(defn- ltrim "Trim all instances of c from the beginning of sequence s" + [s c] (let [len (count s)] (if (and (pos? len) (= (nth s 0) c)) (loop [n 0] @@ -82,24 +86,27 @@ (recur (inc n)))) s))) -(defn- prefix-count [aseq val] - "Return the number of times that val occurs at the start of sequence aseq, -if val is a seq itself, count the number of times any element of val occurs at the -beginning of aseq" +(defn- prefix-count + "Return the number of times that val occurs at the start of sequence aseq, + if val is a seq itself, count the number of times any element of val + occurs at the beginning of aseq" + [aseq val] (let [test (if (coll? val) (set val) #{val})] (loop [pos 0] (if (or (= pos (count aseq)) (not (test (nth aseq pos)))) pos (recur (inc pos)))))) -(defn- prerr [& args] +(defn- prerr "Println to *err*" + [& args] (binding [*out* *err*] (apply println args))) - -(defmacro ^{:private true} prlabel [prefix arg & more-args] + +(defmacro ^{:private true} prlabel "Print args to *err* in name = value format" - `(prerr ~@(cons (list 'quote prefix) (mapcat #(list (list 'quote %) "=" %) + [prefix arg & more-args] + `(prerr ~@(cons (list 'quote prefix) (mapcat #(list (list 'quote %) "=" %) (cons arg (seq more-args)))))) ;; Flush the pretty-print buffer without flushing the underlying stream diff --git a/src/clj/clojure/set.clj b/src/clj/clojure/set.clj index 6a60d4f279..b63a004475 100644 --- a/src/clj/clojure/set.clj +++ b/src/clj/clojure/set.clj @@ -10,9 +10,10 @@ :author "Rich Hickey"} clojure.set) -(defn- bubble-max-key [k coll] - "Move a maximal element of coll according to fn k (which returns a number) - to the front of coll." +(defn- bubble-max-key + "Move a maximal element of coll according to fn k (which returns a + number) to the front of coll." + [k coll] (let [max (apply max-key k coll)] (cons max (remove #(identical? max %) coll)))) From 4ddc34ee11563345ad2142763c03495bf85110ff Mon Sep 17 00:00:00 2001 From: Jozef Wagner Date: Thu, 27 Oct 2016 14:07:50 +0200 Subject: [PATCH 365/854] CLJ-2028 Fix docstrings in filter, filterv, remove and take-while Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 8d90af4a71..9750923bd5 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -2772,7 +2772,7 @@ (defn filter "Returns a lazy sequence of the items in coll for which - (pred item) returns true. pred must be free of side-effects. + (pred item) returns logical true. pred must be free of side-effects. Returns a transducer when no collection is provided." {:added "1.0" :static true} @@ -2805,7 +2805,7 @@ (defn remove "Returns a lazy sequence of the items in coll for which - (pred item) returns false. pred must be free of side-effects. + (pred item) returns logical false. pred must be free of side-effects. Returns a transducer when no collection is provided." {:added "1.0" :static true} @@ -2867,7 +2867,7 @@ (defn take-while "Returns a lazy sequence of successive items from coll while - (pred item) returns true. pred must be free of side-effects. + (pred item) returns logical true. pred must be free of side-effects. Returns a transducer when no collection is provided." {:added "1.0" :static true} @@ -6814,7 +6814,7 @@ (defn filterv "Returns a vector of the items in coll for which - (pred item) returns true. pred must be free of side-effects." + (pred item) returns logical true. pred must be free of side-effects." {:added "1.4" :static true} [pred coll] From a543624897cf68b906f6a4fcd6ee0391b8cc6577 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 1 Jan 2016 02:26:23 -0800 Subject: [PATCH 366/854] CLJ-1873: Add .cljc files to doc strings of require and *data-readers* Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 9750923bd5..b0c803c419 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -5946,9 +5946,11 @@ 'require loads a lib by loading its root resource. The root resource path is derived from the lib name in the following manner: Consider a lib named by the symbol 'x.y.z; it has the root directory - /x/y/, and its root resource is /x/y/z.clj. The root - resource should contain code to create the lib's namespace (usually by using - the ns macro) and load any additional lib resources. + /x/y/, and its root resource is /x/y/z.clj, or + /x/y/z.cljc if /x/y/z.clj does not exist. The + root resource should contain code to create the lib's + namespace (usually by using the ns macro) and load any additional + lib resources. Libspecs @@ -7650,8 +7652,8 @@ "Map from reader tag symbols to data reader Vars. When Clojure starts, it searches for files named 'data_readers.clj' - at the root of the classpath. Each such file must contain a literal - map of symbols, like this: + and 'data_readers.cljc' at the root of the classpath. Each such file + must contain a literal map of symbols, like this: {foo/bar my.project.foo/bar foo/baz my.project/baz} @@ -7672,7 +7674,7 @@ Reader tags without namespace qualifiers are reserved for Clojure. Default reader tags are defined in clojure.core/default-data-readers but may be overridden in - data_readers.clj or by rebinding this Var." + data_readers.clj, data_readers.cljc, or by rebinding this Var." {}) (def ^{:added "1.5" :dynamic true} *default-data-reader-fn* From ee1b606ad066ac8df2efd4a6b8d0d365c206f5bf Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 18 Jan 2016 17:51:01 -0600 Subject: [PATCH 367/854] CLJ-1159 Improve docstring of clojure.java.io/delete-file Signed-off-by: Stuart Halloway --- src/clj/clojure/java/io.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/java/io.clj b/src/clj/clojure/java/io.clj index d9e67f2e98..72a30ed7a6 100644 --- a/src/clj/clojure/java/io.clj +++ b/src/clj/clojure/java/io.clj @@ -428,7 +428,7 @@ (reduce file (file parent child) more))) (defn delete-file - "Delete file f. Raise an exception if it fails unless silently is true." + "Delete file f. If silently is nil or false, raise an exception on failure, else return the value of silently." {:added "1.2"} [f & [silently]] (or (.delete (file f)) From c9dba73a43ca7e294b3db5e6a73c997759851689 Mon Sep 17 00:00:00 2001 From: Steve Miner Date: Wed, 5 Oct 2016 17:36:23 -0400 Subject: [PATCH 368/854] deftype docstring typo Signed-off-by: Stuart Halloway --- src/clj/clojure/core_deftype.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj index 8795ee5d12..73a4787fe6 100644 --- a/src/clj/clojure/core_deftype.clj +++ b/src/clj/clojure/core_deftype.clj @@ -425,8 +425,8 @@ Options are expressed as sequential keywords and arguments (in any order). Supported options: - :load-ns - if true, importing the record class will cause the - namespace in which the record was defined to be loaded. + :load-ns - if true, importing the type class will cause the + namespace in which the type was defined to be loaded. Defaults to false. Each spec consists of a protocol or interface name followed by zero From a43d912acfe30c5e78f46d0bee0a91697a1d9aea Mon Sep 17 00:00:00 2001 From: Ruslan Al-Fakikh Date: Mon, 25 Apr 2016 17:04:54 +0300 Subject: [PATCH 369/854] CLJ-1918 fixed docstring Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index b0c803c419..5dcb9abe94 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -3255,7 +3255,7 @@ "Blocks the current thread (indefinitely!) until all actions dispatched thus far, from this thread or agent, to the agent(s) have occurred. Will block on failed agents. Will never return if - a failed agent is restarted with :clear-actions true." + a failed agent is restarted with :clear-actions true or shutdown-agents was called." {:added "1.0" :static true} [& agents] From 32b2759a4d3508926ca2ec0f91413e6892b44d47 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 1 Jan 2016 02:58:35 -0800 Subject: [PATCH 370/854] CLJ-1837: Clarify doc strings of index-of and last-index-of Signed-off-by: Stuart Halloway --- src/clj/clojure/string.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/string.clj b/src/clj/clojure/string.clj index 910403ee58..35e0650f65 100644 --- a/src/clj/clojure/string.clj +++ b/src/clj/clojure/string.clj @@ -317,7 +317,7 @@ Design notes for clojure.string: (defn index-of "Return index of value (string or char) in s, optionally searching - forward from from-index or nil if not found." + forward from from-index. Return nil if value not found." {:added "1.8"} ([^CharSequence s value] (let [result ^long @@ -338,7 +338,7 @@ Design notes for clojure.string: (defn last-index-of "Return last index of value (string or char) in s, optionally - searching backward from from-index or nil if not found." + searching backward from from-index. Return nil if value not found." {:added "1.8"} ([^CharSequence s value] (let [result ^long From 4e3795c1f4b9dabb162fa2340242fc8eb908ff9c Mon Sep 17 00:00:00 2001 From: Yen-Chin Lee Date: Tue, 20 Oct 2015 22:36:17 +0800 Subject: [PATCH 371/854] CLJ-1826: drop-last docstring refers to 'coll' args refer to 's' Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 5dcb9abe94..ff1484507f 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -2915,8 +2915,8 @@ "Return a lazy sequence of all but the last n (default 1) items in coll" {:added "1.0" :static true} - ([s] (drop-last 1 s)) - ([n s] (map (fn [x _] x) s (drop n s)))) + ([coll] (drop-last 1 coll)) + ([n coll] (map (fn [x _] x) coll (drop n coll)))) (defn take-last "Returns a seq of the last n items in coll. Depending on the type From e19157c4809622fcaac1d8ccca8e3f6a67b3d848 Mon Sep 17 00:00:00 2001 From: Matthew Boston Date: Mon, 30 Nov 2015 12:09:14 -0700 Subject: [PATCH 372/854] CLJ-1859: Update parameter name to reflect docstring Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index ff1484507f..0b7d15c4d9 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -859,9 +859,9 @@ (defn zero? "Returns true if num is zero, else false" { - :inline (fn [x] `(. clojure.lang.Numbers (isZero ~x))) + :inline (fn [num] `(. clojure.lang.Numbers (isZero ~num))) :added "1.0"} - [x] (. clojure.lang.Numbers (isZero x))) + [num] (. clojure.lang.Numbers (isZero num))) (defn count "Returns the number of items in the collection. (count nil) returns @@ -1239,16 +1239,16 @@ (defn pos? "Returns true if num is greater than zero, else false" { - :inline (fn [x] `(. clojure.lang.Numbers (isPos ~x))) + :inline (fn [num] `(. clojure.lang.Numbers (isPos ~num))) :added "1.0"} - [x] (. clojure.lang.Numbers (isPos x))) + [num] (. clojure.lang.Numbers (isPos num))) (defn neg? "Returns true if num is less than zero, else false" { - :inline (fn [x] `(. clojure.lang.Numbers (isNeg ~x))) + :inline (fn [num] `(. clojure.lang.Numbers (isNeg ~num))) :added "1.0"} - [x] (. clojure.lang.Numbers (isNeg x))) + [num] (. clojure.lang.Numbers (isNeg num))) (defn quot "quot[ient] of dividing numerator by denominator." From 12e976ca3b07d7434ad4571a6bbeb05ef45d49b4 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Thu, 7 Sep 2017 13:43:48 -0500 Subject: [PATCH 373/854] CLJ-1074: add ##Inf/##-Inf/##NaN symbols Signed-off-by: Stuart Halloway --- src/clj/clojure/core_print.clj | 14 ++++++++++++++ src/jvm/clojure/lang/EdnReader.java | 22 +++++++++++++++++++++- src/jvm/clojure/lang/LispReader.java | 21 +++++++++++++++++++++ test/clojure/test_clojure/printer.clj | 9 +++++++++ test/clojure/test_clojure/reader.cljc | 12 +++++++++++- 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index f17e2f7a56..1b2b7a5765 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -128,6 +128,20 @@ (defmethod print-method Number [o, ^Writer w] (.write w (str o))) +(defmethod print-method Double [o, ^Writer w] + (cond + (= Double/POSITIVE_INFINITY o) (.write w "##Inf") + (= Double/NEGATIVE_INFINITY o) (.write w "##-Inf") + (.isNaN ^Double o) (.write w "##NaN") + :else (.write w (str o)))) + +(defmethod print-method Float [o, ^Writer w] + (cond + (= Float/POSITIVE_INFINITY o) (.write w "##Inf") + (= Float/NEGATIVE_INFINITY o) (.write w "##-Inf") + (.isNaN ^Float o) (.write w "##NaN") + :else (.write w (str o)))) + (defmethod print-dup Number [o, ^Writer w] (print-ctor o (fn [o w] diff --git a/src/jvm/clojure/lang/EdnReader.java b/src/jvm/clojure/lang/EdnReader.java index c5c3665e27..e744969747 100644 --- a/src/jvm/clojure/lang/EdnReader.java +++ b/src/jvm/clojure/lang/EdnReader.java @@ -49,6 +49,7 @@ public class EdnReader{ macros['#'] = new DispatchReader(); + dispatchMacros['#'] = new SymbolicValueReader(); dispatchMacros['^'] = new MetaReader(); //dispatchMacros['"'] = new RegexReader(); dispatchMacros['{'] = new SetReader(); @@ -705,6 +706,26 @@ public Object invoke(Object reader, Object leftangle, Object opts) { } } + +public static class SymbolicValueReader extends AFn{ + + static IPersistentMap specials = PersistentHashMap.create(Symbol.intern("Inf"), Double.POSITIVE_INFINITY, + Symbol.intern("-Inf"), Double.NEGATIVE_INFINITY, + Symbol.intern("NaN"), Double.NaN); + + public Object invoke(Object reader, Object quote, Object opts) { + PushbackReader r = (PushbackReader) reader; + Object o = read(r, true, null, true, opts); + + if (!(o instanceof Symbol)) + throw Util.runtimeException("Invalid token: ##" + o); + if (!(specials.containsKey(o))) + throw Util.runtimeException("Unknown symbolic value: ##" + o); + + return specials.valAt(o); + } +} + public static List readDelimitedList(char delim, PushbackReader r, boolean isRecursive, Object opts) { final int firstline = (r instanceof LineNumberingPushbackReader) ? @@ -785,4 +806,3 @@ private Object readTagged(PushbackReader reader, Symbol tag, IPersistentMap opts } } - diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index 588dcd6353..1a9b178936 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -107,6 +107,7 @@ public class LispReader{ dispatchMacros['^'] = new MetaReader(); + dispatchMacros['#'] = new SymbolicValueReader(); dispatchMacros['\''] = new VarReader(); dispatchMacros['"'] = new RegexReader(); dispatchMacros['('] = new FnReader(); @@ -728,6 +729,26 @@ public Object invoke(Object reader, Object colon, Object opts, Object pendingFor } } + +public static class SymbolicValueReader extends AFn{ + + static IPersistentMap specials = PersistentHashMap.create(Symbol.intern("Inf"), Double.POSITIVE_INFINITY, + Symbol.intern("-Inf"), Double.NEGATIVE_INFINITY, + Symbol.intern("NaN"), Double.NaN); + + public Object invoke(Object reader, Object quote, Object opts, Object pendingForms) { + PushbackReader r = (PushbackReader) reader; + Object o = read(r, true, null, true, opts, ensurePending(pendingForms)); + + if (!(o instanceof Symbol)) + throw Util.runtimeException("Invalid token: ##" + o); + if (!(specials.containsKey(o))) + throw Util.runtimeException("Unknown symbolic value: ##" + o); + + return specials.valAt(o); + } +} + public static class WrappingReader extends AFn{ final Symbol sym; diff --git a/test/clojure/test_clojure/printer.clj b/test/clojure/test_clojure/printer.clj index dc15618749..3d9cc65f8f 100644 --- a/test/clojure/test_clojure/printer.clj +++ b/test/clojure/test_clojure/printer.clj @@ -140,3 +140,12 @@ (let [date-map (bean (java.util.Date. 0))] (is (= (binding [*print-namespace-maps* true] (pr-str date-map)) (binding [*print-namespace-maps* false] (pr-str date-map)))))) + +(deftest print-symbol-values + (are [s v] (= s (pr-str v)) + "##Inf" Double/POSITIVE_INFINITY + "##-Inf" Double/NEGATIVE_INFINITY + "##NaN" Double/NaN + "##Inf" Float/POSITIVE_INFINITY + "##-Inf" Float/NEGATIVE_INFINITY + "##NaN" Float/NaN)) diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc index 2e64673366..4508309073 100644 --- a/test/clojure/test_clojure/reader.cljc +++ b/test/clojure/test_clojure/reader.cljc @@ -213,6 +213,10 @@ (is (instance? Double -1.0)) (is (instance? Double -1.)) + (is (= Double/POSITIVE_INFINITY ##Inf)) + (is (= Double/NEGATIVE_INFINITY ##-Inf)) + (is (and (instance? Double ##NaN) (.isNaN ##NaN))) + ; Read BigDecimal (is (instance? BigDecimal 9223372036854775808M)) (is (instance? BigDecimal -9223372036854775809M)) @@ -744,4 +748,10 @@ (deftest namespaced-map-edn (is (= {1 1, :a/b 2, :b/c 3, :d 4} (edn/read-string "#:a{1 1, :b 2, :b/c 3, :_/d 4}") - (edn/read-string "#:a {1 1, :b 2, :b/c 3, :_/d 4}")))) \ No newline at end of file + (edn/read-string "#:a {1 1, :b 2, :b/c 3, :_/d 4}")))) + +(deftest invalid-symbol-value + (is (thrown-with-msg? Exception #"Invalid token" (read-string "##5"))) + (is (thrown-with-msg? Exception #"Invalid token" (edn/read-string "##5"))) + (is (thrown-with-msg? Exception #"Unknown symbolic value" (read-string "##Foo"))) + (is (thrown-with-msg? Exception #"Unknown symbolic value" (edn/read-string "##Foo")))) From 7dbda4fb3caa3f46d4089cbbb766eb3b1f31c252 Mon Sep 17 00:00:00 2001 From: Chad Taylor Date: Sun, 9 Feb 2014 23:10:36 -0600 Subject: [PATCH 374/854] CLJ-1358 - fix doc to expand special cases Signed-off-by: Stuart Halloway --- src/clj/clojure/repl.clj | 2 +- test/clojure/test_clojure/repl.clj | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj index 0852382267..c796df3efa 100644 --- a/src/clj/clojure/repl.clj +++ b/src/clj/clojure/repl.clj @@ -135,7 +135,7 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) {:added "1.0"} [name] (if-let [special-name ('{& fn catch try finally try} name)] - (#'print-doc (#'special-doc special-name)) + `(#'print-doc (#'special-doc '~special-name)) (cond (special-doc-map name) `(#'print-doc (#'special-doc '~name)) (keyword? name) `(#'print-doc {:spec '~name :doc '~(spec/describe name)}) diff --git a/test/clojure/test_clojure/repl.clj b/test/clojure/test_clojure/repl.clj index 17bd084262..c7a0c41b09 100644 --- a/test/clojure/test_clojure/repl.clj +++ b/test/clojure/test_clojure/repl.clj @@ -8,7 +8,9 @@ (deftest test-doc (testing "with namespaces" (is (= "clojure.pprint" - (second (str/split-lines (with-out-str (doc clojure.pprint)))))))) + (second (str/split-lines (with-out-str (doc clojure.pprint))))))) + (testing "with special cases" + (is (= (with-out-str (doc catch)) (with-out-str (doc try)))))) (deftest test-source (is (= "(defn foo [])" (source-fn 'clojure.test-clojure.repl.example/foo))) From 9fdbd8cd56524911d120e4631cc53c572ebdd33d Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 6 Sep 2017 12:29:45 -0500 Subject: [PATCH 375/854] CLJ-1454 New atom fns to return [old new] Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 17 +++++++ src/jvm/clojure/lang/Atom.java | 72 ++++++++++++++++++++++++++++- src/jvm/clojure/lang/IAtom2.java | 23 +++++++++ test/clojure/test_clojure/atoms.clj | 24 ++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/jvm/clojure/lang/IAtom2.java diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 0b7d15c4d9..421fdc5d96 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -2351,6 +2351,17 @@ ([^clojure.lang.IAtom atom f x y] (.swap atom f x y)) ([^clojure.lang.IAtom atom f x y & args] (.swap atom f x y args))) +(defn swap-vals! + "Atomically swaps the value of atom to be: + (apply f current-value-of-atom args). Note that f may be called + multiple times, and thus should be free of side effects. + Returns [old new], the value of the atom before and after the swap." + {:added "1.9"} + (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f] (.swapVals atom f)) + (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f x] (.swapVals atom f x)) + (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f x y] (.swapVals atom f x y)) + (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f x y & args] (.swapVals atom f x y args))) + (defn compare-and-set! "Atomically sets the value of atom to newval if and only if the current value of the atom is identical to oldval. Returns true if @@ -2366,6 +2377,12 @@ :static true} [^clojure.lang.IAtom atom newval] (.reset atom newval)) +(defn reset-vals! + "Sets the value of atom to newval. Returns [old new], the value of the + atom before and after the reset." + {:added "1.9"} + ^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom newval] (.resetVals atom newval)) + (defn set-validator! "Sets the validator-fn for a var/ref/agent/atom. validator-fn must be nil or a side-effect-free fn of one argument, which will be passed the intended diff --git a/src/jvm/clojure/lang/Atom.java b/src/jvm/clojure/lang/Atom.java index a964c4947f..127611c5e5 100644 --- a/src/jvm/clojure/lang/Atom.java +++ b/src/jvm/clojure/lang/Atom.java @@ -14,7 +14,7 @@ import java.util.concurrent.atomic.AtomicReference; -final public class Atom extends ARef implements IAtom{ +final public class Atom extends ARef implements IAtom2{ final AtomicReference state; public Atom(Object state){ @@ -86,6 +86,62 @@ public Object swap(IFn f, Object x, Object y, ISeq args) { } } +public IPersistentVector swapVals(IFn f) { + for(; ;) + { + Object oldv = deref(); + Object newv = f.invoke(oldv); + validate(newv); + if(state.compareAndSet(oldv, newv)) + { + notifyWatches(oldv, newv); + return LazilyPersistentVector.createOwning(oldv, newv); + } + } +} + +public IPersistentVector swapVals(IFn f, Object arg) { + for(; ;) + { + Object oldv = deref(); + Object newv = f.invoke(oldv, arg); + validate(newv); + if(state.compareAndSet(oldv, newv)) + { + notifyWatches(oldv, newv); + return LazilyPersistentVector.createOwning(oldv, newv); + } + } +} + +public IPersistentVector swapVals(IFn f, Object arg1, Object arg2) { + for(; ;) + { + Object oldv = deref(); + Object newv = f.invoke(oldv, arg1, arg2); + validate(newv); + if(state.compareAndSet(oldv, newv)) + { + notifyWatches(oldv, newv); + return LazilyPersistentVector.createOwning(oldv, newv); + } + } +} + +public IPersistentVector swapVals(IFn f, Object x, Object y, ISeq args) { + for(; ;) + { + Object oldv = deref(); + Object newv = f.applyTo(RT.listStar(oldv, x, y, args)); + validate(newv); + if(state.compareAndSet(oldv, newv)) + { + notifyWatches(oldv, newv); + return LazilyPersistentVector.createOwning(oldv, newv); + } + } +} + public boolean compareAndSet(Object oldv, Object newv){ validate(newv); boolean ret = state.compareAndSet(oldv, newv); @@ -101,4 +157,18 @@ public Object reset(Object newval){ notifyWatches(oldval, newval); return newval; } + +public IPersistentVector resetVals(Object newv){ + validate(newv); + for(; ;) + { + Object oldv = deref(); + if(state.compareAndSet(oldv, newv)) + { + notifyWatches(oldv, newv); + return LazilyPersistentVector.createOwning(oldv, newv); + } + } +} + } diff --git a/src/jvm/clojure/lang/IAtom2.java b/src/jvm/clojure/lang/IAtom2.java new file mode 100644 index 0000000000..ab7c0f4b98 --- /dev/null +++ b/src/jvm/clojure/lang/IAtom2.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +package clojure.lang; + +public interface IAtom2 extends IAtom { +IPersistentVector swapVals(IFn f); + +IPersistentVector swapVals(IFn f, Object arg); + +IPersistentVector swapVals(IFn f, Object arg1, Object arg2); + +IPersistentVector swapVals(IFn f, Object x, Object y, ISeq args); + +IPersistentVector resetVals(Object newv); +} diff --git a/test/clojure/test_clojure/atoms.clj b/test/clojure/test_clojure/atoms.clj index 672a148768..f9ecadcc56 100644 --- a/test/clojure/test_clojure/atoms.clj +++ b/test/clojure/test_clojure/atoms.clj @@ -18,3 +18,27 @@ ; swap! reset! ; compare-and-set! +(deftest swap-vals-returns-old-value + (let [a (atom 0)] + (is (= [0 1] (swap-vals! a inc))) + (is (= [1 2] (swap-vals! a inc))) + (is (= 2 @a)))) + +(deftest deref-swap-arities + (binding [*warn-on-reflection* true] + (let [a (atom 0)] + (is (= [0 1] (swap-vals! a + 1))) + (is (= [1 3] (swap-vals! a + 1 1))) + (is (= [3 6] (swap-vals! a + 1 1 1))) + (is (= [6 10] (swap-vals! a + 1 1 1 1))) + (is (= 10 @a))))) + +(deftest deref-reset-returns-old-value + (let [a (atom 0)] + (is (= [0 :b] (reset-vals! a :b))) + (is (= [:b 45M] (reset-vals! a 45M))) + (is (= 45M @a)))) + +(deftest reset-on-deref-reset-equality + (let [a (atom :usual-value)] + (is (= :usual-value (reset! a (first (reset-vals! a :almost-never-seen-value))))))) From 31ec81ad3c98cc02be32258ee48a94cce129c00c Mon Sep 17 00:00:00 2001 From: Johan Mena Date: Wed, 6 May 2015 09:59:14 -0500 Subject: [PATCH 376/854] CLJ-1705 - update test to desired behavior Signed-off-by: Stuart Halloway --- test/clojure/test_clojure/vectors.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/clojure/test_clojure/vectors.clj b/test/clojure/test_clojure/vectors.clj index 232b2c9386..0bea3ff4b8 100644 --- a/test/clojure/test_clojure/vectors.clj +++ b/test/clojure/test_clojure/vectors.clj @@ -322,10 +322,11 @@ (vector-of :double) (vector-of :char)) (testing "with invalid type argument" - (are [x] (thrown? NullPointerException x) + (are [x] (thrown? IllegalArgumentException x) (vector-of nil) (vector-of Float/TYPE) (vector-of 'int) + (vector-of :integer) (vector-of "")))) (testing "vector-like (vector-of :type x1 x2 x3 … xn)" (are [vec gvec] (and (instance? clojure.core.Vec gvec) From b0b084b10e4ed91188bba409ae80f1159fa1cfd5 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 6 May 2015 10:00:07 -0500 Subject: [PATCH 377/854] CLJ-1705 - vector-of throws IllegalArgEx if type not appropriate Signed-off-by: Stuart Halloway --- src/clj/clojure/gvec.clj | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/clj/clojure/gvec.clj b/src/clj/clojure/gvec.clj index b60f9a4824..3c40073793 100644 --- a/src/clj/clojure/gvec.clj +++ b/src/clj/clojure/gvec.clj @@ -475,7 +475,13 @@ :char (mk-am char) :boolean (mk-am boolean)}) -(defn vector-of +(defmacro ^:private ams-check [t] + `(let [am# (ams ~t)] + (if am# + am# + (throw (IllegalArgumentException. (str "Unrecognized type " ~t)))))) + +(defn vector-of "Creates a new vector of a single primitive type t, where t is one of :int :long :float :double :byte :short :char or :boolean. The resulting vector complies with the interface of vectors in general, @@ -485,28 +491,28 @@ {:added "1.2" :arglists '([t] [t & elements])} ([t] - (let [am ^clojure.core.ArrayManager (ams t)] + (let [^clojure.core.ArrayManager am (ams-check t)] (Vec. am 0 5 EMPTY-NODE (.array am 0) nil))) ([t x1] - (let [am ^clojure.core.ArrayManager (ams t) + (let [^clojure.core.ArrayManager am (ams-check t) arr (.array am 1)] (.aset am arr 0 x1) (Vec. am 1 5 EMPTY-NODE arr nil))) ([t x1 x2] - (let [am ^clojure.core.ArrayManager (ams t) + (let [^clojure.core.ArrayManager am (ams-check t) arr (.array am 2)] (.aset am arr 0 x1) (.aset am arr 1 x2) (Vec. am 2 5 EMPTY-NODE arr nil))) ([t x1 x2 x3] - (let [am ^clojure.core.ArrayManager (ams t) + (let [^clojure.core.ArrayManager am (ams-check t) arr (.array am 3)] (.aset am arr 0 x1) (.aset am arr 1 x2) (.aset am arr 2 x3) (Vec. am 3 5 EMPTY-NODE arr nil))) ([t x1 x2 x3 x4] - (let [am ^clojure.core.ArrayManager (ams t) + (let [^clojure.core.ArrayManager am (ams-check t) arr (.array am 4)] (.aset am arr 0 x1) (.aset am arr 1 x2) From 4cf6ca7b900c64bfd2c6a33b6b130eef6696b130 Mon Sep 17 00:00:00 2001 From: Chouser Date: Tue, 20 Jun 2017 12:19:29 -0400 Subject: [PATCH 378/854] CLJ-2184 propagate metadata in doto forms Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 421fdc5d96..d8e358739d 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -3831,9 +3831,11 @@ (let [gx (gensym)] `(let [~gx ~x] ~@(map (fn [f] - (if (seq? f) - `(~(first f) ~gx ~@(next f)) - `(~f ~gx))) + (with-meta + (if (seq? f) + `(~(first f) ~gx ~@(next f)) + `(~f ~gx)) + (meta f))) forms) ~gx))) From 861d48ea761418fcb59dc4eb1c63bd29c2f35231 Mon Sep 17 00:00:00 2001 From: Michael Blume Date: Fri, 23 Jun 2017 15:36:13 -0700 Subject: [PATCH 379/854] CLJ-2188 Mark slurp as returning String Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index d8e358739d..71b7525683 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6857,7 +6857,8 @@ (defn slurp "Opens a reader on f and reads all its contents, returning a string. See clojure.java.io/reader for a complete list of supported arguments." - {:added "1.0"} + {:added "1.0" + :tag String} ([f & opts] (let [opts (normalize-slurp-opts opts) sw (java.io.StringWriter.)] From 6ab6d5001ecdc71e2eda4a4608f353a30ee99e96 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 7 Sep 2017 16:26:47 -0500 Subject: [PATCH 380/854] [maven-release-plugin] prepare release clojure-1.9.0-alpha20 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3b52bbdb63..a4991ad87a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-alpha20 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-alpha20 From 43540be97668ed453cad74e18f434c3e8bd5d638 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 7 Sep 2017 16:26:47 -0500 Subject: [PATCH 381/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a4991ad87a..3b52bbdb63 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-alpha20 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-alpha20 + HEAD From ecd15907082d31511f1ed0a249bc48fa532311f4 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 8 Sep 2017 12:51:00 -0500 Subject: [PATCH 382/854] CLJ-2077 conditionally load clojure.instant Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 71b7525683..0ad5dd671c 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6688,7 +6688,15 @@ (load "core_deftype") (load "core/protocols") (load "gvec") -(load "instant") + +(defmacro ^:private when-class [class-name & body] + `(try + (Class/forName ^String ~class-name) + ~@body + (catch ClassNotFoundException _#))) + +(when-class "java.sql.Timestamp" + (load "instant")) (defprotocol Inst (inst-ms* [inst])) @@ -6698,10 +6706,8 @@ (inst-ms* [inst] (.getTime ^java.util.Date inst))) ;; conditionally extend to Instant on Java 8+ -(try - (Class/forName "java.time.Instant") - (load "core_instant18") - (catch ClassNotFoundException cnfe)) +(when-class "java.time.Instant" + (load "core_instant18")) (defn inst-ms "Return the number of milliseconds since January 1, 1970, 00:00:00 GMT" @@ -7665,8 +7671,10 @@ (def ^{:added "1.4"} default-data-readers "Default map of data reader functions provided by Clojure. May be overridden by binding *data-readers*." - {'inst #'clojure.instant/read-instant-date - 'uuid #'clojure.uuid/default-uuid-reader}) + (merge + {'uuid #'clojure.uuid/default-uuid-reader} + (when-class "java.sql.Timestamp" + {'inst #'clojure.instant/read-instant-date}))) (def ^{:added "1.4" :dynamic true} *data-readers* "Map from reader tag symbols to data reader Vars. From 396fb64b421c3b02bf1ffc4f87e7ea5bffa51e61 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 18 Sep 2017 10:27:49 -0500 Subject: [PATCH 383/854] Change log updates for 1.9.0-beta1 Signed-off-by: Stuart Halloway --- changes.md | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/changes.md b/changes.md index 61b2eaf8ce..61a57dd7bc 100644 --- a/changes.md +++ b/changes.md @@ -1,5 +1,179 @@ +# Changes to Clojure in Version 1.9 + +## 1 New and Improved Features + +### 1.1 spec + +spec is a new core library for describing, validating, and testing the structure of data and functions. + +For more information, see: + +* [About spec](https://clojure.org/about/spec) +* [spec Guide](https://clojure.org/guides/spec) + +Note that spec is in alpha state and API compatibility is not guaranteed. Also, spec and the specs for the Clojure core API are distributed as external libraries that must be included to use Clojure. + +### 1.2 Support for working with maps with qualified keys + +Several enhancements have been made to add support for working with maps with qualified keys: + +* Map namespace syntax - specify the default namespace context for the keys (or symbols) in a map once - `#:car{:make "Jeep" :model "Wrangler"}`. For more information see https://clojure.org/reference/reader#_maps ([CLJ-1910](http://dev.clojure.org/jira/browse/CLJ-1910)) +* Destructuring support - namespaced map keys can now specified once as a namespace for :keys or :syms. For more information see https://clojure.org/reference/special_forms#_map_binding_destructuring ([CLJ-1919](http://dev.clojure.org/jira/browse/CLJ-1919)) +* `*print-namespace-maps*` - by default maps will not print with the map namespace syntax except in the clojure.main repl. This dynamic var is a flag to allow you to control whether the namespace map syntax is used. + +### 1.3 New predicates + +Specs rely heavily on predicates and many new type and value oriented predicates have been added to clojure.core: + +* `boolean?` +* `int?` `pos-int?` `neg-int?` `nat-int?` +* `double?` `bigdec?` +* `ident?` `simple-ident?` `qualified-ident?` +* `simple-symbol?` `qualified-symbol?` +* `simple-keyword?` `qualified-keyword?` +* `bytes?` (for `byte[]`) +* `indexed?` +* `uuid?` `uri?` +* `seqable?` +* `any?` + +### 1.4 More support for instants + +More support has been added for the notion of instants in time: + +* Added a new protocol `Inst` for instant types +* `Inst` is extended for `java.util.Date` +* `Inst` is optionally extended for `java.time.Instant` in Java 1.8+ +* New functions that work for instants: `inst?`, `inst-ms` + +### 1.5 Other new core functions + +These are some other new functions in clojure.core: + +* `bounded-count` - a count that avoids realizing the entire collection beyond a bound +* `swap-vals!` and `reset-vals!` - new atom functions that return both the old and new values ([CLJ-1454](http://dev.clojure.org/jira/browse/CLJ-1454)) +* `halt-when` - new transducer that ends transduction when pred is satisfied + +### 1.6 Other reader enhancements + +* Can now bind `*reader-resolver*` to an impl of LispReader$Resolver to control the reader’s use of namespace interactions when resolving autoresolved keywords and maps. +* Add new ## reader macro for symbolic values, and read/print support for double vals ##Inf, ##-Inf, ##NaN ([CLJ-1074](http://dev.clojure.org/jira/browse/CLJ-1074)) + +## 2 Enhancements + +### 2.1 Spec syntax checking + +If a macro has a spec defined via fdef, that spec will be checked at compile time. Specs have been defined for many clojure.core macros and errors will be reported for these based on the specs at compile time. + +### 2.2 Documentation + +* `doc` will now report specs for functions with specs defined using `fdef` +* `doc` can now be invoked with a fully-qualified keyword representing a spec name + +### 2.3 Performance + +* Improved update-in performance +* Optimized seq & destructuring +* [CLJ-2210](http://dev.clojure.org/jira/browse/CLJ-2210) + Cache class derivation in compiler to improve compiler performance +* [CLJ-2188](http://dev.clojure.org/jira/browse/CLJ-2188) + `slurp` - mark return type as String +* [CLJ-2070](http://dev.clojure.org/jira/browse/CLJ-2070) + `clojure.core/delay` - improve performance +* [CLJ-1917](http://dev.clojure.org/jira/browse/CLJ-1917) + Reducing seq over string should call String/length outside of loop +* [CLJ-1901](http://dev.clojure.org/jira/browse/CLJ-1901) + `amap` - should call alength only once +* [CLJ-1224](http://dev.clojure.org/jira/browse/CLJ-1935) + Record instances now cache hasheq and hashCode like maps +* [CLJ-99](http://dev.clojure.org/jira/browse/CLJ-99) + `min-key` and `max-key` - evaluate k on each arg at most once + +### 2.4 Other enhancements + +* Added Var serialization for identity, not value +* `into` now has a 0-arity (returns `[]`) and 1-arity (returns the coll that's passed) +* [CLJ-2184](http://dev.clojure.org/jira/browse/CLJ-2184) + Propagate meta in doto forms to improve error reporting +* [CLJ-1744](http://dev.clojure.org/jira/browse/CLJ-1744) + Clear unused locals, which can prevent memory leaks in some cases +* [CLJ-1673](http://dev.clojure.org/jira/browse/CLJ-1673) + `clojure.repl/dir-fn` now works on namespace aliases +* [CLJ-1423](http://dev.clojure.org/jira/browse/CLJ-1423) + Allow vars to be invoked with infinite arglists (also, faster) + +## 3 Fixes + +### 3.1 Security + +* [CLJ-2204](http://dev.clojure.org/jira/browse/CLJ-2204) + Disable serialization of proxy classes to avoid potential issue when deserializing + +### 3.2 Docs + +* [CLJ-2170](http://dev.clojure.org/jira/browse/CLJ-2170) + fix improperly located docstrings +* [CLJ-2156](http://dev.clojure.org/jira/browse/CLJ-2156) + `clojure.java.io/copy` - doc char[] support +* [CLJ-2104](http://dev.clojure.org/jira/browse/CLJ-2104) + `clojure.pprint` docstring - fix typo +* [CLJ-2051](http://dev.clojure.org/jira/browse/CLJ-2051) + `clojure.instant/validated` docstring - fix typo +* [CLJ-2039](http://dev.clojure.org/jira/browse/CLJ-2039) + `deftype` - fix typo in docstring +* [CLJ-2028](http://dev.clojure.org/jira/browse/CLJ-2028) + `filter`, `filterv`, `remove`, `take-while` - fix docstrings +* [CLJ-1918](http://dev.clojure.org/jira/browse/CLJ-1918) + `await` - improve docstring re `shutdown-agents` +* [CLJ-1873](http://dev.clojure.org/jira/browse/CLJ-1873) + `require`, `*data-readers*` - add .cljc files to docstrings +* [CLJ-1859](http://dev.clojure.org/jira/browse/CLJ-1859) + `zero?`, `pos?`, `neg?` - fix docstrings +* [CLJ-1837](http://dev.clojure.org/jira/browse/CLJ-1837) + `index-of`, `last-index-of` - clarify docstrings +* [CLJ-1826](http://dev.clojure.org/jira/browse/CLJ-1826) + `drop-last` - fix docstring +* [CLJ-1159](http://dev.clojure.org/jira/browse/CLJ-1159) + `clojure.java.io/delete-file` - improve docstring + +### 3.3 Other fixes + +* `clojure.core/Throwable->map` formerly returned `StackTraceElement`s which were later handled by the printer. Now the StackTraceElements are converted to data such that the return value is pure Clojure data, as intended. +* [CLJ-2091](http://dev.clojure.org/jira/browse/CLJ-2091) + `clojure.lang.APersistentVector#hashCode` is not thread-safe +* [CLJ-2077](http://dev.clojure.org/jira/browse/CLJ-2077) + Clojure can't be loaded from the boot classpath under java 9 +* [CLJ-2048](http://dev.clojure.org/jira/browse/CLJ-2048) + Specify type to avoid ClassCastException when stack trace is elided by JVM +* [CLJ-1914](http://dev.clojure.org/jira/browse/CLJ-1914) + Fixed race condition in concurrent `range` realization +* [CLJ-1887](http://dev.clojure.org/jira/browse/CLJ-1887) + `IPersistentVector.length()` - implement missing method +* [CLJ-1870](http://dev.clojure.org/jira/browse/CLJ-1870) + Fixed reloading a `defmulti` removes metadata on the var +* [CLJ-1860](http://dev.clojure.org/jira/browse/CLJ-1860) + Make -0.0 hash consistent with 0.0 +* [CLJ-1841](http://dev.clojure.org/jira/browse/CLJ-1841) + `bean` - iterator was broken +* [CLJ-1793](http://dev.clojure.org/jira/browse/CLJ-1793) + Clear 'this' before calls in tail position +* [CLJ-1790](http://dev.clojure.org/jira/browse/CLJ-1790) + Fixed error extending protocols to Java arrays +* [CLJ-1714](http://dev.clojure.org/jira/browse/CLJ-1714) + using a class in a type hint shouldn’t load the class +* [CLJ-1705](http://dev.clojure.org/jira/browse/CLJ-1705) + `vector-of` - fix NullPointerException if given unrecognized type +* [CLJ-1398](http://dev.clojure.org/jira/browse/CLJ-1398) + `clojure.java.javadoc/javadoc` - update doc urls +* [CLJ-1371](http://dev.clojure.org/jira/browse/CLJ-1371) + `Numbers.divide(Object, Object)` - add checks for NaN +* [CLJ-1358](http://dev.clojure.org/jira/browse/CLJ-1358) + `doc` - does not expand special cases properly (try, catch) +* [CLJ-1242](http://dev.clojure.org/jira/browse/CLJ-1242) + equals doesn't throw on sorted collections + # Changes to Clojure in Version 1.8 ## 1 New and Improved Features From 528a7c672675caba37aaf9d967d6ead03d401b71 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 18 Sep 2017 12:13:32 -0500 Subject: [PATCH 384/854] [maven-release-plugin] prepare release clojure-1.9.0-beta1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3b52bbdb63..2ae7e8fe89 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-beta1 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-beta1 From bdb32612536a9a00f0da5085de47f164010165c1 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 18 Sep 2017 12:13:32 -0500 Subject: [PATCH 385/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2ae7e8fe89..3b52bbdb63 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-beta1 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-beta1 + HEAD From e29a3e694a1a2df3f705f84dcd8a7be2d7aa7a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Marczyk?= Date: Wed, 4 Oct 2017 22:04:45 +0200 Subject: [PATCH 386/854] CLJ-2247: restore last match semantics of {min,max}-key The CLJ-99 patch (d10a9d36ef91e1f329528890d6fc70471d78485d) makes two changes to the behaviour of {min,max}-key: 1. it ensures that the key function is only called once on each argument; 2. it causes both functions to return the first match, whereas previously they returned the last one. This preserves 1. and reverts 2. Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 4 ++-- test/clojure/test_clojure/other_functions.clj | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 0ad5dd671c..6b0249994c 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -4944,7 +4944,7 @@ (if more (let [w (first more) kw (k w)] - (if (> kw kv) + (if (>= kw kv) (recur w kw (next more)) (recur v kv (next more)))) v))))) @@ -4962,7 +4962,7 @@ (if more (let [w (first more) kw (k w)] - (if (< kw kv) + (if (<= kw kv) (recur w kw (next more)) (recur v kv (next more)))) v))))) diff --git a/test/clojure/test_clojure/other_functions.clj b/test/clojure/test_clojure/other_functions.clj index 94ce9d70d8..958df5eca8 100644 --- a/test/clojure/test_clojure/other_functions.clj +++ b/test/clojure/test_clojure/other_functions.clj @@ -335,7 +335,10 @@ count ["longest" "a" "xy" "foo" "bar"] "a" "longest" - [5 10 15 20 25] 25 5 #(if (neg? %) (- %) %) [-2 -1 0 1 2 3 4] 0 4 - {nil 1 false -1 true 0} [true true false nil] false nil)) + {nil 1 false -1 true 0} [true true false nil] false nil) + (are [f k coll expected] (= expected (apply f k coll)) + min-key :x [{:x 1000} {:x 1001} {:x 1002} {:x 1000 :second true}] {:x 1000 :second true} + max-key :x [{:x 1000} {:x 999} {:x 998} {:x 1000 :second true}] {:x 1000 :second true})) ; Printing From 5481362e470e717f42661a66748b53c07bc6eeec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Marczyk?= Date: Thu, 5 Oct 2017 09:52:02 +0200 Subject: [PATCH 387/854] CLJ-2247: document "last match" semantics of {min,max}-key Signed-off-by: Stuart Halloway --- src/clj/clojure/core.clj | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 6b0249994c..89b20a9bda 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -4932,7 +4932,9 @@ (^String [^String s start end] (. s (substring start end)))) (defn max-key - "Returns the x for which (k x), a number, is greatest." + "Returns the x for which (k x), a number, is greatest. + + If there are multiple such xs, the last one is returned." {:added "1.0" :static true} ([k x] x) @@ -4950,7 +4952,9 @@ v))))) (defn min-key - "Returns the x for which (k x), a number, is least." + "Returns the x for which (k x), a number, is least. + + If there are multiple such xs, the last one is returned." {:added "1.0" :static true} ([k x] x) From 9a478cf9debc3c562f7623e8978337f28245075a Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 6 Oct 2017 14:12:11 -0500 Subject: [PATCH 388/854] update spec.alpha version and remove unneeded dep exclusions Signed-off-by: Stuart Halloway --- pom.xml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 3b52bbdb63..8202e2c2a8 100644 --- a/pom.xml +++ b/pom.xml @@ -41,28 +41,12 @@ org.clojure spec.alpha - 0.1.123 - - - org.clojure - clojure - - + 0.1.134 org.clojure core.specs.alpha 0.1.24 - - - org.clojure - clojure - - - org.clojure - spec.alpha - - org.codehaus.jsr166-mirror From 128017179e811c09d4785421b1cb6479b0f6aa39 Mon Sep 17 00:00:00 2001 From: Stuart Halloway Date: Fri, 6 Oct 2017 16:12:14 -0400 Subject: [PATCH 389/854] CLJ-700 expand RT.getFrom() .contains() .find() to handle transient types --- src/jvm/clojure/lang/ATransientMap.java | 13 ++++++++- .../clojure/lang/ITransientAssociative2.java | 16 +++++++++++ src/jvm/clojure/lang/PersistentVector.java | 14 +++++++++- src/jvm/clojure/lang/RT.java | 20 +++++++++++++- test/clojure/test_clojure/transients.clj | 27 +++++++++++++++++++ 5 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 src/jvm/clojure/lang/ITransientAssociative2.java diff --git a/src/jvm/clojure/lang/ATransientMap.java b/src/jvm/clojure/lang/ATransientMap.java index 59199a8764..8cfcf9bd9b 100644 --- a/src/jvm/clojure/lang/ATransientMap.java +++ b/src/jvm/clojure/lang/ATransientMap.java @@ -14,7 +14,7 @@ import clojure.lang.PersistentHashMap.INode; -public abstract class ATransientMap extends AFn implements ITransientMap { +public abstract class ATransientMap extends AFn implements ITransientMap, ITransientAssociative2 { abstract void ensureEditable(); abstract ITransientMap doAssoc(Object key, Object val); abstract ITransientMap doWithout(Object key); @@ -79,6 +79,17 @@ public final Object valAt(Object key, Object notFound) { return doValAt(key, notFound); } + private static final Object NOT_FOUND = new Object(); + public final boolean containsKey(Object key){ + return valAt(key, NOT_FOUND) != NOT_FOUND; + } + public final IMapEntry entryAt(Object key){ + Object v = valAt(key, NOT_FOUND); + if(v != NOT_FOUND) + return MapEntry.create(key, v); + return null; + } + public final int count() { ensureEditable(); return doCount(); diff --git a/src/jvm/clojure/lang/ITransientAssociative2.java b/src/jvm/clojure/lang/ITransientAssociative2.java new file mode 100644 index 0000000000..6affcf9617 --- /dev/null +++ b/src/jvm/clojure/lang/ITransientAssociative2.java @@ -0,0 +1,16 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +package clojure.lang; + +public interface ITransientAssociative2 extends ITransientAssociative { + boolean containsKey(Object key); + IMapEntry entryAt(Object key); +} diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java index 3f6de59ab8..459dfb5730 100644 --- a/src/jvm/clojure/lang/PersistentVector.java +++ b/src/jvm/clojure/lang/PersistentVector.java @@ -515,7 +515,7 @@ else if(subidx == 0) } } -static final class TransientVector extends AFn implements ITransientVector, Counted{ +static final class TransientVector extends AFn implements ITransientVector, ITransientAssociative2, Counted{ volatile int cnt; volatile int shift; volatile Node root; @@ -678,6 +678,18 @@ public Object valAt(Object key, Object notFound){ return notFound; } + private static final Object NOT_FOUND = new Object(); + public final boolean containsKey(Object key){ + return valAt(key, NOT_FOUND) != NOT_FOUND; + } + + public final IMapEntry entryAt(Object key){ + Object v = valAt(key, NOT_FOUND); + if(v != NOT_FOUND) + return MapEntry.create(key, v); + return null; + } + public Object invoke(Object arg1) { //note - relies on ensureEditable in nth if(Util.isInteger(arg1)) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 3c835e3f96..0c1253ae9d 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -771,6 +771,10 @@ else if(key instanceof Number && (coll instanceof String || coll.getClass().isAr return nth(coll, n); return null; } + else if(coll instanceof ITransientSet) { + ITransientSet set = (ITransientSet) coll; + return set.get(key); + } return null; } @@ -800,6 +804,12 @@ else if(key instanceof Number && (coll instanceof String || coll.getClass().isAr int n = ((Number) key).intValue(); return n >= 0 && n < count(coll) ? nth(coll, n) : notFound; } + else if(coll instanceof ITransientSet) { + ITransientSet set = (ITransientSet) coll; + if(set.contains(key)) + return set.get(key); + return notFound; + } return notFound; } @@ -829,6 +839,10 @@ else if(key instanceof Number && (coll instanceof String || coll.getClass().isAr int n = ((Number) key).intValue(); return n >= 0 && n < count(coll); } + else if(coll instanceof ITransientSet) + return ((ITransientSet)coll).contains(key) ? T : F; + else if(coll instanceof ITransientAssociative2) + return (((ITransientAssociative2)coll).containsKey(key)) ? T : F; throw new IllegalArgumentException("contains? not supported on type: " + coll.getClass().getName()); } @@ -837,12 +851,16 @@ static public Object find(Object coll, Object key){ return null; else if(coll instanceof Associative) return ((Associative) coll).entryAt(key); - else { + else if(coll instanceof Map) { Map m = (Map) coll; if(m.containsKey(key)) return MapEntry.create(key, m.get(key)); return null; } + else if(coll instanceof ITransientAssociative2) { + return ((ITransientAssociative2) coll).entryAt(key); + } + throw new IllegalArgumentException("find not supported on type: " + coll.getClass().getName()); } //takes a seq of key,val,key,val diff --git a/test/clojure/test_clojure/transients.clj b/test/clojure/test_clojure/transients.clj index dcc956e39e..f64ba92694 100644 --- a/test/clojure/test_clojure/transients.clj +++ b/test/clojure/test_clojure/transients.clj @@ -53,3 +53,30 @@ t2 @(future (conj! t 4)) p (persistent! t2)] (is (= [1 2 3 4] p)))) + +(deftest transient-lookups + (let [tv (transient [1 2 3])] + (is (= 1 (get tv 0))) + (is (= :foo (get tv 4 :foo))) + (is (= true (contains? tv 0))) + (is (= [0 1] (find tv 0))) + (is (= nil (find tv -1)))) + (let [ts (transient #{1 2})] + (is (= true (contains? ts 1))) + (is (= false (contains? ts 99))) + (is (= 1 (get ts 1))) + (is (= nil (get ts 99)))) + (let [tam (transient (array-map :a 1 :b 2))] + (is (= true (contains? tam :a))) + (is (= false (contains? tam :x))) + (is (= 1 (get tam :a))) + (is (= nil (get tam :x))) + (is (= [:a 1] (find tam :a))) + (is (= nil (find tam :x)))) + (let [thm (transient (hash-map :a 1 :b 2))] + (is (= true (contains? thm :a))) + (is (= false (contains? thm :x))) + (is (= 1 (get thm :a))) + (is (= nil (get thm :x))) + (is (= [:a 1] (find thm :a))) + (is (= nil (find thm :x))))) From dab54cb872eb94a36d009ab7c87ce72945e64317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20B=C3=BCrgin?= Date: Tue, 19 Sep 2017 18:54:25 +0200 Subject: [PATCH 390/854] Update Guava API URL for clojure.java.javadoc Signed-off-by: Stuart Halloway --- src/clj/clojure/java/javadoc.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/java/javadoc.clj b/src/clj/clojure/java/javadoc.clj index 4eea1ec2e1..863de75cd9 100644 --- a/src/clj/clojure/java/javadoc.clj +++ b/src/clj/clojure/java/javadoc.clj @@ -27,7 +27,7 @@ (def ^:dynamic *remote-javadocs* (ref (sorted-map - "com.google.common." "http://docs.guava-libraries.googlecode.com/git/javadoc/" + "com.google.common." "http://google.github.io/guava/releases/23.0/api/docs/" "java." *core-java-api* "javax." *core-java-api* "org.ietf.jgss." *core-java-api* From e064eb850e80be7a7c24e87d386cccf58b4eb89c Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 6 Oct 2017 14:32:07 -0500 Subject: [PATCH 391/854] update changelog for 1.9.0-beta2 Signed-off-by: Stuart Halloway --- changes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changes.md b/changes.md index 61a57dd7bc..72b04075fd 100644 --- a/changes.md +++ b/changes.md @@ -173,6 +173,8 @@ If a macro has a spec defined via fdef, that spec will be checked at compile tim `doc` - does not expand special cases properly (try, catch) * [CLJ-1242](http://dev.clojure.org/jira/browse/CLJ-1242) equals doesn't throw on sorted collections +* [CLJ-700](http://dev.clojure.org/jira/browse/CLJ-700) + `contains?`, `get`, and `find` broken for transient collections # Changes to Clojure in Version 1.8 From 640c77f9095998c1064dc9b6c5e81601cd1513ba Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 6 Oct 2017 15:48:59 -0500 Subject: [PATCH 392/854] [maven-release-plugin] prepare release clojure-1.9.0-beta2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8202e2c2a8..02bc36e779 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-beta2 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-beta2 From a8f1c6436a8bfe181b0a00cf9e44845dcbbb63ee Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 6 Oct 2017 15:48:59 -0500 Subject: [PATCH 393/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 02bc36e779..8202e2c2a8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-beta2 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-beta2 + HEAD From 3d9b356306db77946bdf4809baeb660f94cec846 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 24 Oct 2017 08:43:41 -0500 Subject: [PATCH 394/854] Add clojure.spec.skip-macros system property to disable spec macro checking Signed-off-by: Stuart Halloway --- src/jvm/clojure/lang/RT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 0c1253ae9d..f4bb9a5b51 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -300,7 +300,7 @@ static public void addURL(Object url) throws MalformedURLException{ } public static boolean checkSpecAsserts = Boolean.getBoolean("clojure.spec.check-asserts"); - +public static boolean instrumentMacros = ! Boolean.getBoolean("clojure.spec.skip-macros"); static volatile boolean CHECK_SPECS = false; static{ @@ -339,7 +339,7 @@ public Object invoke(Object arg1) { throw Util.sneakyThrow(e); } - CHECK_SPECS = true; + CHECK_SPECS = RT.instrumentMacros; } static public Keyword keyword(String ns, String name){ From bfaa0c67152736637a2c2593a93ddbb2971b2d66 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 25 Oct 2017 14:51:40 -0500 Subject: [PATCH 395/854] [maven-release-plugin] prepare release clojure-1.9.0-beta3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8202e2c2a8..4b7bb08e16 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-beta3 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-beta3 From 08e1c941eb584f556745ca57fae1e0b313458c2c Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 25 Oct 2017 14:51:40 -0500 Subject: [PATCH 396/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4b7bb08e16..8202e2c2a8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-beta3 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-beta3 + HEAD From b98ba848d04867c5542d50e46429d9c1f2472719 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 30 Oct 2017 10:23:30 -0500 Subject: [PATCH 397/854] CLJ-2259 Remove unnecessary bigdec? predicate added in 1.9 Signed-off-by: Stuart Halloway --- changes.md | 2 +- pom.xml | 2 +- src/clj/clojure/core.clj | 5 ---- test/clojure/test_clojure/predicates.clj | 34 ++++++++++++------------ 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/changes.md b/changes.md index 72b04075fd..d387eb5eb3 100644 --- a/changes.md +++ b/changes.md @@ -29,7 +29,7 @@ Specs rely heavily on predicates and many new type and value oriented predicates * `boolean?` * `int?` `pos-int?` `neg-int?` `nat-int?` -* `double?` `bigdec?` +* `double?` * `ident?` `simple-ident?` `qualified-ident?` * `simple-symbol?` `qualified-symbol?` * `simple-keyword?` `qualified-keyword?` diff --git a/pom.xml b/pom.xml index 8202e2c2a8..5ea5de9897 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ org.clojure spec.alpha - 0.1.134 + 0.1.143 org.clojure diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 89b20a9bda..5fe3b1bc03 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1420,11 +1420,6 @@ {:added "1.9"} [x] (instance? Double x)) -(defn bigdec? - "Return true if x is a BigDecimal" - {:added "1.9"} - [x] (instance? java.math.BigDecimal x)) - ;; (defn complement diff --git a/test/clojure/test_clojure/predicates.clj b/test/clojure/test_clojure/predicates.clj index 906819624b..7efdc6fe71 100644 --- a/test/clojure/test_clojure/predicates.clj +++ b/test/clojure/test_clojure/predicates.clj @@ -147,23 +147,23 @@ barray (byte-array 0) uri (java.net.URI. "http://clojure.org")] [' - [identity int? pos-int? neg-int? nat-int? double? boolean? indexed? seqable? ident? uuid? bigdec? inst? uri? bytes?] - [0 true false false true false false false false false false false false false false] - [1 true true false true false false false false false false false false false false] - [-1 true false true false false false false false false false false false false false] - [1.0 false false false false true false false false false false false false false false] - [true false false false false false true false false false false false false false false] - [[] false false false false false false true true false false false false false false] - [nil false false false false false false false true false false false false false false] - [{} false false false false false false false true false false false false false false] - [:foo false false false false false false false false true false false false false false] - ['foo false false false false false false false false true false false false false false] - [0.0M false false false false false false false false false false true false false false] - [0N false false false false false false false false false false false false false false] - [uuid false false false false false false false false false true false false false false] - [uri false false false false false false false false false false false false true false] - [now false false false false false false false false false false false true false false] - [barray false false false false false false false true false false false false false true]])) + [identity int? pos-int? neg-int? nat-int? double? boolean? indexed? seqable? ident? uuid? decimal? inst? uri? bytes?] + [0 true false false true false false false false false false false false false false] + [1 true true false true false false false false false false false false false false] + [-1 true false true false false false false false false false false false false false] + [1.0 false false false false true false false false false false false false false false] + [true false false false false false true false false false false false false false false] + [[] false false false false false false true true false false false false false false] + [nil false false false false false false false true false false false false false false] + [{} false false false false false false false true false false false false false false] + [:foo false false false false false false false false true false false false false false] + ['foo false false false false false false false false true false false false false false] + [0.0M false false false false false false false false false false true false false false] + [0N false false false false false false false false false false false false false false] + [uuid false false false false false false false false false true false false false false] + [uri false false false false false false false false false false false false true false] + [now false false false false false false false false false false false true false false] + [barray false false false false false false false true false false false false false true]])) (deftest test-preds (let [[preds & rows] pred-val-table] From e7ff2d6c1d27abedf4bd1c5de3557b7bad720763 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 31 Oct 2017 09:05:00 -0500 Subject: [PATCH 398/854] [maven-release-plugin] prepare release clojure-1.9.0-beta4 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5ea5de9897..4865d02e21 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-beta4 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-beta4 From 44f6aad12dd6f47c8ab717f7753299ef2a766ce8 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 31 Oct 2017 09:05:00 -0500 Subject: [PATCH 399/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4865d02e21..5ea5de9897 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-beta4 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-beta4 + HEAD From e749d85ccc404577d679950788e8820ccb027e73 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 7 Nov 2017 08:36:15 -0600 Subject: [PATCH 400/854] [maven-release-plugin] prepare release clojure-1.9.0-RC1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5ea5de9897..d87634b14c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-RC1 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-RC1 From a19c36927598677c32099dabd0fdb9d3097df259 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 7 Nov 2017 08:36:15 -0600 Subject: [PATCH 401/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d87634b14c..5ea5de9897 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-RC1 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-RC1 + HEAD From 7ae47e64c5144464bbd591cad8c5094bd52e3195 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 27 Nov 2017 13:56:22 -0600 Subject: [PATCH 402/854] Local build with deps included Signed-off-by: Stuart Halloway --- build.xml | 7 +++++++ pom.xml | 41 +++++++++++++++++++++++++++++++++++++++++ readme.txt | 29 ++++++++++++++++------------- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/build.xml b/build.xml index 07628534c8..35f4b28fad 100644 --- a/build.xml +++ b/build.xml @@ -194,4 +194,11 @@ + + + + + + + diff --git a/pom.xml b/pom.xml index 5ea5de9897..d5997042bc 100644 --- a/pom.xml +++ b/pom.xml @@ -315,5 +315,46 @@ + + local + + + org.clojure + test.check + 0.9.0 + + + org.clojure + clojure + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + + + clojure.main + + + clojure.jar + + + + + + + diff --git a/readme.txt b/readme.txt index 4871d0f114..70ee0758b6 100644 --- a/readme.txt +++ b/readme.txt @@ -7,29 +7,32 @@ * the terms of this license. * You must not remove this notice, or any other, from this software. -Docs: http://clojure.org +Docs: https://clojure.org Feedback: http://groups.google.com/group/clojure -Getting Started: http://dev.clojure.org/display/doc/Getting+Started +Getting Started: https://clojure.org/guides/getting_started -To run: java -cp clojure-${VERSION}.jar clojure.main - -To build locally with Ant: +To build and run locally with Ant: One-time setup: ./antsetup.sh - To build: ant + To build: ant local + To run: java -jar clojure.jar -Maven 2 build instructions: +To build locally with Maven: - To build: mvn package - The built JARs will be in target/ + To build (output JARs in target/): + mvn package - To build without testing: mvn package -Dmaven.test.skip=true + To build without testing: + mvn package -Dmaven.test.skip=true - To build and install in local Maven repository: mvn install + To build and install in local Maven repository: + mvn install - To build a ZIP distribution: mvn package -Pdistribution - The built .zip will be in target/ + To build a standalone jar with dependencies included: + mvn -Plocal -Dmaven.test.skip=true package + To run with the standalone jar: + java -jar clojure.jar -------------------------------------------------------------------------- This program uses the ASM bytecode engineering library which is distributed From d7e04247af8cbfa34fe4795ebd0ee25de2e83dca Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 27 Nov 2017 15:22:08 -0600 Subject: [PATCH 403/854] [maven-release-plugin] prepare release clojure-1.9.0-RC2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d5997042bc..d22d69fc93 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0-RC2 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0-RC2 From 0592567e000e0f986834abe661a0a15d3a57178c Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 27 Nov 2017 15:22:08 -0600 Subject: [PATCH 404/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d22d69fc93..d5997042bc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-RC2 + 1.9.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0-RC2 + HEAD From 841fa60b41bc74367fb16ec65d025ea5bde7a617 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 8 Dec 2017 07:59:39 -0600 Subject: [PATCH 405/854] [maven-release-plugin] prepare release clojure-1.9.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d5997042bc..680a76426b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0-master-SNAPSHOT + 1.9.0 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.9.0 From 269b2d1760b71b995ed4f13ea026852cd40e94f3 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 8 Dec 2017 07:59:39 -0600 Subject: [PATCH 406/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 680a76426b..bf8888d5b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.9.0 + 1.10.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.9.0 + HEAD From 537d5ebd20174cc7249af5b6d0c42d75ed48d381 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 18 Jan 2018 17:02:12 -0500 Subject: [PATCH 407/854] added string capture mode to LNPReader --- .../lang/LineNumberingPushbackReader.java | 67 ++++++++++++------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/jvm/clojure/lang/LineNumberingPushbackReader.java b/src/jvm/clojure/lang/LineNumberingPushbackReader.java index 47067e9737..a774f0054d 100644 --- a/src/jvm/clojure/lang/LineNumberingPushbackReader.java +++ b/src/jvm/clojure/lang/LineNumberingPushbackReader.java @@ -27,46 +27,65 @@ public class LineNumberingPushbackReader extends PushbackReader{ private boolean _atLineStart = true; private boolean _prev; -private int _columnNumber = 1; +private int _columnNumber = 1; +private StringBuilder sb = null; public LineNumberingPushbackReader(Reader r){ super(new LineNumberReader(r)); } -public LineNumberingPushbackReader(Reader r, int size){ - super(new LineNumberReader(r, size)); -} - +public LineNumberingPushbackReader(Reader r, int size){ + super(new LineNumberReader(r, size)); +} + public int getLineNumber(){ return ((LineNumberReader) in).getLineNumber() + 1; } - -public void setLineNumber(int line) { ((LineNumberReader) in).setLineNumber(line - 1); } - -public int getColumnNumber(){ - return _columnNumber; -} - + +public void setLineNumber(int line) { ((LineNumberReader) in).setLineNumber(line - 1); } + +public void captureString(){ + this.sb = new StringBuilder(); +} + +public String getString(){ + if(sb != null) + { + String ret = sb.toString(); + sb = null; + return ret; + } + return null; +} + +public int getColumnNumber(){ + return _columnNumber; +} + public int read() throws IOException{ int c = super.read(); _prev = _atLineStart; - if((c == newline) || (c == -1)) - { - _atLineStart = true; - _columnNumber = 1; - } - else - { - _atLineStart = false; - _columnNumber++; - } + if((c == newline) || (c == -1)) + { + _atLineStart = true; + _columnNumber = 1; + } + else + { + _atLineStart = false; + _columnNumber++; + } + if(sb != null) + sb.append((char)c); return c; } public void unread(int c) throws IOException{ super.unread(c); _atLineStart = _prev; - _columnNumber--; + _columnNumber--; + if(sb != null) + sb.deleteCharAt(sb.length()-1); } public String readLine() throws IOException{ @@ -85,7 +104,7 @@ public String readLine() throws IOException{ line = (rest == null) ? first : first + rest; _prev = false; _atLineStart = true; - _columnNumber = 1; + _columnNumber = 1; break; } return line; From 4f1dcff047f029301be9f5653cfb7db63f67e25d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 18 Jan 2018 16:14:53 -0600 Subject: [PATCH 408/854] [maven-release-plugin] prepare release clojure-1.10.0-alpha1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bf8888d5b8..55b4110bcc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-master-SNAPSHOT + 1.10.0-alpha1 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.10.0-alpha1 From 9ae44d15dbf9319267ff0880f29f5e7d7849f01f Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 18 Jan 2018 16:14:54 -0600 Subject: [PATCH 409/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 55b4110bcc..bf8888d5b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-alpha1 + 1.10.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.10.0-alpha1 + HEAD From 76cb5cfdd446db69700c7c619c738a4ef2026947 Mon Sep 17 00:00:00 2001 From: Nicola Mometto Date: Fri, 19 Jan 2018 13:24:52 +0000 Subject: [PATCH 410/854] CLJ-2313: fix string capture in readLine Signed-off-by: Rich Hickey --- src/jvm/clojure/lang/LineNumberingPushbackReader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jvm/clojure/lang/LineNumberingPushbackReader.java b/src/jvm/clojure/lang/LineNumberingPushbackReader.java index a774f0054d..37640cf120 100644 --- a/src/jvm/clojure/lang/LineNumberingPushbackReader.java +++ b/src/jvm/clojure/lang/LineNumberingPushbackReader.java @@ -101,6 +101,8 @@ public String readLine() throws IOException{ default: String first = String.valueOf((char) c); String rest = ((LineNumberReader)in).readLine(); + if (sb != null) + sb.append(rest+"\n"); line = (rest == null) ? first : first + rest; _prev = false; _atLineStart = true; From b6d7760d5532cf26c714a635b997256597700197 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 19 Jan 2018 08:32:38 -0600 Subject: [PATCH 411/854] [maven-release-plugin] prepare release clojure-1.10.0-alpha2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bf8888d5b8..6002dc2e79 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-master-SNAPSHOT + 1.10.0-alpha2 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.10.0-alpha2 From 1215ba346ffea3fe48def6ec70542e3300b6f9ed Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 19 Jan 2018 08:32:38 -0600 Subject: [PATCH 412/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6002dc2e79..bf8888d5b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-alpha2 + 1.10.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.10.0-alpha2 + HEAD From 86a158d0e0718f5c93f9f2bb71e26bc794e7d58e Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Thu, 8 Feb 2018 16:12:53 -0500 Subject: [PATCH 413/854] first cut of prepl --- src/clj/clojure/core.clj | 55 +++++++ src/clj/clojure/core/server.clj | 139 +++++++++++++++++- src/clj/clojure/core_print.clj | 24 +++ .../lang/LineNumberingPushbackReader.java | 2 +- 4 files changed, 214 insertions(+), 6 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 5fe3b1bc03..7f25cc403a 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -3759,6 +3759,21 @@ ([opts stream] (. clojure.lang.LispReader (read stream opts)))) +(defn read+string + "Like read, and taking the same args. stream must be a LineNumberingPushbackReader. + Returns a vector containing the object read and the (whitespace-trimmed) string read." + {:added "1.10"} + ([] (read+string *out*)) + ([^clojure.lang.LineNumberingPushbackReader stream & args] + (try + (.captureString stream) + (let [o (apply read stream args) + s (.trim (.getString stream))] + [o s]) + (catch Throwable ex + (.getString stream) + (throw ex))))) + (defn read-line "Reads the next line from stream that is the current value of *in* ." {:added "1.0" @@ -7766,3 +7781,43 @@ "Return true if x is a java.net.URI" {:added "1.9"} [x] (instance? java.net.URI x)) + +(defonce ^:private tapset (atom #{})) +(defonce ^:private ^java.util.concurrent.ArrayBlockingQueue tapq (java.util.concurrent.ArrayBlockingQueue. 1024)) + +(defn add-tap + "adds f, a fn of one argument, to the tap set. This function will be called with anything sent via tap>. + This function may (briefly) block (e.g. for streams), and will never impede calls to tap>, + but blocking indefinitely may cause tap values to be dropped. + Remember f in order to remove-tap" + {:added "1.10"} + [f] + (swap! tapset conj f) + nil) + +(defn remove-tap + "remove f from the tap set the tap set." + {:added "1.10"} + [f] + (swap! tapset disj f) + nil) + +(defn tap> + "sends x to any taps. Will not block. Returns true if there was room in the queue, + false if not (dropped)." + {:added "1.10"} + [x] + (.offer tapq x)) + +(defonce ^:private tap-loop + (doto (Thread. + #(let [x (.take tapq) + taps @tapset] + (doseq [tap taps] + (try + (tap x) + (catch Throwable ex))) + (recur)) + "clojure.core/tap-loop") + (.setDaemon true) + (.start))) diff --git a/src/clj/clojure/core/server.clj b/src/clj/clojure/core/server.clj index 8bcf3627c0..72f5385b9f 100644 --- a/src/clj/clojure/core/server.clj +++ b/src/clj/clojure/core/server.clj @@ -12,8 +12,11 @@ (:require [clojure.string :as str] [clojure.edn :as edn] [clojure.main :as m]) - (:import [java.net InetAddress Socket ServerSocket SocketException] - [java.util.concurrent.locks ReentrantLock])) + (:import + [clojure.lang LineNumberingPushbackReader] + [java.net InetAddress Socket ServerSocket SocketException] + [java.io Reader Writer PrintWriter BufferedWriter BufferedReader InputStreamReader OutputStreamWriter] + [java.util.concurrent.locks ReentrantLock])) (set! *warn-on-reflection* true) @@ -106,8 +109,8 @@ (when (not (.isClosed socket)) (try (let [conn (.accept socket) - in (clojure.lang.LineNumberingPushbackReader. (java.io.InputStreamReader. (.getInputStream conn))) - out (java.io.BufferedWriter. (java.io.OutputStreamWriter. (.getOutputStream conn))) + in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream conn))) + out (BufferedWriter. (OutputStreamWriter. (.getOutputStream conn))) client-id (str client-counter)] (thread (str "Clojure Connection " name " " client-id) client-daemon @@ -179,4 +182,130 @@ [] (m/repl :init repl-init - :read repl-read)) \ No newline at end of file + :read repl-read)) + +(defn prepl + "a REPL with structured output (for programs) + reads forms to eval from in-reader (a LineNumberingPushbackReader) + Closing the input or passing the form :repl/quit will cause it to return + + Calls out-fn with data, one of: + {:tag :ret + :val val ;;eval result + :ns ns-name-string + :ms long ;;eval time in milliseconds + :form string ;;iff successfully read + } + {:tag :out + :val string} ;chars from during-eval *out* + {:tag :err + :val string} ;chars from during-eval *err* + {:tag :tap + :val val} ;values from tap> + + You might get more than one :out or :err per eval, but exactly one :ret + tap output can happen at any time (i.e. between evals) + If during eval an attempt is made to read *in* it will read from in-reader unless :stdin is supplied +" + [in-reader out-fn & {:keys [stdin]}] + (let [EOF (Object.) + tapfn #(out-fn {:tag :tap :val %1})] + (m/with-bindings + (in-ns 'user) + (binding [*in* (or stdin in-reader) + *out* (PrintWriter-on #(out-fn {:tag :out :val %1}) nil) + *err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil)] + (try + (add-tap tapfn) + (loop [] + (when (try + (let [[form s] (read+string in-reader false EOF)] + (try + (when-not (identical? form EOF) + (let [start (System/nanoTime) + ret (eval form) + ms (quot (- (System/nanoTime) start) 1000000)] + (when-not (= :repl/quit ret) + (set! *3 *2) + (set! *2 *1) + (set! *1 ret) + (out-fn {:tag :ret + :val (if (instance? Throwable ret) + (Throwable->map ret) + ret) + :ns (str (.name *ns*)) + :ms ms + :form s}) + true))) + (catch Throwable ex + (set! *e ex) + (out-fn {:tag :ret :val (Throwable->map ex) :ns (str (.name *ns*)) :form s}) + true))) + (catch Throwable ex + (set! *e ex) + (out-fn {:tag :ret :val (Throwable->map ex) :ns (str (.name *ns*))}) + true)) + (recur))) + (finally + (remove-tap tapfn))))))) + +(defn- resolve-fn [valf] + (if (symbol? valf) + (or (resolve valf) + (when-let [nsname (namespace valf)] + (require (symbol nsname)) + (resolve valf)) + (throw (Exception. (str "can't resolve: " valf)))) + valf)) + +(defn io-prepl + "prepl bound to *in* and *out*, suitable for use with e.g. server/repl (socket-repl). + :ret and :tap vals will be processed by valf, a fn of one argument + or a symbol naming same (default pr-str)" + [& {:keys [valf] :or {valf pr-str}}] + (let [valf (resolve-fn valf) + out *out* + lock (Object.)] + (prepl *in* + #(binding [*out* out, *flush-on-newline* true, *print-readably* true] + (locking lock + (prn (cond-> %1 + (#{:ret :tap} (:tag %1)) + (assoc :val (valf (:val %1)))))))))) + +(defn remote-prepl + "Implements a prepl on in-reader and out-fn by forwarding to a + remote [io-]prepl over a socket. Messages will be read by readf, a + fn of a LineNumberingPushbackReader and EOF value or a symbol naming + same (default #(read %1 false %2)), + :ret and :tap vals will be processed by valf, a fn of one argument + or a symbol naming same (default read-string). If that function + throws, :val will be unprocessed." + [^String host port ^Reader + in-reader out-fn & {:keys [valf readf] :or {valf read-string, readf #(read %1 false %2)}}] + (let [valf (resolve-fn valf) + readf (resolve-fn readf) + ^long port (if (string? port) (Integer/valueOf ^String port) port) + socket (Socket. host port) + rd (-> socket .getInputStream InputStreamReader. BufferedReader. LineNumberingPushbackReader.) + wr (-> socket .getOutputStream OutputStreamWriter.) + EOF (Object.)] + (thread "clojure.core.server/remote-prepl" true + (try (loop [] + (let [{:keys [tag val] :as m} (readf rd EOF)] + (when-not (identical? m EOF) + (out-fn (cond-> m + (#{:ret :tap} tag) + (assoc :val (try (valf val) (catch Throwable ex val))))) + (recur)))) + (finally + (.close wr)))) + (let [buf (char-array 1024)] + (try (loop [] + (let [n (.read in-reader buf)] + (when-not (= n -1) + (.write wr buf 0 n) + (.flush wr) + (recur)))) + (finally + (.close rd)))))) diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj index 1b2b7a5765..45d27da168 100644 --- a/src/clj/clojure/core_print.clj +++ b/src/clj/clojure/core_print.clj @@ -545,3 +545,27 @@ (print-method (:form o) w)) (def ^{:private true} print-initialized true) + +(defn ^java.io.PrintWriter PrintWriter-on + "implements java.io.PrintWriter given flush-fn, which will be called + when .flush() is called, with a string built up since the last call to .flush(). + if not nil, close-fn will be called with no arguments when .close is called" + {:added "1.10"} + [flush-fn close-fn] + (let [sb (StringBuilder.)] + (-> (proxy [Writer] [] + (flush [] + (when (pos? (.length sb)) + (flush-fn (.toString sb))) + (.setLength sb 0)) + (close [] + (.flush ^Writer this) + (when close-fn (close-fn)) + nil) + (write [str-cbuf off len] + (when (pos? len) + (if (instance? String str-cbuf) + (.append sb ^String str-cbuf ^int off ^int len) + (.append sb ^chars str-cbuf ^int off ^int len))))) + java.io.BufferedWriter. + java.io.PrintWriter.))) diff --git a/src/jvm/clojure/lang/LineNumberingPushbackReader.java b/src/jvm/clojure/lang/LineNumberingPushbackReader.java index 37640cf120..a761c44039 100644 --- a/src/jvm/clojure/lang/LineNumberingPushbackReader.java +++ b/src/jvm/clojure/lang/LineNumberingPushbackReader.java @@ -75,7 +75,7 @@ public int read() throws IOException{ _atLineStart = false; _columnNumber++; } - if(sb != null) + if(sb != null && c != -1) sb.append((char)c); return c; } From c53dd3bd4f0b9d91ef96aafa22bec14c78b1f5a4 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 8 Feb 2018 15:26:06 -0600 Subject: [PATCH 414/854] [maven-release-plugin] prepare release clojure-1.10.0-alpha3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bf8888d5b8..b95d03506f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-master-SNAPSHOT + 1.10.0-alpha3 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.10.0-alpha3 From a244a62d2c114d6543f6b2a01d399a3db4eb04bd Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 8 Feb 2018 15:26:06 -0600 Subject: [PATCH 415/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b95d03506f..bf8888d5b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-alpha3 + 1.10.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.10.0-alpha3 + HEAD From 08e592f4decbaa08de570ded9ac169785b1608f9 Mon Sep 17 00:00:00 2001 From: Rich Hickey Date: Fri, 9 Feb 2018 11:20:15 -0500 Subject: [PATCH 416/854] *in* --- src/clj/clojure/core.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 7f25cc403a..dc6d29fc90 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -3763,7 +3763,7 @@ "Like read, and taking the same args. stream must be a LineNumberingPushbackReader. Returns a vector containing the object read and the (whitespace-trimmed) string read." {:added "1.10"} - ([] (read+string *out*)) + ([] (read+string *in*)) ([^clojure.lang.LineNumberingPushbackReader stream & args] (try (.captureString stream) From 462a66e34bb52ef7dc0eca55df70cbb56db56b3c Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 9 Feb 2018 10:25:57 -0600 Subject: [PATCH 417/854] [maven-release-plugin] prepare release clojure-1.10.0-alpha4 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bf8888d5b8..dcde0f7e48 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-master-SNAPSHOT + 1.10.0-alpha4 http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - HEAD + clojure-1.10.0-alpha4 From 131c5f71b8d65169d233b03d39f7582a1a5d926e Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 9 Feb 2018 10:25:57 -0600 Subject: [PATCH 418/854] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index dcde0f7e48..bf8888d5b8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ clojure clojure jar - 1.10.0-alpha4 + 1.10.0-master-SNAPSHOT http://clojure.org/ Clojure core environment and runtime library. @@ -30,7 +30,7 @@ scm:git:git@github.com:clojure/clojure.git scm:git:git@github.com:clojure/clojure.git git@github.com:clojure/clojure.git - clojure-1.10.0-alpha4 + HEAD From 4ef4b1ed7a2e8bb0aaaacfb0942729252c2c3091 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Tue, 29 May 2018 14:51:42 -0500 Subject: [PATCH 419/854] CLJ-2354 tunnel nil through tap> Signed-off-by: Rich Hickey --- src/clj/clojure/core.clj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index dc6d29fc90..d71c9cb8d4 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -7807,11 +7807,12 @@ false if not (dropped)." {:added "1.10"} [x] - (.offer tapq x)) + (.offer tapq (if (nil? x) ::tap-nil x))) (defonce ^:private tap-loop (doto (Thread. - #(let [x (.take tapq) + #(let [t (.take tapq) + x (if (identical? ::tap-nil t) nil t) taps @tapset] (doseq [tap taps] (try From 148a0c9aa83b12efaa43c72e5ee39baff0948f9a Mon Sep 17 00:00:00 2001 From: Ghadi Shayban Date: Fri, 22 Jun 2018 23:28:23 -0400 Subject: [PATCH 420/854] vendor asm to sha 88a0aa8a79df7370cd178281bdf690ac2361c19a the following script vendors asm + GeneratorAdapter.java and its deps in $CLOJURE/src/main/java under the package clojure.asm #!/bin/bash # author Ghadi Shayban set -e ASMURI=https://gitlab.ow2.org/asm/asm.git if [ -z ${1+x} ] then echo error: provide an asm git sha / ref echo recent asm tags: git ls-remote --refs --tags $ASMURI \ | cut -f 3 -d / | tac | head -n 10 exit 1 fi ASMROOT=asmvendor CLJASM=src/jvm/clojure/asm echo shallow clone git clone --quiet --no-checkout --depth 30 $ASMURI $ASMROOT pushd $ASMROOT > /dev/null GITREF=$(git rev-parse $1) git checkout --quiet $GITREF popd > /dev/null echo removing existing clojure.asm git rm -r --ignore-unmatch $CLJASM > /dev/null mkdir -p $CLJASM $CLJASM/commons echo copying vendored files cp $ASMROOT/asm/src/main/java/org/objectweb/asm/*.java $CLJASM for cls in GeneratorAdapter Method LocalVariablesSorter TableSwitchGenerator; do cp \ $ASMROOT/asm-commons/src/main/java/org/objectweb/asm/commons/${cls}.java \ $CLJASM/commons done echo rewriting package names find $CLJASM -name '*.java' -print0 | xargs -0 sed -iBAK 's/org.objectweb.asm/clojure.asm/g' find $CLJASM -name '*BAK' -delete echo git commit git add $CLJASM cat - "${BASH_SOURCE[0]}" > COMMIT_MSG < /dev/null \ && rm COMMIT_MSG rm -rf $ASMROOT Signed-off-by: Stuart Halloway --- src/jvm/clojure/asm/AnnotationVisitor.java | 271 +- src/jvm/clojure/asm/AnnotationWriter.java | 662 +- src/jvm/clojure/asm/Attribute.java | 526 +- src/jvm/clojure/asm/ByteVector.java | 602 +- src/jvm/clojure/asm/ClassReader.java | 5593 ++++++++++------- src/jvm/clojure/asm/ClassVisitor.java | 563 +- src/jvm/clojure/asm/ClassWriter.java | 2646 +++----- src/jvm/clojure/asm/ConstantDynamic.java | 147 + src/jvm/clojure/asm/Constants.java | 177 + src/jvm/clojure/asm/Context.java | 231 +- src/jvm/clojure/asm/CurrentFrame.java | 56 + src/jvm/clojure/asm/Edge.java | 142 +- src/jvm/clojure/asm/FieldVisitor.java | 217 +- src/jvm/clojure/asm/FieldWriter.java | 557 +- src/jvm/clojure/asm/Frame.java | 2702 ++++---- src/jvm/clojure/asm/Handle.java | 316 +- src/jvm/clojure/asm/Handler.java | 289 +- src/jvm/clojure/asm/Item.java | 311 - src/jvm/clojure/asm/Label.java | 1135 ++-- src/jvm/clojure/asm/MethodVisitor.java | 1428 +++-- src/jvm/clojure/asm/MethodWriter.java | 4990 +++++++-------- src/jvm/clojure/asm/ModuleVisitor.java | 175 + src/jvm/clojure/asm/ModuleWriter.java | 253 + src/jvm/clojure/asm/Opcodes.java | 643 +- src/jvm/clojure/asm/Symbol.java | 240 + src/jvm/clojure/asm/SymbolTable.java | 1277 ++++ src/jvm/clojure/asm/Type.java | 1727 ++--- src/jvm/clojure/asm/TypePath.java | 201 + src/jvm/clojure/asm/TypeReference.java | 436 ++ .../clojure/asm/commons/AdviceAdapter.java | 625 -- .../clojure/asm/commons/AnalyzerAdapter.java | 920 --- .../asm/commons/CodeSizeEvaluator.java | 217 - .../clojure/asm/commons/GeneratorAdapter.java | 2826 ++++----- .../asm/commons/InstructionAdapter.java | 1090 ---- .../asm/commons/LocalVariablesSorter.java | 645 +- src/jvm/clojure/asm/commons/Method.java | 467 +- .../asm/commons/SerialVersionUIDAdder.java | 533 -- .../clojure/asm/commons/StaticInitMerger.java | 96 - .../asm/commons/TableSwitchGenerator.java | 78 +- src/jvm/clojure/asm/commons/package.html | 48 - src/jvm/clojure/asm/package.html | 87 - 41 files changed, 18003 insertions(+), 18142 deletions(-) create mode 100644 src/jvm/clojure/asm/ConstantDynamic.java create mode 100644 src/jvm/clojure/asm/Constants.java create mode 100644 src/jvm/clojure/asm/CurrentFrame.java delete mode 100644 src/jvm/clojure/asm/Item.java create mode 100644 src/jvm/clojure/asm/ModuleVisitor.java create mode 100644 src/jvm/clojure/asm/ModuleWriter.java create mode 100644 src/jvm/clojure/asm/Symbol.java create mode 100644 src/jvm/clojure/asm/SymbolTable.java create mode 100644 src/jvm/clojure/asm/TypePath.java create mode 100644 src/jvm/clojure/asm/TypeReference.java delete mode 100644 src/jvm/clojure/asm/commons/AdviceAdapter.java delete mode 100644 src/jvm/clojure/asm/commons/AnalyzerAdapter.java delete mode 100644 src/jvm/clojure/asm/commons/CodeSizeEvaluator.java delete mode 100644 src/jvm/clojure/asm/commons/InstructionAdapter.java delete mode 100644 src/jvm/clojure/asm/commons/SerialVersionUIDAdder.java delete mode 100644 src/jvm/clojure/asm/commons/StaticInitMerger.java delete mode 100644 src/jvm/clojure/asm/commons/package.html delete mode 100644 src/jvm/clojure/asm/package.html diff --git a/src/jvm/clojure/asm/AnnotationVisitor.java b/src/jvm/clojure/asm/AnnotationVisitor.java index eb01145144..940bfb77c6 100644 --- a/src/jvm/clojure/asm/AnnotationVisitor.java +++ b/src/jvm/clojure/asm/AnnotationVisitor.java @@ -1,169 +1,150 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A visitor to visit a Java annotation. The methods of this class must be - * called in the following order: ( visit | visitEnum | - * visitAnnotation | visitArray )* visitEnd. + * A visitor to visit a Java annotation. The methods of this class must be called in the following + * order: ( visit | visitEnum | visitAnnotation | visitArray )* + * visitEnd. * * @author Eric Bruneton * @author Eugene Kuleshov */ public abstract class AnnotationVisitor { - /** - * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. - */ - protected final int api; + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + */ + protected final int api; - /** - * The annotation visitor to which this visitor must delegate method calls. - * May be null. - */ - protected AnnotationVisitor av; + /** The annotation visitor to which this visitor must delegate method calls. May be null. */ + protected AnnotationVisitor av; - /** - * Constructs a new {@link AnnotationVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - */ - public AnnotationVisitor(final int api) { - this(api, null); - } + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + */ + public AnnotationVisitor(final int api) { + this(api, null); + } - /** - * Constructs a new {@link AnnotationVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - * @param av - * the annotation visitor to which this visitor must delegate - * method calls. May be null. - */ - public AnnotationVisitor(final int api, final AnnotationVisitor av) { - if (api != Opcodes.ASM4) { - throw new IllegalArgumentException(); - } - this.api = api; - this.av = av; + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + * @param annotationVisitor the annotation visitor to which this visitor must delegate method + * calls. May be null. + */ + public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) { + if (api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM7_EXPERIMENTAL) { + throw new IllegalArgumentException(); } + this.api = api; + this.av = annotationVisitor; + } - /** - * Visits a primitive value of the annotation. - * - * @param name - * the value name. - * @param value - * the actual value, whose type must be {@link Byte}, - * {@link Boolean}, {@link Character}, {@link Short}, - * {@link Integer} , {@link Long}, {@link Float}, {@link Double}, - * {@link String} or {@link Type} or OBJECT or ARRAY sort. This - * value can also be an array of byte, boolean, short, char, int, - * long, float or double values (this is equivalent to using - * {@link #visitArray visitArray} and visiting each array element - * in turn, but is more convenient). - */ - public void visit(String name, Object value) { - if (av != null) { - av.visit(name, value); - } + /** + * Visits a primitive value of the annotation. + * + * @param name the value name. + * @param value the actual value, whose type must be {@link Byte}, {@link Boolean}, {@link + * Character}, {@link Short}, {@link Integer} , {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type} of {@link Type#OBJECT} or {@link Type#ARRAY} sort. This + * value can also be an array of byte, boolean, short, char, int, long, float or double values + * (this is equivalent to using {@link #visitArray} and visiting each array element in turn, + * but is more convenient). + */ + public void visit(final String name, final Object value) { + if (av != null) { + av.visit(name, value); } + } - /** - * Visits an enumeration value of the annotation. - * - * @param name - * the value name. - * @param desc - * the class descriptor of the enumeration class. - * @param value - * the actual enumeration value. - */ - public void visitEnum(String name, String desc, String value) { - if (av != null) { - av.visitEnum(name, desc, value); - } + /** + * Visits an enumeration value of the annotation. + * + * @param name the value name. + * @param descriptor the class descriptor of the enumeration class. + * @param value the actual enumeration value. + */ + public void visitEnum(final String name, final String descriptor, final String value) { + if (av != null) { + av.visitEnum(name, descriptor, value); } + } - /** - * Visits a nested annotation value of the annotation. - * - * @param name - * the value name. - * @param desc - * the class descriptor of the nested annotation class. - * @return a visitor to visit the actual nested annotation value, or - * null if this visitor is not interested in visiting this - * nested annotation. The nested annotation value must be fully - * visited before calling other methods on this annotation - * visitor. - */ - public AnnotationVisitor visitAnnotation(String name, String desc) { - if (av != null) { - return av.visitAnnotation(name, desc); - } - return null; + /** + * Visits a nested annotation value of the annotation. + * + * @param name the value name. + * @param descriptor the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or null if this visitor + * is not interested in visiting this nested annotation. The nested annotation value must + * be fully visited before calling other methods on this annotation visitor. + */ + public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { + if (av != null) { + return av.visitAnnotation(name, descriptor); } + return null; + } - /** - * Visits an array value of the annotation. Note that arrays of primitive - * types (such as byte, boolean, short, char, int, long, float or double) - * can be passed as value to {@link #visit visit}. This is what - * {@link ClassReader} does. - * - * @param name - * the value name. - * @return a visitor to visit the actual array value elements, or - * null if this visitor is not interested in visiting these - * values. The 'name' parameters passed to the methods of this - * visitor are ignored. All the array values must be visited - * before calling other methods on this annotation visitor. - */ - public AnnotationVisitor visitArray(String name) { - if (av != null) { - return av.visitArray(name); - } - return null; + /** + * Visits an array value of the annotation. Note that arrays of primitive types (such as byte, + * boolean, short, char, int, long, float or double) can be passed as value to {@link #visit + * visit}. This is what {@link ClassReader} does. + * + * @param name the value name. + * @return a visitor to visit the actual array value elements, or null if this visitor is + * not interested in visiting these values. The 'name' parameters passed to the methods of + * this visitor are ignored. All the array values must be visited before calling other + * methods on this annotation visitor. + */ + public AnnotationVisitor visitArray(final String name) { + if (av != null) { + return av.visitArray(name); } + return null; + } - /** - * Visits the end of the annotation. - */ - public void visitEnd() { - if (av != null) { - av.visitEnd(); - } + /** Visits the end of the annotation. */ + public void visitEnd() { + if (av != null) { + av.visitEnd(); } + } } diff --git a/src/jvm/clojure/asm/AnnotationWriter.java b/src/jvm/clojure/asm/AnnotationWriter.java index 9a40bf8224..4ee16510a3 100644 --- a/src/jvm/clojure/asm/AnnotationWriter.java +++ b/src/jvm/clojure/asm/AnnotationWriter.java @@ -1,318 +1,418 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * An {@link AnnotationVisitor} that generates annotations in bytecode form. + * An {@link AnnotationVisitor} that generates a corresponding 'annotation' or 'type_annotation' + * structure, as defined in the Java Virtual Machine Specification (JVMS). AnnotationWriter + * instances can be chained in a doubly linked list, from which Runtime[In]Visible[Type]Annotations + * attributes can be generated with the {@link #putAnnotations} method. Similarly, arrays of such + * lists can be used to generate Runtime[In]VisibleParameterAnnotations attributes. * + * @see JVMS + * 4.7.16 + * @see JVMS + * 4.7.20 * @author Eric Bruneton * @author Eugene Kuleshov */ final class AnnotationWriter extends AnnotationVisitor { - /** - * The class writer to which this annotation must be added. - */ - private final ClassWriter cw; + /** Where the constants used in this AnnotationWriter must be stored. */ + private final SymbolTable symbolTable; - /** - * The number of values in this annotation. - */ - private int size; + /** + * Whether values are named or not. AnnotationWriter instances used for annotation default and + * annotation arrays use unnamed values (i.e. they generate an 'element_value' structure for each + * value, instead of an element_name_index followed by an element_value). + */ + private final boolean useNamedValues; - /** - * true if values are named, false otherwise. Annotation - * writers used for annotation default and annotation arrays use unnamed - * values. - */ - private final boolean named; + /** + * The 'annotation' or 'type_annotation' JVMS structure corresponding to the annotation values + * visited so far. All the fields of these structures, except the last one - the + * element_value_pairs array, must be set before this ByteVector is passed to the constructor + * (num_element_value_pairs can be set to 0, it is reset to the correct value in {@link + * #visitEnd()}). The element_value_pairs array is filled incrementally in the various visit() + * methods. + * + *

Note: as an exception to the above rules, for AnnotationDefault attributes (which contain a + * single element_value by definition), this ByteVector is initially empty when passed to the + * constructor, and {@link #numElementValuePairsOffset} is set to -1. + */ + private final ByteVector annotation; - /** - * The annotation values in bytecode form. This byte vector only contains - * the values themselves, i.e. the number of values must be stored as a - * unsigned short just before these bytes. - */ - private final ByteVector bv; + /** + * The offset in {@link #annotation} where {@link #numElementValuePairs} must be stored (or -1 for + * the case of AnnotationDefault attributes). + */ + private final int numElementValuePairsOffset; - /** - * The byte vector to be used to store the number of values of this - * annotation. See {@link #bv}. - */ - private final ByteVector parent; + /** The number of element value pairs visited so far. */ + private int numElementValuePairs; - /** - * Where the number of values of this annotation must be stored in - * {@link #parent}. - */ - private final int offset; + /** + * The previous AnnotationWriter. This field is used to store the list of annotations of a + * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations + * (annotation values of annotation type), or for AnnotationDefault attributes. + */ + private final AnnotationWriter previousAnnotation; - /** - * Next annotation writer. This field is used to store annotation lists. - */ - AnnotationWriter next; + /** + * The next AnnotationWriter. This field is used to store the list of annotations of a + * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations + * (annotation values of annotation type), or for AnnotationDefault attributes. + */ + private AnnotationWriter nextAnnotation; - /** - * Previous annotation writer. This field is used to store annotation lists. - */ - AnnotationWriter prev; + // ----------------------------------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------ - // Constructor - // ------------------------------------------------------------------------ - - /** - * Constructs a new {@link AnnotationWriter}. - * - * @param cw - * the class writer to which this annotation must be added. - * @param named - * true if values are named, false otherwise. - * @param bv - * where the annotation values must be stored. - * @param parent - * where the number of annotation values must be stored. - * @param offset - * where in parent the number of annotation values must - * be stored. - */ - AnnotationWriter(final ClassWriter cw, final boolean named, - final ByteVector bv, final ByteVector parent, final int offset) { - super(Opcodes.ASM4); - this.cw = cw; - this.named = named; - this.bv = bv; - this.parent = parent; - this.offset = offset; + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param useNamedValues whether values are named or not. AnnotationDefault and annotation arrays + * use unnamed values. + * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to + * the visited content must be stored. This ByteVector must already contain all the fields of + * the structure except the last one (the element_value_pairs array). + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in + * other cases (e.g. nested or array annotations). + */ + AnnotationWriter( + final SymbolTable symbolTable, + final boolean useNamedValues, + final ByteVector annotation, + final AnnotationWriter previousAnnotation) { + super(Opcodes.ASM6); + this.symbolTable = symbolTable; + this.useNamedValues = useNamedValues; + this.annotation = annotation; + // By hypothesis, num_element_value_pairs is stored in the last unsigned short of 'annotation'. + this.numElementValuePairsOffset = annotation.length == 0 ? -1 : annotation.length - 2; + this.previousAnnotation = previousAnnotation; + if (previousAnnotation != null) { + previousAnnotation.nextAnnotation = this; } + } + + /** + * Constructs a new {@link AnnotationWriter} using named values. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to + * the visited content must be stored. This ByteVector must already contain all the fields of + * the structure except the last one (the element_value_pairs array). + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in + * other cases (e.g. nested or array annotations). + */ + AnnotationWriter( + final SymbolTable symbolTable, + final ByteVector annotation, + final AnnotationWriter previousAnnotation) { + this(symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation); + } - // ------------------------------------------------------------------------ - // Implementation of the AnnotationVisitor abstract class - // ------------------------------------------------------------------------ + // ----------------------------------------------------------------------------------------------- + // Implementation of the AnnotationVisitor abstract class + // ----------------------------------------------------------------------------------------------- - @Override - public void visit(final String name, final Object value) { - ++size; - if (named) { - bv.putShort(cw.newUTF8(name)); - } - if (value instanceof String) { - bv.put12('s', cw.newUTF8((String) value)); - } else if (value instanceof Byte) { - bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index); - } else if (value instanceof Boolean) { - int v = ((Boolean) value).booleanValue() ? 1 : 0; - bv.put12('Z', cw.newInteger(v).index); - } else if (value instanceof Character) { - bv.put12('C', cw.newInteger(((Character) value).charValue()).index); - } else if (value instanceof Short) { - bv.put12('S', cw.newInteger(((Short) value).shortValue()).index); - } else if (value instanceof Type) { - bv.put12('c', cw.newUTF8(((Type) value).getDescriptor())); - } else if (value instanceof byte[]) { - byte[] v = (byte[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('B', cw.newInteger(v[i]).index); - } - } else if (value instanceof boolean[]) { - boolean[] v = (boolean[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index); - } - } else if (value instanceof short[]) { - short[] v = (short[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('S', cw.newInteger(v[i]).index); - } - } else if (value instanceof char[]) { - char[] v = (char[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('C', cw.newInteger(v[i]).index); - } - } else if (value instanceof int[]) { - int[] v = (int[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('I', cw.newInteger(v[i]).index); - } - } else if (value instanceof long[]) { - long[] v = (long[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('J', cw.newLong(v[i]).index); - } - } else if (value instanceof float[]) { - float[] v = (float[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('F', cw.newFloat(v[i]).index); - } - } else if (value instanceof double[]) { - double[] v = (double[]) value; - bv.put12('[', v.length); - for (int i = 0; i < v.length; i++) { - bv.put12('D', cw.newDouble(v[i]).index); - } - } else { - Item i = cw.newConstItem(value); - bv.put12(".s.IFJDCS".charAt(i.type), i.index); - } + @Override + public void visit(final String name, final Object value) { + // Case of an element_value with a const_value_index, class_info_index or array_index field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + if (value instanceof String) { + annotation.put12('s', symbolTable.addConstantUtf8((String) value)); + } else if (value instanceof Byte) { + annotation.put12('B', symbolTable.addConstantInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + int booleanValue = ((Boolean) value).booleanValue() ? 1 : 0; + annotation.put12('Z', symbolTable.addConstantInteger(booleanValue).index); + } else if (value instanceof Character) { + annotation.put12('C', symbolTable.addConstantInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + annotation.put12('S', symbolTable.addConstantInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + annotation.put12('c', symbolTable.addConstantUtf8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + byte[] byteArray = (byte[]) value; + annotation.put12('[', byteArray.length); + for (byte byteValue : byteArray) { + annotation.put12('B', symbolTable.addConstantInteger(byteValue).index); + } + } else if (value instanceof boolean[]) { + boolean[] booleanArray = (boolean[]) value; + annotation.put12('[', booleanArray.length); + for (boolean booleanValue : booleanArray) { + annotation.put12('Z', symbolTable.addConstantInteger(booleanValue ? 1 : 0).index); + } + } else if (value instanceof short[]) { + short[] shortArray = (short[]) value; + annotation.put12('[', shortArray.length); + for (short shortValue : shortArray) { + annotation.put12('S', symbolTable.addConstantInteger(shortValue).index); + } + } else if (value instanceof char[]) { + char[] charArray = (char[]) value; + annotation.put12('[', charArray.length); + for (char charValue : charArray) { + annotation.put12('C', symbolTable.addConstantInteger(charValue).index); + } + } else if (value instanceof int[]) { + int[] intArray = (int[]) value; + annotation.put12('[', intArray.length); + for (int intValue : intArray) { + annotation.put12('I', symbolTable.addConstantInteger(intValue).index); + } + } else if (value instanceof long[]) { + long[] longArray = (long[]) value; + annotation.put12('[', longArray.length); + for (long longValue : longArray) { + annotation.put12('J', symbolTable.addConstantLong(longValue).index); + } + } else if (value instanceof float[]) { + float[] floatArray = (float[]) value; + annotation.put12('[', floatArray.length); + for (float floatValue : floatArray) { + annotation.put12('F', symbolTable.addConstantFloat(floatValue).index); + } + } else if (value instanceof double[]) { + double[] doubleArray = (double[]) value; + annotation.put12('[', doubleArray.length); + for (double doubleValue : doubleArray) { + annotation.put12('D', symbolTable.addConstantDouble(doubleValue).index); + } + } else { + Symbol symbol = symbolTable.addConstant(value); + annotation.put12(".s.IFJDCS".charAt(symbol.tag), symbol.index); } + } - @Override - public void visitEnum(final String name, final String desc, - final String value) { - ++size; - if (named) { - bv.putShort(cw.newUTF8(name)); - } - bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value)); + @Override + public void visitEnum(final String name, final String descriptor, final String value) { + // Case of an element_value with an enum_const_value field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); } + annotation + .put12('e', symbolTable.addConstantUtf8(descriptor)) + .putShort(symbolTable.addConstantUtf8(value)); + } - @Override - public AnnotationVisitor visitAnnotation(final String name, - final String desc) { - ++size; - if (named) { - bv.putShort(cw.newUTF8(name)); - } - // write tag and type, and reserve space for values count - bv.put12('@', cw.newUTF8(desc)).putShort(0); - return new AnnotationWriter(cw, true, bv, bv, bv.length - 2); + @Override + public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { + // Case of an element_value with an annotation_value field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); } + // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs. + annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter(symbolTable, annotation, null); + } - @Override - public AnnotationVisitor visitArray(final String name) { - ++size; - if (named) { - bv.putShort(cw.newUTF8(name)); - } - // write tag, and reserve space for array size - bv.put12('[', 0); - return new AnnotationWriter(cw, false, bv, bv, bv.length - 2); + @Override + public AnnotationVisitor visitArray(final String name) { + // Case of an element_value with an array_value field. + // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1 + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); } + // Write tag, and reserve 2 bytes for num_values. Here we take advantage of the fact that the + // end of an element_value of array type is similar to the end of an 'annotation' structure: an + // unsigned short num_values followed by num_values element_value, versus an unsigned short + // num_element_value_pairs, followed by num_element_value_pairs { element_name_index, + // element_value } tuples. This allows us to use an AnnotationWriter with unnamed values to + // visit the array elements. Its num_element_value_pairs will correspond to the number of array + // elements and will be stored in what is in fact num_values. + annotation.put12('[', 0); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null); + } - @Override - public void visitEnd() { - if (parent != null) { - byte[] data = parent.data; - data[offset] = (byte) (size >>> 8); - data[offset + 1] = (byte) size; - } + @Override + public void visitEnd() { + if (numElementValuePairsOffset != -1) { + byte[] data = annotation.data; + data[numElementValuePairsOffset] = (byte) (numElementValuePairs >>> 8); + data[numElementValuePairsOffset + 1] = (byte) numElementValuePairs; } + } - // ------------------------------------------------------------------------ - // Utility methods - // ------------------------------------------------------------------------ + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- - /** - * Returns the size of this annotation writer list. - * - * @return the size of this annotation writer list. - */ - int getSize() { - int size = 0; - AnnotationWriter aw = this; - while (aw != null) { - size += aw.bv.length; - aw = aw.next; - } - return size; + /** + * Returns the size of a Runtime[In]Visible[Type]Annotations attribute containing this annotation + * and all its predecessors (see {@link #previousAnnotation}. Also adds the attribute name + * to the constant pool of the class (if not null). + * + * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or null. + * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this + * annotation and all its predecessors. This includes the size of the attribute_name_index and + * attribute_length fields. + */ + int computeAnnotationsSize(final String attributeName) { + if (attributeName != null) { + symbolTable.addConstantUtf8(attributeName); } + // The attribute_name_index, attribute_length and num_annotations fields use 8 bytes. + int attributeSize = 8; + AnnotationWriter annotationWriter = this; + while (annotationWriter != null) { + attributeSize += annotationWriter.annotation.length; + annotationWriter = annotationWriter.previousAnnotation; + } + return attributeSize; + } - /** - * Puts the annotations of this annotation writer list into the given byte - * vector. - * - * @param out - * where the annotations must be put. - */ - void put(final ByteVector out) { - int n = 0; - int size = 2; - AnnotationWriter aw = this; - AnnotationWriter last = null; - while (aw != null) { - ++n; - size += aw.bv.length; - aw.visitEnd(); // in case user forgot to call visitEnd - aw.prev = last; - last = aw; - aw = aw.next; - } - out.putInt(size); - out.putShort(n); - aw = last; - while (aw != null) { - out.putByteArray(aw.bv.data, 0, aw.bv.length); - aw = aw.prev; - } + /** + * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its + * predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are + * put in the same order they have been visited. + * + * @param attributeNameIndex the constant pool index of the attribute name (one of + * "Runtime[In]Visible[Type]Annotations"). + * @param output where the attribute must be put. + */ + void putAnnotations(final int attributeNameIndex, final ByteVector output) { + int attributeLength = 2; // For num_annotations. + int numAnnotations = 0; + AnnotationWriter annotationWriter = this; + AnnotationWriter firstAnnotation = null; + while (annotationWriter != null) { + // In case the user forgot to call visitEnd(). + annotationWriter.visitEnd(); + attributeLength += annotationWriter.annotation.length; + numAnnotations++; + firstAnnotation = annotationWriter; + annotationWriter = annotationWriter.previousAnnotation; + } + output.putShort(attributeNameIndex); + output.putInt(attributeLength); + output.putShort(numAnnotations); + annotationWriter = firstAnnotation; + while (annotationWriter != null) { + output.putByteArray(annotationWriter.annotation.data, 0, annotationWriter.annotation.length); + annotationWriter = annotationWriter.nextAnnotation; } + } - /** - * Puts the given annotation lists into the given byte vector. - * - * @param panns - * an array of annotation writer lists. - * @param off - * index of the first annotation to be written. - * @param out - * where the annotations must be put. - */ - static void put(final AnnotationWriter[] panns, final int off, - final ByteVector out) { - int size = 1 + 2 * (panns.length - off); - for (int i = off; i < panns.length; ++i) { - size += panns[i] == null ? 0 : panns[i].getSize(); - } - out.putInt(size).putByte(panns.length - off); - for (int i = off; i < panns.length; ++i) { - AnnotationWriter aw = panns[i]; - AnnotationWriter last = null; - int n = 0; - while (aw != null) { - ++n; - aw.visitEnd(); // in case user forgot to call visitEnd - aw.prev = last; - last = aw; - aw = aw.next; - } - out.putShort(n); - aw = last; - while (aw != null) { - out.putByteArray(aw.bv.data, 0, aw.bv.length); - aw = aw.prev; - } - } + /** + * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the + * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the + * constant pool of the class. + * + * @param attributeName one of "Runtime[In]VisibleParameterAnnotations". + * @param annotationWriters an array of AnnotationWriter lists (designated by their last + * element). + * @param annotableParameterCount the number of elements in annotationWriters to take into account + * (elements [0..annotableParameterCount[ are taken into account). + * @return the size in bytes of a Runtime[In]VisibleParameterAnnotations attribute corresponding + * to the given sub-array of AnnotationWriter lists. This includes the size of the + * attribute_name_index and attribute_length fields. + */ + static int computeParameterAnnotationsSize( + final String attributeName, + final AnnotationWriter[] annotationWriters, + final int annotableParameterCount) { + // Note: attributeName is added to the constant pool by the call to computeAnnotationsSize + // below. This assumes that there is at least one non-null element in the annotationWriters + // sub-array (which is ensured by the lazy instantiation of this array in MethodWriter). + // The attribute_name_index, attribute_length and num_parameters fields use 7 bytes, and each + // element of the parameter_annotations array uses 2 bytes for its num_annotations field. + int attributeSize = 7 + 2 * annotableParameterCount; + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + attributeSize += + annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8; + } + return attributeSize; + } + + /** + * Puts a Runtime[In]VisibleParameterAnnotations attribute containing all the annotation lists + * from the given AnnotationWriter sub-array in the given ByteVector. + * + * @param attributeNameIndex constant pool index of the attribute name (one of + * Runtime[In]VisibleParameterAnnotations). + * @param annotationWriters an array of AnnotationWriter lists (designated by their last + * element). + * @param annotableParameterCount the number of elements in annotationWriters to put (elements + * [0..annotableParameterCount[ are put). + * @param output where the attribute must be put. + */ + static void putParameterAnnotations( + final int attributeNameIndex, + final AnnotationWriter[] annotationWriters, + final int annotableParameterCount, + final ByteVector output) { + // The num_parameters field uses 1 byte, and each element of the parameter_annotations array + // uses 2 bytes for its num_annotations field. + int attributeLength = 1 + 2 * annotableParameterCount; + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + attributeLength += + annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8; + } + output.putShort(attributeNameIndex); + output.putInt(attributeLength); + output.putByte(annotableParameterCount); + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + AnnotationWriter firstAnnotation = null; + int numAnnotations = 0; + while (annotationWriter != null) { + // In case user the forgot to call visitEnd(). + annotationWriter.visitEnd(); + numAnnotations++; + firstAnnotation = annotationWriter; + annotationWriter = annotationWriter.previousAnnotation; + } + output.putShort(numAnnotations); + annotationWriter = firstAnnotation; + while (annotationWriter != null) { + output.putByteArray( + annotationWriter.annotation.data, 0, annotationWriter.annotation.length); + annotationWriter = annotationWriter.nextAnnotation; + } } + } } diff --git a/src/jvm/clojure/asm/Attribute.java b/src/jvm/clojure/asm/Attribute.java index 1259c3c3e0..dcbe935cd7 100644 --- a/src/jvm/clojure/asm/Attribute.java +++ b/src/jvm/clojure/asm/Attribute.java @@ -1,255 +1,323 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A non standard class, field, method or code attribute. + * A non standard class, field, method or code attribute, as defined in the Java Virtual Machine + * Specification (JVMS). * + * @see JVMS + * 4.7 + * @see JVMS + * 4.7.3 * @author Eric Bruneton * @author Eugene Kuleshov */ public class Attribute { - /** - * The type of this attribute. - */ - public final String type; - - /** - * The raw value of this attribute, used only for unknown attributes. - */ - byte[] value; - - /** - * The next attribute in this attribute list. May be null. - */ - Attribute next; - - /** - * Constructs a new empty attribute. - * - * @param type - * the type of the attribute. - */ - protected Attribute(final String type) { - this.type = type; - } + /** The type of this attribute, also called its name in the JVMS. */ + public final String type; - /** - * Returns true if this type of attribute is unknown. The default - * implementation of this method always returns true. - * - * @return true if this type of attribute is unknown. - */ - public boolean isUnknown() { - return true; - } + /** + * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}). + * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not + * included. + */ + private byte[] content; - /** - * Returns true if this type of attribute is a code attribute. - * - * @return true if this type of attribute is a code attribute. - */ - public boolean isCodeAttribute() { - return false; - } + /** + * The next attribute in this attribute list (Attribute instances can be linked via this field to + * store a list of class, field, method or code attributes). May be null. + */ + Attribute nextAttribute; + + /** + * Constructs a new empty attribute. + * + * @param type the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns true if this type of attribute is unknown. This means that the attribute + * content can't be parsed to extract constant pool references, labels, etc. Instead, the + * attribute content is read as an opaque byte array, and written back as is. This can lead to + * invalid attributes, if the content actually contains constant pool references, labels, or other + * symbolic references that need to be updated when there are changes to the constant pool, the + * method bytecode, etc. The default implementation of this method always returns true. + * + * @return true if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns true if this type of attribute is a code attribute. + * + * @return true if this type of attribute is a code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or null if this attribute is not a + * code attribute that contains labels. + */ + protected Label[] getLabels() { + return new Label[0]; + } - /** - * Returns the labels corresponding to this attribute. - * - * @return the labels corresponding to this attribute, or null if - * this attribute is not a code attribute that contains labels. - */ - protected Label[] getLabels() { - return null; + /** + * Reads a {@link #type} attribute. This method must return a new {@link Attribute} object, + * of type {@link #type}, corresponding to the 'length' bytes starting at 'offset', in the given + * ClassReader. + * + * @param classReader the class that contains the attribute to be read. + * @param offset index of the first byte of the attribute's content in {@link ClassReader#b}. The + * 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to call the ClassReader methods requiring a + * 'charBuffer' parameter. + * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute + * in {@link ClassReader#b}, or -1 if the attribute to be read is not a code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param labels the labels of the method's code, or null if the attribute to be read is + * not a code attribute. + * @return a new {@link Attribute} object corresponding to the specified bytes. + */ + protected Attribute read( + final ClassReader classReader, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + Attribute attribute = new Attribute(type); + attribute.content = new byte[length]; + System.arraycopy(classReader.b, offset, attribute.content, 0, length); + return attribute; + } + + /** + * Returns the byte array form of the content of this attribute. The 6 header bytes + * (attribute_name_index and attribute_length) must not be added in the returned + * ByteVector. + * + * @param classWriter the class to which this attribute must be added. This parameter can be used + * to add the items that corresponds to this attribute to the constant pool of this class. + * @param code the bytecode of the method corresponding to this code attribute, or null + * if this attribute is not a code attribute. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to this code + * attribute, or 0 if this attribute is not a code attribute. Corresponds to the 'code_length' + * field of the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to this code attribute, or + * -1 if this attribute is not a code attribute. + * @param maxLocals the maximum number of local variables of the method corresponding to this code + * attribute, or -1 if this attribute is not a code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write( + final ClassWriter classWriter, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + return new ByteVector(content); + } + + /** + * Returns the number of attributes of the attribute list that begins with this attribute. + * + * @return the number of attributes of the attribute list that begins with this attribute. + */ + final int getAttributeCount() { + int count = 0; + Attribute attribute = this; + while (attribute != null) { + count += 1; + attribute = attribute.nextAttribute; } + return count; + } + + /** + * Returns the total size in bytes of all the attributes in the attribute list that begins with + * this attribute. This size includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. Also adds the attribute type names to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @return the size of all the attributes in this attribute list. This size includes the size of + * the attribute headers. + */ + final int computeAttributesSize(final SymbolTable symbolTable) { + final byte[] code = null; + final int codeLength = 0; + final int maxStack = -1; + final int maxLocals = -1; + return computeAttributesSize(symbolTable, code, codeLength, maxStack, maxLocals); + } - /** - * Reads a {@link #type type} attribute. This method must return a - * new {@link Attribute} object, of type {@link #type type}, - * corresponding to the len bytes starting at the given offset, in - * the given class reader. - * - * @param cr - * the class that contains the attribute to be read. - * @param off - * index of the first byte of the attribute's content in - * {@link ClassReader#b cr.b}. The 6 attribute header bytes, - * containing the type and the length of the attribute, are not - * taken into account here. - * @param len - * the length of the attribute's content. - * @param buf - * buffer to be used to call {@link ClassReader#readUTF8 - * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass} - * or {@link ClassReader#readConst readConst}. - * @param codeOff - * index of the first byte of code's attribute content in - * {@link ClassReader#b cr.b}, or -1 if the attribute to be read - * is not a code attribute. The 6 attribute header bytes, - * containing the type and the length of the attribute, are not - * taken into account here. - * @param labels - * the labels of the method's code, or null if the - * attribute to be read is not a code attribute. - * @return a new {@link Attribute} object corresponding to the given - * bytes. - */ - protected Attribute read(final ClassReader cr, final int off, - final int len, final char[] buf, final int codeOff, - final Label[] labels) { - Attribute attr = new Attribute(type); - attr.value = new byte[len]; - System.arraycopy(cr.b, off, attr.value, 0, len); - return attr; + /** + * Returns the total size in bytes of all the attributes in the attribute list that begins with + * this attribute. This size includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. Also adds the attribute type names to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param code the bytecode of the method corresponding to these code attributes, or null + * if they are not code attributes. Corresponds to the 'code' field of the Code attribute. + * @param codeLength the length of the bytecode of the method corresponding to these code + * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of + * the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to these code attributes, or + * -1 if they are not code attributes. + * @param maxLocals the maximum number of local variables of the method corresponding to these + * code attributes, or -1 if they are not code attribute. + * @return the size of all the attributes in this attribute list. This size includes the size of + * the attribute headers. + */ + final int computeAttributesSize( + final SymbolTable symbolTable, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + final ClassWriter classWriter = symbolTable.classWriter; + int size = 0; + Attribute attribute = this; + while (attribute != null) { + symbolTable.addConstantUtf8(attribute.type); + size += 6 + attribute.write(classWriter, code, codeLength, maxStack, maxLocals).length; + attribute = attribute.nextAttribute; } + return size; + } - /** - * Returns the byte array form of this attribute. - * - * @param cw - * the class to which this attribute must be added. This - * parameter can be used to add to the constant pool of this - * class the items that corresponds to this attribute. - * @param code - * the bytecode of the method corresponding to this code - * attribute, or null if this attribute is not a code - * attributes. - * @param len - * the length of the bytecode of the method corresponding to this - * code attribute, or null if this attribute is not a - * code attribute. - * @param maxStack - * the maximum stack size of the method corresponding to this - * code attribute, or -1 if this attribute is not a code - * attribute. - * @param maxLocals - * the maximum number of local variables of the method - * corresponding to this code attribute, or -1 if this attribute - * is not a code attribute. - * @return the byte array form of this attribute. - */ - protected ByteVector write(final ClassWriter cw, final byte[] code, - final int len, final int maxStack, final int maxLocals) { - ByteVector v = new ByteVector(); - v.data = value; - v.length = value.length; - return v; + /** + * Puts all the attributes of the attribute list that begins with this attribute, in the given + * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per + * attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param output where the attributes must be written. + */ + final void putAttributes(final SymbolTable symbolTable, final ByteVector output) { + final byte[] code = null; + final int codeLength = 0; + final int maxStack = -1; + final int maxLocals = -1; + putAttributes(symbolTable, code, codeLength, maxStack, maxLocals, output); + } + + /** + * Puts all the attributes of the attribute list that begins with this attribute, in the given + * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per + * attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param code the bytecode of the method corresponding to these code attributes, or null + * if they are not code attributes. Corresponds to the 'code' field of the Code attribute. + * @param codeLength the length of the bytecode of the method corresponding to these code + * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of + * the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to these code attributes, or + * -1 if they are not code attributes. + * @param maxLocals the maximum number of local variables of the method corresponding to these + * code attributes, or -1 if they are not code attribute. + * @param output where the attributes must be written. + */ + final void putAttributes( + final SymbolTable symbolTable, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals, + final ByteVector output) { + final ClassWriter classWriter = symbolTable.classWriter; + Attribute attribute = this; + while (attribute != null) { + ByteVector attributeContent = + attribute.write(classWriter, code, codeLength, maxStack, maxLocals); + // Put attribute_name_index and attribute_length. + output.putShort(symbolTable.addConstantUtf8(attribute.type)).putInt(attributeContent.length); + output.putByteArray(attributeContent.data, 0, attributeContent.length); + attribute = attribute.nextAttribute; } + } + + /** A set of attribute prototypes (attributes with the same type are considered equal). */ + static final class Set { - /** - * Returns the length of the attribute list that begins with this attribute. - * - * @return the length of the attribute list that begins with this attribute. - */ - final int getCount() { - int count = 0; - Attribute attr = this; - while (attr != null) { - count += 1; - attr = attr.next; + private static final int SIZE_INCREMENT = 6; + + private int size; + private Attribute[] data = new Attribute[SIZE_INCREMENT]; + + void addAttributes(final Attribute attributeList) { + Attribute attribute = attributeList; + while (attribute != null) { + if (!contains(attribute)) { + add(attribute); } - return count; + attribute = attribute.nextAttribute; + } } - /** - * Returns the size of all the attributes in this attribute list. - * - * @param cw - * the class writer to be used to convert the attributes into - * byte arrays, with the {@link #write write} method. - * @param code - * the bytecode of the method corresponding to these code - * attributes, or null if these attributes are not code - * attributes. - * @param len - * the length of the bytecode of the method corresponding to - * these code attributes, or null if these attributes - * are not code attributes. - * @param maxStack - * the maximum stack size of the method corresponding to these - * code attributes, or -1 if these attributes are not code - * attributes. - * @param maxLocals - * the maximum number of local variables of the method - * corresponding to these code attributes, or -1 if these - * attributes are not code attributes. - * @return the size of all the attributes in this attribute list. This size - * includes the size of the attribute headers. - */ - final int getSize(final ClassWriter cw, final byte[] code, final int len, - final int maxStack, final int maxLocals) { - Attribute attr = this; - int size = 0; - while (attr != null) { - cw.newUTF8(attr.type); - size += attr.write(cw, code, len, maxStack, maxLocals).length + 6; - attr = attr.next; - } - return size; + Attribute[] toArray() { + Attribute[] result = new Attribute[size]; + System.arraycopy(data, 0, result, 0, size); + return result; } - /** - * Writes all the attributes of this attribute list in the given byte - * vector. - * - * @param cw - * the class writer to be used to convert the attributes into - * byte arrays, with the {@link #write write} method. - * @param code - * the bytecode of the method corresponding to these code - * attributes, or null if these attributes are not code - * attributes. - * @param len - * the length of the bytecode of the method corresponding to - * these code attributes, or null if these attributes - * are not code attributes. - * @param maxStack - * the maximum stack size of the method corresponding to these - * code attributes, or -1 if these attributes are not code - * attributes. - * @param maxLocals - * the maximum number of local variables of the method - * corresponding to these code attributes, or -1 if these - * attributes are not code attributes. - * @param out - * where the attributes must be written. - */ - final void put(final ClassWriter cw, final byte[] code, final int len, - final int maxStack, final int maxLocals, final ByteVector out) { - Attribute attr = this; - while (attr != null) { - ByteVector b = attr.write(cw, code, len, maxStack, maxLocals); - out.putShort(cw.newUTF8(attr.type)).putInt(b.length); - out.putByteArray(b.data, 0, b.length); - attr = attr.next; + private boolean contains(final Attribute attribute) { + for (int i = 0; i < size; ++i) { + if (data[i].type.equals(attribute.type)) { + return true; } + } + return false; + } + + private void add(final Attribute attribute) { + if (size >= data.length) { + Attribute[] newData = new Attribute[data.length + SIZE_INCREMENT]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + data[size++] = attribute; } + } } diff --git a/src/jvm/clojure/asm/ByteVector.java b/src/jvm/clojure/asm/ByteVector.java index 0e4f861905..066dec8a62 100644 --- a/src/jvm/clojure/asm/ByteVector.java +++ b/src/jvm/clojure/asm/ByteVector.java @@ -1,306 +1,360 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A dynamically extensible vector of bytes. This class is roughly equivalent to - * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient. + * A dynamically extensible vector of bytes. This class is roughly equivalent to a DataOutputStream + * on top of a ByteArrayOutputStream, but is more efficient. * * @author Eric Bruneton */ public class ByteVector { - /** - * The content of this vector. - */ - byte[] data; + /** The content of this vector. Only the first {@link #length} bytes contain real data. */ + byte[] data; + + /** The actual number of bytes in this vector. */ + int length; - /** - * Actual number of bytes in this vector. - */ - int length; + /** Constructs a new {@link ByteVector} with a default initial capacity. */ + public ByteVector() { + data = new byte[64]; + } - /** - * Constructs a new {@link ByteVector ByteVector} with a default initial - * size. - */ - public ByteVector() { - data = new byte[64]; + /** + * Constructs a new {@link ByteVector} with the given initial capacity. + * + * @param initialCapacity the initial capacity of the byte vector to be constructed. + */ + public ByteVector(final int initialCapacity) { + data = new byte[initialCapacity]; + } + + /** + * Constructs a new {@link ByteVector} from the given initial data. + * + * @param data the initial data of the new byte vector. + */ + ByteVector(final byte[] data) { + this.data = data; + this.length = data.length; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param byteValue a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int byteValue) { + int currentLength = length; + if (currentLength + 1 > data.length) { + enlarge(1); } + data[currentLength++] = (byte) byteValue; + length = currentLength; + return this; + } - /** - * Constructs a new {@link ByteVector ByteVector} with the given initial - * size. - * - * @param initialSize - * the initial size of the byte vector to be constructed. - */ - public ByteVector(final int initialSize) { - data = new byte[initialSize]; + /** + * Puts two bytes into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param byteValue1 a byte. + * @param byteValue2 another byte. + * @return this byte vector. + */ + final ByteVector put11(final int byteValue1, final int byteValue2) { + int currentLength = length; + if (currentLength + 2 > data.length) { + enlarge(2); } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue1; + currentData[currentLength++] = (byte) byteValue2; + length = currentLength; + return this; + } - /** - * Puts a byte into this byte vector. The byte vector is automatically - * enlarged if necessary. - * - * @param b - * a byte. - * @return this byte vector. - */ - public ByteVector putByte(final int b) { - int length = this.length; - if (length + 1 > data.length) { - enlarge(1); - } - data[length++] = (byte) b; - this.length = length; - return this; + /** + * Puts a short into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param shortValue a short. + * @return this byte vector. + */ + public ByteVector putShort(final int shortValue) { + int currentLength = length; + if (currentLength + 2 > data.length) { + enlarge(2); } + byte[] currentData = data; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } - /** - * Puts two bytes into this byte vector. The byte vector is automatically - * enlarged if necessary. - * - * @param b1 - * a byte. - * @param b2 - * another byte. - * @return this byte vector. - */ - ByteVector put11(final int b1, final int b2) { - int length = this.length; - if (length + 2 > data.length) { - enlarge(2); - } - byte[] data = this.data; - data[length++] = (byte) b1; - data[length++] = (byte) b2; - this.length = length; - return this; + /** + * Puts a byte and a short into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteValue a byte. + * @param shortValue a short. + * @return this byte vector. + */ + final ByteVector put12(final int byteValue, final int shortValue) { + int currentLength = length; + if (currentLength + 3 > data.length) { + enlarge(3); } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } - /** - * Puts a short into this byte vector. The byte vector is automatically - * enlarged if necessary. - * - * @param s - * a short. - * @return this byte vector. - */ - public ByteVector putShort(final int s) { - int length = this.length; - if (length + 2 > data.length) { - enlarge(2); - } - byte[] data = this.data; - data[length++] = (byte) (s >>> 8); - data[length++] = (byte) s; - this.length = length; - return this; + /** + * Puts two bytes and a short into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteValue1 a byte. + * @param byteValue2 another byte. + * @param shortValue a short. + * @return this byte vector. + */ + final ByteVector put112(final int byteValue1, final int byteValue2, final int shortValue) { + int currentLength = length; + if (currentLength + 4 > data.length) { + enlarge(4); } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue1; + currentData[currentLength++] = (byte) byteValue2; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } - /** - * Puts a byte and a short into this byte vector. The byte vector is - * automatically enlarged if necessary. - * - * @param b - * a byte. - * @param s - * a short. - * @return this byte vector. - */ - ByteVector put12(final int b, final int s) { - int length = this.length; - if (length + 3 > data.length) { - enlarge(3); - } - byte[] data = this.data; - data[length++] = (byte) b; - data[length++] = (byte) (s >>> 8); - data[length++] = (byte) s; - this.length = length; - return this; + /** + * Puts an int into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param intValue an int. + * @return this byte vector. + */ + public ByteVector putInt(final int intValue) { + int currentLength = length; + if (currentLength + 4 > data.length) { + enlarge(4); } + byte[] currentData = data; + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + length = currentLength; + return this; + } - /** - * Puts an int into this byte vector. The byte vector is automatically - * enlarged if necessary. - * - * @param i - * an int. - * @return this byte vector. - */ - public ByteVector putInt(final int i) { - int length = this.length; - if (length + 4 > data.length) { - enlarge(4); - } - byte[] data = this.data; - data[length++] = (byte) (i >>> 24); - data[length++] = (byte) (i >>> 16); - data[length++] = (byte) (i >>> 8); - data[length++] = (byte) i; - this.length = length; - return this; + /** + * Puts one byte and two shorts into this byte vector. The byte vector is automatically enlarged + * if necessary. + * + * @param byteValue a byte. + * @param shortValue1 a short. + * @param shortValue2 another short. + * @return this byte vector. + */ + final ByteVector put122(final int byteValue, final int shortValue1, final int shortValue2) { + int currentLength = length; + if (currentLength + 5 > data.length) { + enlarge(5); } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue; + currentData[currentLength++] = (byte) (shortValue1 >>> 8); + currentData[currentLength++] = (byte) shortValue1; + currentData[currentLength++] = (byte) (shortValue2 >>> 8); + currentData[currentLength++] = (byte) shortValue2; + length = currentLength; + return this; + } - /** - * Puts a long into this byte vector. The byte vector is automatically - * enlarged if necessary. - * - * @param l - * a long. - * @return this byte vector. - */ - public ByteVector putLong(final long l) { - int length = this.length; - if (length + 8 > data.length) { - enlarge(8); - } - byte[] data = this.data; - int i = (int) (l >>> 32); - data[length++] = (byte) (i >>> 24); - data[length++] = (byte) (i >>> 16); - data[length++] = (byte) (i >>> 8); - data[length++] = (byte) i; - i = (int) l; - data[length++] = (byte) (i >>> 24); - data[length++] = (byte) (i >>> 16); - data[length++] = (byte) (i >>> 8); - data[length++] = (byte) i; - this.length = length; - return this; + /** + * Puts a long into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param longValue a long. + * @return this byte vector. + */ + public ByteVector putLong(final long longValue) { + int currentLength = length; + if (currentLength + 8 > data.length) { + enlarge(8); } + byte[] currentData = data; + int intValue = (int) (longValue >>> 32); + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + intValue = (int) longValue; + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + length = currentLength; + return this; + } - /** - * Puts an UTF8 string into this byte vector. The byte vector is - * automatically enlarged if necessary. - * - * @param s - * a String. - * @return this byte vector. - */ - public ByteVector putUTF8(final String s) { - int charLength = s.length(); - int len = length; - if (len + 2 + charLength > data.length) { - enlarge(2 + charLength); - } - byte[] data = this.data; - // optimistic algorithm: instead of computing the byte length and then - // serializing the string (which requires two loops), we assume the byte - // length is equal to char length (which is the most frequent case), and - // we start serializing the string right away. During the serialization, - // if we find that this assumption is wrong, we continue with the - // general method. - data[len++] = (byte) (charLength >>> 8); - data[len++] = (byte) charLength; - for (int i = 0; i < charLength; ++i) { - char c = s.charAt(i); - if (c >= '\001' && c <= '\177') { - data[len++] = (byte) c; - } else { - int byteLength = i; - for (int j = i; j < charLength; ++j) { - c = s.charAt(j); - if (c >= '\001' && c <= '\177') { - byteLength++; - } else if (c > '\u07FF') { - byteLength += 3; - } else { - byteLength += 2; - } - } - data[length] = (byte) (byteLength >>> 8); - data[length + 1] = (byte) byteLength; - if (length + 2 + byteLength > data.length) { - length = len; - enlarge(2 + byteLength); - data = this.data; - } - for (int j = i; j < charLength; ++j) { - c = s.charAt(j); - if (c >= '\001' && c <= '\177') { - data[len++] = (byte) c; - } else if (c > '\u07FF') { - data[len++] = (byte) (0xE0 | c >> 12 & 0xF); - data[len++] = (byte) (0x80 | c >> 6 & 0x3F); - data[len++] = (byte) (0x80 | c & 0x3F); - } else { - data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); - data[len++] = (byte) (0x80 | c & 0x3F); - } - } - break; - } - } - length = len; - return this; + /** + * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param stringValue a String whose UTF8 encoded length must be less than 65536. + * @return this byte vector. + */ + public ByteVector putUTF8(final String stringValue) { + int charLength = stringValue.length(); + if (charLength > 65535) { + throw new IllegalArgumentException(); + } + int currentLength = length; + if (currentLength + 2 + charLength > data.length) { + enlarge(2 + charLength); } + byte[] currentData = data; + // Optimistic algorithm: instead of computing the byte length and then serializing the string + // (which requires two loops), we assume the byte length is equal to char length (which is the + // most frequent case), and we start serializing the string right away. During the + // serialization, if we find that this assumption is wrong, we continue with the general method. + currentData[currentLength++] = (byte) (charLength >>> 8); + currentData[currentLength++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= '\u0001' && charValue <= '\u007F') { + currentData[currentLength++] = (byte) charValue; + } else { + length = currentLength; + return encodeUTF8(stringValue, i, 65535); + } + } + length = currentLength; + return this; + } - /** - * Puts an array of bytes into this byte vector. The byte vector is - * automatically enlarged if necessary. - * - * @param b - * an array of bytes. May be null to put len - * null bytes into this byte vector. - * @param off - * index of the fist byte of b that must be copied. - * @param len - * number of bytes of b that must be copied. - * @return this byte vector. - */ - public ByteVector putByteArray(final byte[] b, final int off, final int len) { - if (length + len > data.length) { - enlarge(len); - } - if (b != null) { - System.arraycopy(b, off, data, length, len); - } - length += len; - return this; + /** + * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if + * necessary. The string length is encoded in two bytes before the encoded characters, if there is + * space for that (i.e. if this.length - offset - 2 >= 0). + * + * @param stringValue the String to encode. + * @param offset the index of the first character to encode. The previous characters are supposed + * to have already been encoded, using only one byte per character. + * @param maxByteLength the maximum byte length of the encoded string, including the already + * encoded characters. + * @return this byte vector. + */ + final ByteVector encodeUTF8(final String stringValue, final int offset, final int maxByteLength) { + int charLength = stringValue.length(); + int byteLength = offset; + for (int i = offset; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= '\u0001' && charValue <= '\u007F') { + byteLength++; + } else if (charValue <= '\u07FF') { + byteLength += 2; + } else { + byteLength += 3; + } + } + if (byteLength > maxByteLength) { + throw new IllegalArgumentException(); + } + // Compute where 'byteLength' must be stored in 'data', and store it at this location. + int byteLengthOffset = length - offset - 2; + if (byteLengthOffset >= 0) { + data[byteLengthOffset] = (byte) (byteLength >>> 8); + data[byteLengthOffset + 1] = (byte) byteLength; + } + if (length + byteLength - offset > data.length) { + enlarge(byteLength - offset); } + int currentLength = length; + for (int i = offset; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= '\u0001' && charValue <= '\u007F') { + data[currentLength++] = (byte) charValue; + } else if (charValue <= '\u07FF') { + data[currentLength++] = (byte) (0xC0 | charValue >> 6 & 0x1F); + data[currentLength++] = (byte) (0x80 | charValue & 0x3F); + } else { + data[currentLength++] = (byte) (0xE0 | charValue >> 12 & 0xF); + data[currentLength++] = (byte) (0x80 | charValue >> 6 & 0x3F); + data[currentLength++] = (byte) (0x80 | charValue & 0x3F); + } + } + length = currentLength; + return this; + } - /** - * Enlarge this byte vector so that it can receive n more bytes. - * - * @param size - * number of additional bytes that this byte vector should be - * able to receive. - */ - private void enlarge(final int size) { - int length1 = 2 * data.length; - int length2 = length + size; - byte[] newData = new byte[length1 > length2 ? length1 : length2]; - System.arraycopy(data, 0, newData, 0, length); - data = newData; + /** + * Puts an array of bytes into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteArrayValue an array of bytes. May be null to put byteLength null + * bytes into this byte vector. + * @param byteOffset index of the first byte of byteArrayValue that must be copied. + * @param byteLength number of bytes of byteArrayValue that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray( + final byte[] byteArrayValue, final int byteOffset, final int byteLength) { + if (length + byteLength > data.length) { + enlarge(byteLength); + } + if (byteArrayValue != null) { + System.arraycopy(byteArrayValue, byteOffset, data, length, byteLength); } + length += byteLength; + return this; + } + + /** + * Enlarges this byte vector so that it can receive 'size' more bytes. + * + * @param size number of additional bytes that this byte vector should be able to receive. + */ + private void enlarge(final int size) { + int doubleCapacity = 2 * data.length; + int minimalCapacity = length + size; + byte[] newData = new byte[doubleCapacity > minimalCapacity ? doubleCapacity : minimalCapacity]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } } diff --git a/src/jvm/clojure/asm/ClassReader.java b/src/jvm/clojure/asm/ClassReader.java index 9265a37aa9..6502e86d3a 100644 --- a/src/jvm/clojure/asm/ClassReader.java +++ b/src/jvm/clojure/asm/ClassReader.java @@ -1,2202 +1,3567 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; /** - * A Java class parser to make a {@link ClassVisitor} visit an existing class. - * This class parses a byte array conforming to the Java class file format and - * calls the appropriate visit methods of a given class visitor for each field, - * method and bytecode instruction encountered. + * A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java + * Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the + * appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode + * instruction encountered. * + * @see JVMS 4 * @author Eric Bruneton * @author Eugene Kuleshov */ public class ClassReader { - /** - * True to enable signatures support. - */ - static final boolean SIGNATURES = true; - - /** - * True to enable annotations support. - */ - static final boolean ANNOTATIONS = true; - - /** - * True to enable stack map frames support. - */ - static final boolean FRAMES = true; - - /** - * True to enable bytecode writing support. - */ - static final boolean WRITER = true; - - /** - * True to enable JSR_W and GOTO_W support. - */ - static final boolean RESIZE = true; - - /** - * Flag to skip method code. If this class is set CODE - * attribute won't be visited. This can be used, for example, to retrieve - * annotations for methods and method parameters. - */ - public static final int SKIP_CODE = 1; - - /** - * Flag to skip the debug information in the class. If this flag is set the - * debug information of the class is not visited, i.e. the - * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and - * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be - * called. - */ - public static final int SKIP_DEBUG = 2; - - /** - * Flag to skip the stack map frames in the class. If this flag is set the - * stack map frames of the class is not visited, i.e. the - * {@link MethodVisitor#visitFrame visitFrame} method will not be called. - * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is - * used: it avoids visiting frames that will be ignored and recomputed from - * scratch in the class writer. - */ - public static final int SKIP_FRAMES = 4; - - /** - * Flag to expand the stack map frames. By default stack map frames are - * visited in their original format (i.e. "expanded" for classes whose - * version is less than V1_6, and "compressed" for the other classes). If - * this flag is set, stack map frames are always visited in expanded format - * (this option adds a decompression/recompression step in ClassReader and - * ClassWriter which degrades performances quite a lot). - */ - public static final int EXPAND_FRAMES = 8; - - /** - * The class to be parsed. The content of this array must not be - * modified. This field is intended for {@link Attribute} sub classes, and - * is normally not needed by class generators or adapters. - */ - public final byte[] b; - - /** - * The start index of each constant pool item in {@link #b b}, plus one. The - * one byte offset skips the constant pool item tag that indicates its type. - */ - private final int[] items; - - /** - * The String objects corresponding to the CONSTANT_Utf8 items. This cache - * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item, - * which GREATLY improves performances (by a factor 2 to 3). This caching - * strategy could be extended to all constant pool items, but its benefit - * would not be so great for these items (because they are much less - * expensive to parse than CONSTANT_Utf8 items). - */ - private final String[] strings; - - /** - * Maximum length of the strings contained in the constant pool of the - * class. - */ - private final int maxStringLength; - - /** - * Start index of the class header information (access, name...) in - * {@link #b b}. - */ - public final int header; - - // ------------------------------------------------------------------------ - // Constructors - // ------------------------------------------------------------------------ - - /** - * Constructs a new {@link ClassReader} object. - * - * @param b - * the bytecode of the class to be read. - */ - public ClassReader(final byte[] b) { - this(b, 0, b.length); - } - - /** - * Constructs a new {@link ClassReader} object. - * - * @param b - * the bytecode of the class to be read. - * @param off - * the start offset of the class data. - * @param len - * the length of the class data. - */ - public ClassReader(final byte[] b, final int off, final int len) { - this.b = b; - // checks the class version - if (readShort(off + 6) > Opcodes.V1_7) { - throw new IllegalArgumentException(); - } - // parses the constant pool - items = new int[readUnsignedShort(off + 8)]; - int n = items.length; - strings = new String[n]; - int max = 0; - int index = off + 10; - for (int i = 1; i < n; ++i) { - items[i] = index + 1; - int size; - switch (b[index]) { - case ClassWriter.FIELD: - case ClassWriter.METH: - case ClassWriter.IMETH: - case ClassWriter.INT: - case ClassWriter.FLOAT: - case ClassWriter.NAME_TYPE: - case ClassWriter.INDY: - size = 5; - break; - case ClassWriter.LONG: - case ClassWriter.DOUBLE: - size = 9; - ++i; - break; - case ClassWriter.UTF8: - size = 3 + readUnsignedShort(index + 1); - if (size > max) { - max = size; - } - break; - case ClassWriter.HANDLE: - size = 4; - break; - // case ClassWriter.CLASS: - // case ClassWriter.STR: - // case ClassWriter.MTYPE - default: - size = 3; - break; - } - index += size; - } - maxStringLength = max; - // the class header information starts just after the constant pool - header = index; - } - - /** - * Returns the class's access flags (see {@link Opcodes}). This value may - * not reflect Deprecated and Synthetic flags when bytecode is before 1.5 - * and those flags are represented by attributes. - * - * @return the class access flags - * - * @see ClassVisitor#visit(int, int, String, String, String, String[]) - */ - public int getAccess() { - return readUnsignedShort(header); - } - - /** - * Returns the internal name of the class (see - * {@link Type#getInternalName() getInternalName}). - * - * @return the internal class name - * - * @see ClassVisitor#visit(int, int, String, String, String, String[]) - */ - public String getClassName() { - return readClass(header + 2, new char[maxStringLength]); - } - - /** - * Returns the internal of name of the super class (see - * {@link Type#getInternalName() getInternalName}). For interfaces, the - * super class is {@link Object}. - * - * @return the internal name of super class, or null for - * {@link Object} class. - * - * @see ClassVisitor#visit(int, int, String, String, String, String[]) - */ - public String getSuperName() { - return readClass(header + 4, new char[maxStringLength]); - } - - /** - * Returns the internal names of the class's interfaces (see - * {@link Type#getInternalName() getInternalName}). - * - * @return the array of internal names for all implemented interfaces or - * null. - * - * @see ClassVisitor#visit(int, int, String, String, String, String[]) - */ - public String[] getInterfaces() { - int index = header + 6; - int n = readUnsignedShort(index); - String[] interfaces = new String[n]; - if (n > 0) { - char[] buf = new char[maxStringLength]; - for (int i = 0; i < n; ++i) { - index += 2; - interfaces[i] = readClass(index, buf); - } + /** + * A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed + * nor visited. + */ + public static final int SKIP_CODE = 1; + + /** + * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable + * and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor + * visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and + * {@link MethodVisitor#visitLineNumber} are not called). + */ + public static final int SKIP_DEBUG = 2; + + /** + * A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes + * are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag + * is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames + * that will be ignored and recomputed from scratch. + */ + public static final int SKIP_FRAMES = 4; + + /** + * A flag to expand the stack map frames. By default stack map frames are visited in their + * original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed" + * for the other classes). If this flag is set, stack map frames are always visited in expanded + * format (this option adds a decompression/compression step in ClassReader and ClassWriter which + * degrades performance quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode + * instructions. When resolving a forward jump it may happen that the signed 2 bytes offset + * reserved for it is not sufficient to store the bytecode offset. In this case the jump + * instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes + * offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing + * such instructions, in order to replace them with standard instructions. In addition, when this + * flag is used, goto_w and jsr_w are not converted into goto and jsr, to make sure that + * infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a + * goto_w in ClassWriter cannot occur. + */ + static final int EXPAND_ASM_INSNS = 256; + + /** The size of the temporary byte array used to read class input streams chunk by chunk. */ + private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096; + + /** + * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array + * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally + * not needed by class visitors. + * + *

NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not + * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct + * ClassFile element offsets within this byte array. + */ + public final byte[] b; + + /** + * The offset in bytes, in {@link #b}, of each cp_info entry of the ClassFile's constant_pool + * array, plus one. In other words, the offset of constant pool entry i is given by + * cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - 1]. + */ + private final int[] cpInfoOffsets; + + /** + * The value of each cp_info entry of the ClassFile's constant_pool array, for Constant_Utf8 + * and Constant_Dynamic constants only. The value of constant pool entry i is given by + * cpInfoValues[i]. This cache avoids multiple parsing of those constant pool items. + */ + private final Object[] cpInfoValues; + + /** + * The start offsets in {@link #b} of each element of the bootstrap_methods array (in the + * BootstrapMethods attribute). + * + * @see JVMS + * 4.7.23 + */ + private final int[] bootstrapMethodOffsets; + + /** + * A conservative estimate of the maximum length of the strings contained in the constant pool of + * the class. + */ + private final int maxStringLength; + + /** The offset in bytes, in {@link #b}, of the ClassFile's access_flags field. */ + public final int header; + + // ----------------------------------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFile the JVMS ClassFile structure to be read. + */ + public ClassReader(final byte[] classFile) { + this(classFile, 0, classFile.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param classFileLength the length in bytes of the ClassFile to be read. + */ + public ClassReader( + final byte[] classFileBuffer, final int classFileOffset, final int classFileLength) { + this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true); + } + + /** + * Constructs a new {@link ClassReader} object. This internal constructor must not be exposed + * as a public API. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param checkClassVersion whether to check the class version or not. + */ + ClassReader( + final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) { + this.b = classFileBuffer; + // Check the class' major_version. This field is after the magic and minor_version fields, which + // use 4 and 2 bytes respectively. + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V11) { + throw new IllegalArgumentException( + "Unsupported class file major version " + readShort(classFileOffset + 6)); + } + // Create the constant pool arrays. The constant_pool_count field is after the magic, + // minor_version and major_version fields, which use 4, 2 and 2 bytes respectively. + int constantPoolCount = readUnsignedShort(classFileOffset + 8); + cpInfoOffsets = new int[constantPoolCount]; + cpInfoValues = new Object[constantPoolCount]; + // Compute the offset of each constant pool entry, as well as a conservative estimate of the + // maximum length of the constant pool strings. The first constant pool entry is after the + // magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2 + // bytes respectively. + int currentCpInfoIndex = 1; + int currentCpInfoOffset = classFileOffset + 10; + int currentMaxStringLength = 0; + // The offset of the other entries depend on the total size of all the previous entries. + while (currentCpInfoIndex < constantPoolCount) { + cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1; + int cpInfoSize; + switch (classFileBuffer[currentCpInfoOffset]) { + case Symbol.CONSTANT_FIELDREF_TAG: + case Symbol.CONSTANT_METHODREF_TAG: + case Symbol.CONSTANT_INTERFACE_METHODREF_TAG: + case Symbol.CONSTANT_INTEGER_TAG: + case Symbol.CONSTANT_FLOAT_TAG: + case Symbol.CONSTANT_NAME_AND_TYPE_TAG: + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + case Symbol.CONSTANT_DYNAMIC_TAG: + cpInfoSize = 5; + break; + case Symbol.CONSTANT_LONG_TAG: + case Symbol.CONSTANT_DOUBLE_TAG: + cpInfoSize = 9; + currentCpInfoIndex++; + break; + case Symbol.CONSTANT_UTF8_TAG: + cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1); + if (cpInfoSize > currentMaxStringLength) { + // The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate + // of the length in characters of the corresponding string, and is much cheaper to + // compute than this exact length. + currentMaxStringLength = cpInfoSize; + } + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + cpInfoSize = 4; + break; + case Symbol.CONSTANT_CLASS_TAG: + case Symbol.CONSTANT_STRING_TAG: + case Symbol.CONSTANT_METHOD_TYPE_TAG: + case Symbol.CONSTANT_PACKAGE_TAG: + case Symbol.CONSTANT_MODULE_TAG: + cpInfoSize = 3; + break; + default: + throw new IllegalArgumentException(); + } + currentCpInfoOffset += cpInfoSize; + } + this.maxStringLength = currentMaxStringLength; + // The Classfile's access_flags field is just after the last constant pool entry. + this.header = currentCpInfoOffset; + + // Read the BootstrapMethods attribute, if any (only get the offset of each method). + int currentAttributeOffset = getFirstAttributeOffset(); + int[] currentBootstrapMethodOffsets = null; + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, new char[maxStringLength]); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // Read the num_bootstrap_methods field and create an array of this size. + currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)]; + // Compute and store the offset of each 'bootstrap_methods' array field entry. + int currentBootstrapMethodOffset = currentAttributeOffset + 2; + for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) { + currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset; + // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each), + // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2). + currentBootstrapMethodOffset += + 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2; } - return interfaces; - } - - /** - * Copies the constant pool data into the given {@link ClassWriter}. Should - * be called before the {@link #accept(ClassVisitor,int)} method. - * - * @param classWriter - * the {@link ClassWriter} to copy constant pool into. - */ - void copyPool(final ClassWriter classWriter) { - char[] buf = new char[maxStringLength]; - int ll = items.length; - Item[] items2 = new Item[ll]; - for (int i = 1; i < ll; i++) { - int index = items[i]; - int tag = b[index - 1]; - Item item = new Item(i); - int nameType; - switch (tag) { - case ClassWriter.FIELD: - case ClassWriter.METH: - case ClassWriter.IMETH: - nameType = items[readUnsignedShort(index + 2)]; - item.set(tag, readClass(index, buf), readUTF8(nameType, buf), - readUTF8(nameType + 2, buf)); - break; - case ClassWriter.INT: - item.set(readInt(index)); - break; - case ClassWriter.FLOAT: - item.set(Float.intBitsToFloat(readInt(index))); - break; - case ClassWriter.NAME_TYPE: - item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf), - null); - break; - case ClassWriter.LONG: - item.set(readLong(index)); - ++i; - break; - case ClassWriter.DOUBLE: - item.set(Double.longBitsToDouble(readLong(index))); - ++i; - break; - case ClassWriter.UTF8: { - String s = strings[i]; - if (s == null) { - index = items[i]; - s = strings[i] = readUTF(index + 2, - readUnsignedShort(index), buf); - } - item.set(tag, s, null, null); - break; - } - case ClassWriter.HANDLE: { - int fieldOrMethodRef = items[readUnsignedShort(index + 1)]; - nameType = items[readUnsignedShort(fieldOrMethodRef + 2)]; - item.set(ClassWriter.HANDLE_BASE + readByte(index), - readClass(fieldOrMethodRef, buf), - readUTF8(nameType, buf), readUTF8(nameType + 2, buf)); - break; - } - case ClassWriter.INDY: - if (classWriter.bootstrapMethods == null) { - copyBootstrapMethods(classWriter, items2, buf); - } - nameType = items[readUnsignedShort(index + 2)]; - item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf), - readUnsignedShort(index)); - break; - // case ClassWriter.STR: - // case ClassWriter.CLASS: - // case ClassWriter.MTYPE - default: - item.set(tag, readUTF8(index, buf), null, null); - break; - } + } + currentAttributeOffset += attributeLength; + } + this.bootstrapMethodOffsets = currentBootstrapMethodOffsets; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param inputStream an input stream of the JVMS ClassFile structure to be read. This input + * stream must contain nothing more than the ClassFile structure itself. It is read from its + * current position to its end. + * @throws IOException if a problem occurs during reading. + */ + public ClassReader(final InputStream inputStream) throws IOException { + this(readStream(inputStream, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param className the fully qualified name of the class to be read. The ClassFile structure is + * retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}. + * @throws IOException if an exception occurs during reading. + */ + public ClassReader(final String className) throws IOException { + this( + readStream( + ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true)); + } + + /** + * Reads the given input stream and returns its content as a byte array. + * + * @param inputStream an input stream. + * @param close true to close the input stream after reading. + * @return the content of the given input stream. + * @throws IOException if a problem occurs during reading. + */ + private static byte[] readStream(final InputStream inputStream, final boolean close) + throws IOException { + if (inputStream == null) { + throw new IOException("Class not found"); + } + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE]; + int bytesRead; + while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { + outputStream.write(data, 0, bytesRead); + } + outputStream.flush(); + return outputStream.toByteArray(); + } finally { + if (close) { + inputStream.close(); + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated + * and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes. + * + * @return the class access flags. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see {@link Type#getInternalName()}). + * + * @return the internal class name. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + // this_class is just after the access_flags field (using 2 bytes). + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For + * interfaces, the super class is {@link Object}. + * + * @return the internal name of the super class, or null for {@link Object} class. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + // super_class is after the access_flags and this_class fields (2 bytes each). + return readClass(header + 4, new char[maxStringLength]); + } + + /** + * Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}). + * + * @return the internal names of the directly implemented interfaces. Inherited implemented + * interfaces are not returned. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + // interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each). + int currentOffset = header + 6; + int interfacesCount = readUnsignedShort(currentOffset); + String[] interfaces = new String[interfacesCount]; + if (interfacesCount > 0) { + char[] charBuffer = new char[maxStringLength]; + for (int i = 0; i < interfacesCount; ++i) { + currentOffset += 2; + interfaces[i] = readClass(currentOffset, charBuffer); + } + } + return interfaces; + } + + // ----------------------------------------------------------------------------------------------- + // Public methods + // ----------------------------------------------------------------------------------------------- + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept(final ClassVisitor classVisitor, final int parsingOptions) { + accept(classVisitor, new Attribute[0], parsingOptions); + } + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. This may + * corrupt it if this value contains references to the constant pool, or has syntactic or + * semantic links with a class element that has been transformed by a class adapter between + * the reader and the writer. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept( + final ClassVisitor classVisitor, + final Attribute[] attributePrototypes, + final int parsingOptions) { + Context context = new Context(); + context.attributePrototypes = attributePrototypes; + context.parsingOptions = parsingOptions; + context.charBuffer = new char[maxStringLength]; + + // Read the access_flags, this_class, super_class, interface_count and interfaces fields. + char[] charBuffer = context.charBuffer; + int currentOffset = header; + int accessFlags = readUnsignedShort(currentOffset); + String thisClass = readClass(currentOffset + 2, charBuffer); + String superClass = readClass(currentOffset + 4, charBuffer); + String[] interfaces = new String[readUnsignedShort(currentOffset + 6)]; + currentOffset += 8; + for (int i = 0; i < interfaces.length; ++i) { + interfaces[i] = readClass(currentOffset, charBuffer); + currentOffset += 2; + } - int index2 = item.hashCode % items2.length; - item.next = items2[index2]; - items2[index2] = item; - } + // Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the InnerClasses attribute, or 0. + int innerClassesOffset = 0; + // - The offset of the EnclosingMethod attribute, or 0. + int enclosingMethodOffset = 0; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The string corresponding to the SourceFile attribute, or null. + String sourceFile = null; + // - The string corresponding to the SourceDebugExtension attribute, or null. + String sourceDebugExtension = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the Module attribute, or 0. + int moduleOffset = 0; + // - The offset of the ModulePackages attribute, or 0. + int modulePackagesOffset = 0; + // - The string corresponding to the ModuleMainClass attribute, or null. + String moduleMainClass = null; + // - The string corresponding to the NestHost attribute, or null. + String nestHostClass = null; + // - The offset of the NestMembers attribute, or 0. + int nestMembersOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int currentAttributeOffset = getFirstAttributeOffset(); + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SOURCE_FILE.equals(attributeName)) { + sourceFile = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.INNER_CLASSES.equals(attributeName)) { + innerClassesOffset = currentAttributeOffset; + } else if (Constants.ENCLOSING_METHOD.equals(attributeName)) { + enclosingMethodOffset = currentAttributeOffset; + } else if (Constants.NEST_HOST.equals(attributeName)) { + nestHostClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.NEST_MEMBERS.equals(attributeName)) { + nestMembersOffset = currentAttributeOffset; + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) { + sourceDebugExtension = + readUTF(currentAttributeOffset, attributeLength, new char[attributeLength]); + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.MODULE.equals(attributeName)) { + moduleOffset = currentAttributeOffset; + } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) { + moduleMainClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.MODULE_PACKAGES.equals(attributeName)) { + modulePackagesOffset = currentAttributeOffset; + } else if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // This attribute is read in the constructor. + } else { + Attribute attribute = + readAttribute( + attributePrototypes, + attributeName, + currentAttributeOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentAttributeOffset += attributeLength; + } - int off = items[1] - 1; - classWriter.pool.putByteArray(b, off, header - off); - classWriter.items = items2; - classWriter.threshold = (int) (0.75d * ll); - classWriter.index = ll; - } - - /** - * Copies the bootstrap method data into the given {@link ClassWriter}. - * Should be called before the {@link #accept(ClassVisitor,int)} method. - * - * @param classWriter - * the {@link ClassWriter} to copy bootstrap methods into. - */ - private void copyBootstrapMethods(final ClassWriter classWriter, - final Item[] items, final char[] c) { - // finds the "BootstrapMethods" attribute - int u = getAttributes(); - boolean found = false; - for (int i = readUnsignedShort(u); i > 0; --i) { - String attrName = readUTF8(u + 2, c); - if ("BootstrapMethods".equals(attrName)) { - found = true; - break; - } - u += 6 + readInt(u + 4); - } - if (!found) { - return; - } - // copies the bootstrap methods in the class writer - int boostrapMethodCount = readUnsignedShort(u + 8); - for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) { - int position = v - u - 10; - int hashCode = readConst(readUnsignedShort(v), c).hashCode(); - for (int k = readUnsignedShort(v + 2); k > 0; --k) { - hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode(); - v += 2; - } - v += 4; - Item item = new Item(j); - item.set(position, hashCode & 0x7FFFFFFF); - int index = item.hashCode % items.length; - item.next = items[index]; - items[index] = item; - } - int attrSize = readInt(u + 4); - ByteVector bootstrapMethods = new ByteVector(attrSize + 62); - bootstrapMethods.putByteArray(b, u + 10, attrSize - 2); - classWriter.bootstrapMethodsCount = boostrapMethodCount; - classWriter.bootstrapMethods = bootstrapMethods; - } - - /** - * Constructs a new {@link ClassReader} object. - * - * @param is - * an input stream from which to read the class. - * @throws IOException - * if a problem occurs during reading. - */ - public ClassReader(final InputStream is) throws IOException { - this(readClass(is, false)); - } - - /** - * Constructs a new {@link ClassReader} object. - * - * @param name - * the binary qualified name of the class to be read. - * @throws IOException - * if an exception occurs during reading. - */ - public ClassReader(final String name) throws IOException { - this(readClass( - ClassLoader.getSystemResourceAsStream(name.replace('.', '/') - + ".class"), true)); - } - - /** - * Reads the bytecode of a class. - * - * @param is - * an input stream from which to read the class. - * @param close - * true to close the input stream after reading. - * @return the bytecode read from the given input stream. - * @throws IOException - * if a problem occurs during reading. - */ - private static byte[] readClass(final InputStream is, boolean close) - throws IOException { - if (is == null) { - throw new IOException("Class not found"); - } - try { - byte[] b = new byte[is.available()]; - int len = 0; - while (true) { - int n = is.read(b, len, b.length - len); - if (n == -1) { - if (len < b.length) { - byte[] c = new byte[len]; - System.arraycopy(b, 0, c, 0, len); - b = c; - } - return b; - } - len += n; - if (len == b.length) { - int last = is.read(); - if (last < 0) { - return b; - } - byte[] c = new byte[b.length + 1000]; - System.arraycopy(b, 0, c, 0, len); - c[len++] = (byte) last; - b = c; - } - } - } finally { - if (close) { - is.close(); - } - } + // Visit the class declaration. The minor_version and major_version fields start 6 bytes before + // the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition). + classVisitor.visit( + readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces); + + // Visit the SourceFile and SourceDebugExtenstion attributes. + if ((parsingOptions & SKIP_DEBUG) == 0 + && (sourceFile != null || sourceDebugExtension != null)) { + classVisitor.visitSource(sourceFile, sourceDebugExtension); } - // ------------------------------------------------------------------------ - // Public methods - // ------------------------------------------------------------------------ - - /** - * Makes the given visitor visit the Java class of this {@link ClassReader} - * . This class is the one specified in the constructor (see - * {@link #ClassReader(byte[]) ClassReader}). - * - * @param classVisitor - * the visitor that must visit this class. - * @param flags - * option flags that can be used to modify the default behavior - * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} - * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. - */ - public void accept(final ClassVisitor classVisitor, final int flags) { - accept(classVisitor, new Attribute[0], flags); - } - - /** - * Makes the given visitor visit the Java class of this {@link ClassReader}. - * This class is the one specified in the constructor (see - * {@link #ClassReader(byte[]) ClassReader}). - * - * @param classVisitor - * the visitor that must visit this class. - * @param attrs - * prototypes of the attributes that must be parsed during the - * visit of the class. Any attribute whose type is not equal to - * the type of one the prototypes will not be parsed: its byte - * array value will be passed unchanged to the ClassWriter. - * This may corrupt it if this value contains references to - * the constant pool, or has syntactic or semantic links with a - * class element that has been transformed by a class adapter - * between the reader and the writer. - * @param flags - * option flags that can be used to modify the default behavior - * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} - * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. - */ - public void accept(final ClassVisitor classVisitor, - final Attribute[] attrs, final int flags) { - int u = header; // current offset in the class file - char[] c = new char[maxStringLength]; // buffer used to read strings - - Context context = new Context(); - context.attrs = attrs; - context.flags = flags; - context.buffer = c; - - // reads the class declaration - int access = readUnsignedShort(u); - String name = readClass(u + 2, c); - String superClass = readClass(u + 4, c); - String[] interfaces = new String[readUnsignedShort(u + 6)]; - u += 8; - for (int i = 0; i < interfaces.length; ++i) { - interfaces[i] = readClass(u, c); - u += 2; - } + // Visit the Module, ModulePackages and ModuleMainClass attributes. + if (moduleOffset != 0) { + readModule(classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass); + } - // reads the class attributes - String signature = null; - String sourceFile = null; - String sourceDebug = null; - String enclosingOwner = null; - String enclosingName = null; - String enclosingDesc = null; - int anns = 0; - int ianns = 0; - int innerClasses = 0; - Attribute attributes = null; - - u = getAttributes(); - for (int i = readUnsignedShort(u); i > 0; --i) { - String attrName = readUTF8(u + 2, c); - // tests are sorted in decreasing frequency order - // (based on frequencies observed on typical classes) - if ("SourceFile".equals(attrName)) { - sourceFile = readUTF8(u + 8, c); - } else if ("InnerClasses".equals(attrName)) { - innerClasses = u + 8; - } else if ("EnclosingMethod".equals(attrName)) { - enclosingOwner = readClass(u + 8, c); - int item = readUnsignedShort(u + 10); - if (item != 0) { - enclosingName = readUTF8(items[item], c); - enclosingDesc = readUTF8(items[item] + 2, c); - } - } else if (SIGNATURES && "Signature".equals(attrName)) { - signature = readUTF8(u + 8, c); - } else if (ANNOTATIONS - && "RuntimeVisibleAnnotations".equals(attrName)) { - anns = u + 8; - } else if ("Deprecated".equals(attrName)) { - access |= Opcodes.ACC_DEPRECATED; - } else if ("Synthetic".equals(attrName)) { - access |= Opcodes.ACC_SYNTHETIC - | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; - } else if ("SourceDebugExtension".equals(attrName)) { - int len = readInt(u + 4); - sourceDebug = readUTF(u + 8, len, new char[len]); - } else if (ANNOTATIONS - && "RuntimeInvisibleAnnotations".equals(attrName)) { - ianns = u + 8; - } else if ("BootstrapMethods".equals(attrName)) { - int[] bootstrapMethods = new int[readUnsignedShort(u + 8)]; - for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) { - bootstrapMethods[j] = v; - v += 2 + readUnsignedShort(v + 2) << 1; - } - context.bootstrapMethods = bootstrapMethods; - } else { - Attribute attr = readAttribute(attrs, attrName, u + 8, - readInt(u + 4), c, -1, null); - if (attr != null) { - attr.next = attributes; - attributes = attr; - } - } - u += 6 + readInt(u + 4); - } + // Visit the NestHost attribute. + if (nestHostClass != null) { + classVisitor.visitNestHostExperimental(nestHostClass); + } - // visits the class declaration - classVisitor.visit(readInt(items[1] - 7), access, name, signature, - superClass, interfaces); + // Visit the EnclosingMethod attribute. + if (enclosingMethodOffset != 0) { + String className = readClass(enclosingMethodOffset, charBuffer); + int methodIndex = readUnsignedShort(enclosingMethodOffset + 2); + String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer); + String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer); + classVisitor.visitOuterClass(className, name, type); + } - // visits the source and debug info - if ((flags & SKIP_DEBUG) == 0 - && (sourceFile != null || sourceDebug != null)) { - classVisitor.visitSource(sourceFile, sourceDebug); - } + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } - // visits the outer class - if (enclosingOwner != null) { - classVisitor.visitOuterClass(enclosingOwner, enclosingName, - enclosingDesc); - } + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } - // visits the class annotations - if (ANNOTATIONS && anns != 0) { - for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { - v = readAnnotationValues(v + 2, c, true, - classVisitor.visitAnnotation(readUTF8(v, c), true)); - } - } - if (ANNOTATIONS && ianns != 0) { - for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { - v = readAnnotationValues(v + 2, c, true, - classVisitor.visitAnnotation(readUTF8(v, c), false)); - } - } + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } - // visits the attributes - while (attributes != null) { - Attribute attr = attributes.next; - attributes.next = null; - classVisitor.visitAttribute(attributes); - attributes = attr; - } + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } - // visits the inner classes - if (innerClasses != 0) { - int v = innerClasses + 2; - for (int i = readUnsignedShort(innerClasses); i > 0; --i) { - classVisitor.visitInnerClass(readClass(v, c), - readClass(v + 2, c), readUTF8(v + 4, c), - readUnsignedShort(v + 6)); - v += 8; - } - } + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in ClassWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + classVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } - // visits the fields and methods - u = header + 10 + 2 * interfaces.length; - for (int i = readUnsignedShort(u - 2); i > 0; --i) { - u = readField(classVisitor, context, u); - } - u += 2; - for (int i = readUnsignedShort(u - 2); i > 0; --i) { - u = readMethod(classVisitor, context, u); - } + // Visit the NestedMembers attribute. + if (nestMembersOffset != 0) { + int numberOfNestMembers = readUnsignedShort(nestMembersOffset); + int currentNestMemberOffset = nestMembersOffset + 2; + while (numberOfNestMembers-- > 0) { + classVisitor.visitNestMemberExperimental(readClass(currentNestMemberOffset, charBuffer)); + currentNestMemberOffset += 2; + } + } - // visits the end of the class - classVisitor.visitEnd(); - } - - /** - * Reads a field and makes the given visitor visit it. - * - * @param classVisitor - * the visitor that must visit the field. - * @param context - * information about the class being parsed. - * @param u - * the start offset of the field in the class file. - * @return the offset of the first byte following the field in the class. - */ - private int readField(final ClassVisitor classVisitor, - final Context context, int u) { - // reads the field declaration - char[] c = context.buffer; - int access = readUnsignedShort(u); - String name = readUTF8(u + 2, c); - String desc = readUTF8(u + 4, c); - u += 6; - - // reads the field attributes - String signature = null; - int anns = 0; - int ianns = 0; - Object value = null; - Attribute attributes = null; - - for (int i = readUnsignedShort(u); i > 0; --i) { - String attrName = readUTF8(u + 2, c); - // tests are sorted in decreasing frequency order - // (based on frequencies observed on typical classes) - if ("ConstantValue".equals(attrName)) { - int item = readUnsignedShort(u + 8); - value = item == 0 ? null : readConst(item, c); - } else if (SIGNATURES && "Signature".equals(attrName)) { - signature = readUTF8(u + 8, c); - } else if ("Deprecated".equals(attrName)) { - access |= Opcodes.ACC_DEPRECATED; - } else if ("Synthetic".equals(attrName)) { - access |= Opcodes.ACC_SYNTHETIC - | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; - } else if (ANNOTATIONS - && "RuntimeVisibleAnnotations".equals(attrName)) { - anns = u + 8; - } else if (ANNOTATIONS - && "RuntimeInvisibleAnnotations".equals(attrName)) { - ianns = u + 8; - } else { - Attribute attr = readAttribute(context.attrs, attrName, u + 8, - readInt(u + 4), c, -1, null); - if (attr != null) { - attr.next = attributes; - attributes = attr; - } - } - u += 6 + readInt(u + 4); + // Visit the InnerClasses attribute. + if (innerClassesOffset != 0) { + int numberOfClasses = readUnsignedShort(innerClassesOffset); + int currentClassesOffset = innerClassesOffset + 2; + while (numberOfClasses-- > 0) { + classVisitor.visitInnerClass( + readClass(currentClassesOffset, charBuffer), + readClass(currentClassesOffset + 2, charBuffer), + readUTF8(currentClassesOffset + 4, charBuffer), + readUnsignedShort(currentClassesOffset + 6)); + currentClassesOffset += 8; + } + } + + // Visit the fields and methods. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (fieldsCount-- > 0) { + currentOffset = readField(classVisitor, context, currentOffset); + } + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + currentOffset = readMethod(classVisitor, context, currentOffset); + } + + // Visit the end of the class. + classVisitor.visitEnd(); + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse modules, fields and methods + // ---------------------------------------------------------------------------------------------- + + /** + * Reads the module attribute and visit it. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param moduleOffset the offset of the Module attribute (excluding the attribute_info's + * attribute_name_index and attribute_length fields). + * @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the + * attribute_info's attribute_name_index and attribute_length fields), or 0. + * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or null. + */ + private void readModule( + final ClassVisitor classVisitor, + final Context context, + final int moduleOffset, + final int modulePackagesOffset, + final String moduleMainClass) { + char[] buffer = context.charBuffer; + + // Read the module_name_index, module_flags and module_version_index fields and visit them. + int currentOffset = moduleOffset; + String moduleName = readModule(currentOffset, buffer); + int moduleFlags = readUnsignedShort(currentOffset + 2); + String moduleVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + ModuleVisitor moduleVisitor = classVisitor.visitModule(moduleName, moduleFlags, moduleVersion); + if (moduleVisitor == null) { + return; + } + + // Visit the ModuleMainClass attribute. + if (moduleMainClass != null) { + moduleVisitor.visitMainClass(moduleMainClass); + } + + // Visit the ModulePackages attribute. + if (modulePackagesOffset != 0) { + int packageCount = readUnsignedShort(modulePackagesOffset); + int currentPackageOffset = modulePackagesOffset + 2; + while (packageCount-- > 0) { + moduleVisitor.visitPackage(readPackage(currentPackageOffset, buffer)); + currentPackageOffset += 2; + } + } + + // Read the 'requires_count' and 'requires' fields. + int requiresCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (requiresCount-- > 0) { + // Read the requires_index, requires_flags and requires_version fields and visit them. + String requires = readModule(currentOffset, buffer); + int requiresFlags = readUnsignedShort(currentOffset + 2); + String requiresVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + moduleVisitor.visitRequire(requires, requiresFlags, requiresVersion); + } + + // Read the 'exports_count' and 'exports' fields. + int exportsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exportsCount-- > 0) { + // Read the exports_index, exports_flags, exports_to_count and exports_to_index fields + // and visit them. + String exports = readPackage(currentOffset, buffer); + int exportsFlags = readUnsignedShort(currentOffset + 2); + int exportsToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] exportsTo = null; + if (exportsToCount != 0) { + exportsTo = new String[exportsToCount]; + for (int i = 0; i < exportsToCount; ++i) { + exportsTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; } - u += 2; + } + moduleVisitor.visitExport(exports, exportsFlags, exportsTo); + } - // visits the field declaration - FieldVisitor fv = classVisitor.visitField(access, name, desc, - signature, value); - if (fv == null) { - return u; + // Reads the 'opens_count' and 'opens' fields. + int opensCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (opensCount-- > 0) { + // Read the opens_index, opens_flags, opens_to_count and opens_to_index fields and visit them. + String opens = readPackage(currentOffset, buffer); + int opensFlags = readUnsignedShort(currentOffset + 2); + int opensToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] opensTo = null; + if (opensToCount != 0) { + opensTo = new String[opensToCount]; + for (int i = 0; i < opensToCount; ++i) { + opensTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; } + } + moduleVisitor.visitOpen(opens, opensFlags, opensTo); + } - // visits the field annotations - if (ANNOTATIONS && anns != 0) { - for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { - v = readAnnotationValues(v + 2, c, true, - fv.visitAnnotation(readUTF8(v, c), true)); - } + // Read the 'uses_count' and 'uses' fields. + int usesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (usesCount-- > 0) { + moduleVisitor.visitUse(readClass(currentOffset, buffer)); + currentOffset += 2; + } + + // Read the 'provides_count' and 'provides' fields. + int providesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (providesCount-- > 0) { + // Read the provides_index, provides_with_count and provides_with_index fields and visit them. + String provides = readClass(currentOffset, buffer); + int providesWithCount = readUnsignedShort(currentOffset + 2); + currentOffset += 4; + String[] providesWith = new String[providesWithCount]; + for (int i = 0; i < providesWithCount; ++i) { + providesWith[i] = readClass(currentOffset, buffer); + currentOffset += 2; + } + moduleVisitor.visitProvide(provides, providesWith); + } + + // Visit the end of the module attributes. + moduleVisitor.visitEnd(); + } + + /** + * Reads a JVMS field_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the field. + * @param context information about the class being parsed. + * @param fieldInfoOffset the start offset of the field_info structure. + * @return the offset of the first byte following the field_info structure. + */ + private int readField( + final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = fieldInfoOffset; + int accessFlags = readUnsignedShort(currentOffset); + String name = readUTF8(currentOffset + 2, charBuffer); + String descriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the field attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The value corresponding to the ConstantValue attribute, or null. + Object constantValue = null; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CONSTANT_VALUE.equals(attributeName)) { + int constantvalueIndex = readUnsignedShort(currentOffset); + constantValue = constantvalueIndex == 0 ? null : readConst(constantvalueIndex, charBuffer); + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Visit the field declaration. + FieldVisitor fieldVisitor = + classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue); + if (fieldVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + fieldVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + fieldVisitor.visitEnd(); + return currentOffset; + } + + /** + * Reads a JVMS method_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the method. + * @param context information about the class being parsed. + * @param methodInfoOffset the start offset of the method_info structure. + * @return the offset of the first byte following the method_info structure. + */ + private int readMethod( + final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = methodInfoOffset; + context.currentMethodAccessFlags = readUnsignedShort(currentOffset); + context.currentMethodName = readUTF8(currentOffset + 2, charBuffer); + context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the method attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the Code attribute, or 0. + int codeOffset = 0; + // - The offset of the Exceptions attribute, or 0. + int exceptionsOffset = 0; + // - The strings corresponding to the Exceptions attribute, or null. + String[] exceptions = null; + // - Whether the method has a Synthetic attribute. + boolean synthetic = false; + // - The constant pool index contained in the Signature attribute, or 0. + int signatureIndex = 0; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0. + int runtimeVisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0. + int runtimeInvisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the AnnotationDefault attribute, or 0. + int annotationDefaultOffset = 0; + // - The offset of the MethodParameters attribute, or 0. + int methodParametersOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CODE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_CODE) == 0) { + codeOffset = currentOffset; } - if (ANNOTATIONS && ianns != 0) { - for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { - v = readAnnotationValues(v + 2, c, true, - fv.visitAnnotation(readUTF8(v, c), false)); - } + } else if (Constants.EXCEPTIONS.equals(attributeName)) { + exceptionsOffset = currentOffset; + exceptions = new String[readUnsignedShort(exceptionsOffset)]; + int currentExceptionOffset = exceptionsOffset + 2; + for (int i = 0; i < exceptions.length; ++i) { + exceptions[i] = readClass(currentExceptionOffset, charBuffer); + currentExceptionOffset += 2; } + } else if (Constants.SIGNATURE.equals(attributeName)) { + signatureIndex = readUnsignedShort(currentOffset); + } else if (Constants.DEPRECATED.equals(attributeName)) { + context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) { + annotationDefaultOffset = currentOffset; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + synthetic = true; + context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.METHOD_PARAMETERS.equals(attributeName)) { + methodParametersOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } - // visits the field attributes - while (attributes != null) { - Attribute attr = attributes.next; - attributes.next = null; - fv.visitAttribute(attributes); - attributes = attr; - } + // Visit the method declaration. + MethodVisitor methodVisitor = + classVisitor.visitMethod( + context.currentMethodAccessFlags, + context.currentMethodName, + context.currentMethodDescriptor, + signatureIndex == 0 ? null : readUTF(signatureIndex, charBuffer), + exceptions); + if (methodVisitor == null) { + return currentOffset; + } - // visits the end of the field - fv.visitEnd(); - - return u; - } - - /** - * Reads a method and makes the given visitor visit it. - * - * @param classVisitor - * the visitor that must visit the method. - * @param context - * information about the class being parsed. - * @param u - * the start offset of the method in the class file. - * @return the offset of the first byte following the method in the class. - */ - private int readMethod(final ClassVisitor classVisitor, - final Context context, int u) { - // reads the method declaration - char[] c = context.buffer; - int access = readUnsignedShort(u); - String name = readUTF8(u + 2, c); - String desc = readUTF8(u + 4, c); - u += 6; - - // reads the method attributes - int code = 0; - int exception = 0; - String[] exceptions = null; - String signature = null; - int anns = 0; - int ianns = 0; - int dann = 0; - int mpanns = 0; - int impanns = 0; - int firstAttribute = u; - Attribute attributes = null; - - for (int i = readUnsignedShort(u); i > 0; --i) { - String attrName = readUTF8(u + 2, c); - // tests are sorted in decreasing frequency order - // (based on frequencies observed on typical classes) - if ("Code".equals(attrName)) { - if ((context.flags & SKIP_CODE) == 0) { - code = u + 8; - } - } else if ("Exceptions".equals(attrName)) { - exceptions = new String[readUnsignedShort(u + 8)]; - exception = u + 10; - for (int j = 0; j < exceptions.length; ++j) { - exceptions[j] = readClass(exception, c); - exception += 2; - } - } else if (SIGNATURES && "Signature".equals(attrName)) { - signature = readUTF8(u + 8, c); - } else if ("Deprecated".equals(attrName)) { - access |= Opcodes.ACC_DEPRECATED; - } else if (ANNOTATIONS - && "RuntimeVisibleAnnotations".equals(attrName)) { - anns = u + 8; - } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { - dann = u + 8; - } else if ("Synthetic".equals(attrName)) { - access |= Opcodes.ACC_SYNTHETIC - | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; - } else if (ANNOTATIONS - && "RuntimeInvisibleAnnotations".equals(attrName)) { - ianns = u + 8; - } else if (ANNOTATIONS - && "RuntimeVisibleParameterAnnotations".equals(attrName)) { - mpanns = u + 8; - } else if (ANNOTATIONS - && "RuntimeInvisibleParameterAnnotations".equals(attrName)) { - impanns = u + 8; - } else { - Attribute attr = readAttribute(context.attrs, attrName, u + 8, - readInt(u + 4), c, -1, null); - if (attr != null) { - attr.next = attributes; - attributes = attr; - } - } - u += 6 + readInt(u + 4); - } - u += 2; + // If the returned MethodVisitor is in fact a MethodWriter, it means there is no method + // adapter between the reader and the writer. In this case, it might be possible to copy + // the method attributes directly into the writer. If so, return early without visiting + // the content of these attributes. + if (methodVisitor instanceof MethodWriter) { + MethodWriter methodWriter = (MethodWriter) methodVisitor; + if (methodWriter.canCopyMethodAttributes( + this, + methodInfoOffset, + currentOffset - methodInfoOffset, + synthetic, + (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0, + signatureIndex, + exceptionsOffset)) { + return currentOffset; + } + } - // visits the method declaration - MethodVisitor mv = classVisitor.visitMethod(access, name, desc, - signature, exceptions); - if (mv == null) { - return u; - } + // Visit the MethodParameters attribute. + if (methodParametersOffset != 0) { + int parametersCount = readByte(methodParametersOffset); + int currentParameterOffset = methodParametersOffset + 1; + while (parametersCount-- > 0) { + // Read the name_index and access_flags fields and visit them. + methodVisitor.visitParameter( + readUTF8(currentParameterOffset, charBuffer), + readUnsignedShort(currentParameterOffset + 2)); + currentParameterOffset += 4; + } + } - /* - * if the returned MethodVisitor is in fact a MethodWriter, it means - * there is no method adapter between the reader and the writer. If, in - * addition, the writer's constant pool was copied from this reader - * (mw.cw.cr == this), and the signature and exceptions of the method - * have not been changed, then it is possible to skip all visit events - * and just copy the original code of the method to the writer (the - * access, name and descriptor can have been changed, this is not - * important since they are not copied as is from the reader). - */ - if (WRITER && mv instanceof MethodWriter) { - MethodWriter mw = (MethodWriter) mv; - if (mw.cw.cr == this && signature == mw.signature) { - boolean sameExceptions = false; - if (exceptions == null) { - sameExceptions = mw.exceptionCount == 0; - } else if (exceptions.length == mw.exceptionCount) { - sameExceptions = true; - for (int j = exceptions.length - 1; j >= 0; --j) { - exception -= 2; - if (mw.exceptions[j] != readUnsignedShort(exception)) { - sameExceptions = false; - break; - } - } - } - if (sameExceptions) { - /* - * we do not copy directly the code into MethodWriter to - * save a byte array copy operation. The real copy will be - * done in ClassWriter.toByteArray(). - */ - mw.classReaderOffset = firstAttribute; - mw.classReaderLength = u - firstAttribute; - return u; - } - } - } + // Visit the AnnotationDefault attribute. + if (annotationDefaultOffset != 0) { + AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault(); + readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer); + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + } - // visits the method annotations - if (ANNOTATIONS && dann != 0) { - AnnotationVisitor dv = mv.visitAnnotationDefault(); - readAnnotationValue(dann, c, null, dv); - if (dv != null) { - dv.visitEnd(); - } - } - if (ANNOTATIONS && anns != 0) { - for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { - v = readAnnotationValues(v + 2, c, true, - mv.visitAnnotation(readUTF8(v, c), true)); - } + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleParameterAnnotations attribute. + if (runtimeVisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true); + } + + // Visit the RuntimeInvisibleParameterAnnotations attribute. + if (runtimeInvisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, + context, + runtimeInvisibleParameterAnnotationsOffset, + /* visible = */ false); + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the Code attribute. + if (codeOffset != 0) { + methodVisitor.visitCode(); + readCode(methodVisitor, context, codeOffset); + } + + // Visit the end of the method. + methodVisitor.visitEnd(); + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse a Code attribute + // ---------------------------------------------------------------------------------------------- + + /** + * Reads a JVMS 'Code' attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the Code attribute. + * @param context information about the class being parsed. + * @param codeOffset the start offset in {@link #b} of the Code attribute, excluding its + * attribute_name_index and attribute_length fields. + */ + private void readCode( + final MethodVisitor methodVisitor, final Context context, final int codeOffset) { + int currentOffset = codeOffset; + + // Read the max_stack, max_locals and code_length fields. + final byte[] classFileBuffer = b; + final char[] charBuffer = context.charBuffer; + final int maxStack = readUnsignedShort(currentOffset); + final int maxLocals = readUnsignedShort(currentOffset + 2); + final int codeLength = readInt(currentOffset + 4); + currentOffset += 8; + + // Read the bytecode 'code' array to create a label for each referenced instruction. + final int bytecodeStartOffset = currentOffset; + final int bytecodeEndOffset = currentOffset + codeLength; + final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1]; + while (currentOffset < bytecodeEndOffset) { + final int bytecodeOffset = currentOffset - bytecodeStartOffset; + final int opcode = classFileBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Constants.NOP: + case Constants.ACONST_NULL: + case Constants.ICONST_M1: + case Constants.ICONST_0: + case Constants.ICONST_1: + case Constants.ICONST_2: + case Constants.ICONST_3: + case Constants.ICONST_4: + case Constants.ICONST_5: + case Constants.LCONST_0: + case Constants.LCONST_1: + case Constants.FCONST_0: + case Constants.FCONST_1: + case Constants.FCONST_2: + case Constants.DCONST_0: + case Constants.DCONST_1: + case Constants.IALOAD: + case Constants.LALOAD: + case Constants.FALOAD: + case Constants.DALOAD: + case Constants.AALOAD: + case Constants.BALOAD: + case Constants.CALOAD: + case Constants.SALOAD: + case Constants.IASTORE: + case Constants.LASTORE: + case Constants.FASTORE: + case Constants.DASTORE: + case Constants.AASTORE: + case Constants.BASTORE: + case Constants.CASTORE: + case Constants.SASTORE: + case Constants.POP: + case Constants.POP2: + case Constants.DUP: + case Constants.DUP_X1: + case Constants.DUP_X2: + case Constants.DUP2: + case Constants.DUP2_X1: + case Constants.DUP2_X2: + case Constants.SWAP: + case Constants.IADD: + case Constants.LADD: + case Constants.FADD: + case Constants.DADD: + case Constants.ISUB: + case Constants.LSUB: + case Constants.FSUB: + case Constants.DSUB: + case Constants.IMUL: + case Constants.LMUL: + case Constants.FMUL: + case Constants.DMUL: + case Constants.IDIV: + case Constants.LDIV: + case Constants.FDIV: + case Constants.DDIV: + case Constants.IREM: + case Constants.LREM: + case Constants.FREM: + case Constants.DREM: + case Constants.INEG: + case Constants.LNEG: + case Constants.FNEG: + case Constants.DNEG: + case Constants.ISHL: + case Constants.LSHL: + case Constants.ISHR: + case Constants.LSHR: + case Constants.IUSHR: + case Constants.LUSHR: + case Constants.IAND: + case Constants.LAND: + case Constants.IOR: + case Constants.LOR: + case Constants.IXOR: + case Constants.LXOR: + case Constants.I2L: + case Constants.I2F: + case Constants.I2D: + case Constants.L2I: + case Constants.L2F: + case Constants.L2D: + case Constants.F2I: + case Constants.F2L: + case Constants.F2D: + case Constants.D2I: + case Constants.D2L: + case Constants.D2F: + case Constants.I2B: + case Constants.I2C: + case Constants.I2S: + case Constants.LCMP: + case Constants.FCMPL: + case Constants.FCMPG: + case Constants.DCMPL: + case Constants.DCMPG: + case Constants.IRETURN: + case Constants.LRETURN: + case Constants.FRETURN: + case Constants.DRETURN: + case Constants.ARETURN: + case Constants.RETURN: + case Constants.ARRAYLENGTH: + case Constants.ATHROW: + case Constants.MONITORENTER: + case Constants.MONITOREXIT: + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + currentOffset += 1; + break; + case Constants.IFEQ: + case Constants.IFNE: + case Constants.IFLT: + case Constants.IFGE: + case Constants.IFGT: + case Constants.IFLE: + case Constants.IF_ICMPEQ: + case Constants.IF_ICMPNE: + case Constants.IF_ICMPLT: + case Constants.IF_ICMPGE: + case Constants.IF_ICMPGT: + case Constants.IF_ICMPLE: + case Constants.IF_ACMPEQ: + case Constants.IF_ACMPNE: + case Constants.GOTO: + case Constants.JSR: + case Constants.IFNULL: + case Constants.IFNONNULL: + createLabel(bytecodeOffset + readShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + createLabel(bytecodeOffset + readUnsignedShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + case Constants.ASM_GOTO_W: + createLabel(bytecodeOffset + readInt(currentOffset + 1), labels); + currentOffset += 5; + break; + case Constants.WIDE: + switch (classFileBuffer[currentOffset + 1] & 0xFF) { + case Constants.ILOAD: + case Constants.FLOAD: + case Constants.ALOAD: + case Constants.LLOAD: + case Constants.DLOAD: + case Constants.ISTORE: + case Constants.FSTORE: + case Constants.ASTORE: + case Constants.LSTORE: + case Constants.DSTORE: + case Constants.RET: + currentOffset += 4; + break; + case Constants.IINC: + currentOffset += 6; + break; + default: + throw new IllegalArgumentException(); + } + break; + case Constants.TABLESWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of table entries. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numTableEntries = readInt(currentOffset + 8) - readInt(currentOffset + 4) + 1; + currentOffset += 12; + // Read the table labels. + while (numTableEntries-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset), labels); + currentOffset += 4; + } + break; + case Constants.LOOKUPSWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of switch cases. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numSwitchCases = readInt(currentOffset + 4); + currentOffset += 8; + // Read the switch labels. + while (numSwitchCases-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset + 4), labels); + currentOffset += 8; + } + break; + case Constants.ILOAD: + case Constants.LLOAD: + case Constants.FLOAD: + case Constants.DLOAD: + case Constants.ALOAD: + case Constants.ISTORE: + case Constants.LSTORE: + case Constants.FSTORE: + case Constants.DSTORE: + case Constants.ASTORE: + case Constants.RET: + case Constants.BIPUSH: + case Constants.NEWARRAY: + case Constants.LDC: + currentOffset += 2; + break; + case Constants.SIPUSH: + case Constants.LDC_W: + case Constants.LDC2_W: + case Constants.GETSTATIC: + case Constants.PUTSTATIC: + case Constants.GETFIELD: + case Constants.PUTFIELD: + case Constants.INVOKEVIRTUAL: + case Constants.INVOKESPECIAL: + case Constants.INVOKESTATIC: + case Constants.NEW: + case Constants.ANEWARRAY: + case Constants.CHECKCAST: + case Constants.INSTANCEOF: + case Constants.IINC: + currentOffset += 3; + break; + case Constants.INVOKEINTERFACE: + case Constants.INVOKEDYNAMIC: + currentOffset += 5; + break; + case Constants.MULTIANEWARRAY: + currentOffset += 4; + break; + default: + throw new IllegalArgumentException(); + } + } + + // Read the 'exception_table_length' and 'exception_table' field to create a label for each + // referenced instruction, and to make methodVisitor visit the corresponding try catch blocks. + { + int exceptionTableLength = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exceptionTableLength-- > 0) { + Label start = createLabel(readUnsignedShort(currentOffset), labels); + Label end = createLabel(readUnsignedShort(currentOffset + 2), labels); + Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels); + String catchType = + readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer); + currentOffset += 8; + methodVisitor.visitTryCatchBlock(start, end, handler, catchType); + } + } + + // Read the Code attributes to create a label for each referenced instruction (the variables + // are ordered as in Section 4.7 of the JVMS). Attribute offsets exclude the + // attribute_name_index and attribute_length fields. + // - The offset of the current 'stack_map_frame' in the StackMap[Table] attribute, or 0. + // Initially, this is the offset of the first 'stack_map_frame' entry. Then this offset is + // updated after each stack_map_frame is read. + int stackMapFrameOffset = 0; + // - The end offset of the StackMap[Table] attribute, or 0. + int stackMapTableEndOffset = 0; + // - Whether the stack map frames are compressed (i.e. in a StackMapTable) or not. + boolean compressedFrames = true; + // - The offset of the LocalVariableTable attribute, or 0. + int localVariableTableOffset = 0; + // - The offset of the LocalVariableTypeTable attribute, or 0. + int localVariableTypeTableOffset = 0; + // - The offset of each 'type_annotation' entry in the RuntimeVisibleTypeAnnotations + // attribute, or null. + int[] visibleTypeAnnotationOffsets = null; + // - The offset of each 'type_annotation' entry in the RuntimeInvisibleTypeAnnotations + // attribute, or null. + int[] invisibleTypeAnnotationOffsets = null; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + if (Constants.LOCAL_VARIABLE_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + localVariableTableOffset = currentOffset; + // Parse the attribute to find the corresponding (debug only) labels. + int currentLocalVariableTableOffset = currentOffset; + int localVariableTableLength = readUnsignedShort(currentLocalVariableTableOffset); + currentLocalVariableTableOffset += 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentLocalVariableTableOffset); + createDebugLabel(startPc, labels); + int length = readUnsignedShort(currentLocalVariableTableOffset + 2); + createDebugLabel(startPc + length, labels); + // Skip the name_index, descriptor_index and index fields (2 bytes each). + currentLocalVariableTableOffset += 10; + } } - if (ANNOTATIONS && ianns != 0) { - for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { - v = readAnnotationValues(v + 2, c, true, - mv.visitAnnotation(readUTF8(v, c), false)); - } + } else if (Constants.LOCAL_VARIABLE_TYPE_TABLE.equals(attributeName)) { + localVariableTypeTableOffset = currentOffset; + // Here we do not extract the labels corresponding to the attribute content. We assume they + // are the same or a subset of those of the LocalVariableTable attribute. + } else if (Constants.LINE_NUMBER_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + // Parse the attribute to find the corresponding (debug only) labels. + int currentLineNumberTableOffset = currentOffset; + int lineNumberTableLength = readUnsignedShort(currentLineNumberTableOffset); + currentLineNumberTableOffset += 2; + while (lineNumberTableLength-- > 0) { + int startPc = readUnsignedShort(currentLineNumberTableOffset); + int lineNumber = readUnsignedShort(currentLineNumberTableOffset + 2); + currentLineNumberTableOffset += 4; + createDebugLabel(startPc, labels); + labels[startPc].addLineNumber(lineNumber); + } } - if (ANNOTATIONS && mpanns != 0) { - readParameterAnnotations(mpanns, desc, c, true, mv); + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + visibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true); + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // type annotation at a time (i.e. after a type annotation has been visited, the next type + // annotation is read), and the labels it contains are also extracted one annotation at a + // time. This assumes that type annotations are ordered by increasing bytecode offset. + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + invisibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false); + // Same comment as above for the RuntimeVisibleTypeAnnotations attribute. + } else if (Constants.STACK_MAP_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; } - if (ANNOTATIONS && impanns != 0) { - readParameterAnnotations(impanns, desc, c, false, mv); + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // frame at a time (i.e. after a frame has been visited, the next frame is read), and the + // labels it contains are also extracted one frame at a time. Thanks to the ordering of + // frames, having only a "one frame lookahead" is not a problem, i.e. it is not possible to + // see an offset smaller than the offset of the current instruction and for which no Label + // exist. Except for UNINITIALIZED type offsets. We solve this by parsing the stack map + // table without a full decoding (see below). + } else if ("StackMap".equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; + compressedFrames = false; } + // IMPORTANT! Here we assume that the frames are ordered, as in the StackMapTable attribute, + // although this is not guaranteed by the attribute format. This allows an incremental + // extraction of the labels corresponding to this attribute (see the comment above for the + // StackMapTable attribute). + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + codeOffset, + labels); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } - // visits the method attributes - while (attributes != null) { - Attribute attr = attributes.next; - attributes.next = null; - mv.visitAttribute(attributes); - attributes = attr; + // Initialize the context fields related to stack map frames, and generate the first + // (implicit) stack map frame, if needed. + final boolean expandFrames = (context.parsingOptions & EXPAND_FRAMES) != 0; + if (stackMapFrameOffset != 0) { + // The bytecode offset of the first explicit frame is not offset_delta + 1 but only + // offset_delta. Setting the implicit frame offset to -1 allows us to use of the + // "offset_delta + 1" rule in all cases. + context.currentFrameOffset = -1; + context.currentFrameType = 0; + context.currentFrameLocalCount = 0; + context.currentFrameLocalCountDelta = 0; + context.currentFrameLocalTypes = new Object[maxLocals]; + context.currentFrameStackCount = 0; + context.currentFrameStackTypes = new Object[maxStack]; + if (expandFrames) { + computeImplicitFrame(context); + } + // Find the labels for UNINITIALIZED frame types. Instead of decoding each element of the + // stack map table, we look for 3 consecutive bytes that "look like" an UNINITIALIZED type + // (tag ITEM_Uninitialized, offset within bytecode bounds, NEW instruction at this offset). + // We may find false positives (i.e. not real UNINITIALIZED types), but this should be rare, + // and the only consequence will be the creation of an unneeded label. This is better than + // creating a label for each NEW instruction, and faster than fully decoding the whole stack + // map table. + for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) { + if (classFileBuffer[offset] == Frame.ITEM_UNINITIALIZED) { + int potentialBytecodeOffset = readUnsignedShort(offset + 1); + if (potentialBytecodeOffset >= 0 + && potentialBytecodeOffset < codeLength + && (classFileBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF) + == Opcodes.NEW) { + createLabel(potentialBytecodeOffset, labels); + } } + } + } + if (expandFrames && (context.parsingOptions & EXPAND_ASM_INSNS) != 0) { + // Expanding the ASM specific instructions can introduce F_INSERT frames, even if the method + // does not currently have any frame. These inserted frames must be computed by simulating the + // effect of the bytecode instructions, one by one, starting from the implicit first frame. + // For this, MethodWriter needs to know maxLocals before the first instruction is visited. To + // ensure this, we visit the implicit first frame here (passing only maxLocals - the rest is + // computed in MethodWriter). + methodVisitor.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null); + } - // visits the method code - if (code != 0) { - context.access = access; - context.name = name; - context.desc = desc; - mv.visitCode(); - readCode(mv, context, code); + // Visit the bytecode instructions. First, introduce state variables for the incremental parsing + // of the type annotations. + + // Index of the next runtime visible type annotation to read (in the + // visibleTypeAnnotationOffsets array). + int currentVisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime visible type annotation to read, or -1. + int currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0); + // Index of the next runtime invisible type annotation to read (in the + // invisibleTypeAnnotationOffsets array). + int currentInvisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime invisible type annotation to read, or -1. + int currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0); + + // Whether a F_INSERT stack map frame must be inserted before the current instruction. + boolean insertFrame = false; + + // The delta to subtract from a goto_w or jsr_w opcode to get the corresponding goto or jsr + // opcode, or 0 if goto_w and jsr_w must be left unchanged (i.e. when expanding ASM specific + // instructions). + final int wideJumpOpcodeDelta = + (context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0; + + currentOffset = bytecodeStartOffset; + while (currentOffset < bytecodeEndOffset) { + final int currentBytecodeOffset = currentOffset - bytecodeStartOffset; + + // Visit the label and the line number(s) for this bytecode offset, if any. + Label currentLabel = labels[currentBytecodeOffset]; + if (currentLabel != null) { + currentLabel.accept(methodVisitor, (context.parsingOptions & SKIP_DEBUG) == 0); + } + + // Visit the stack map frame for this bytecode offset, if any. + while (stackMapFrameOffset != 0 + && (context.currentFrameOffset == currentBytecodeOffset + || context.currentFrameOffset == -1)) { + // If there is a stack map frame for this offset, make methodVisitor visit it, and read the + // next stack map frame if there is one. + if (context.currentFrameOffset != -1) { + if (!compressedFrames || expandFrames) { + methodVisitor.visitFrame( + Opcodes.F_NEW, + context.currentFrameLocalCount, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } else { + methodVisitor.visitFrame( + context.currentFrameType, + context.currentFrameLocalCountDelta, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } + // Since there is already a stack map frame for this bytecode offset, there is no need to + // insert a new one. + insertFrame = false; } - - // visits the end of the method - mv.visitEnd(); - - return u; - } - - /** - * Reads the bytecode of a method and makes the given visitor visit it. - * - * @param mv - * the visitor that must visit the method's code. - * @param context - * information about the class being parsed. - * @param u - * the start offset of the code attribute in the class file. - */ - private void readCode(final MethodVisitor mv, final Context context, int u) { - // reads the header - byte[] b = this.b; - char[] c = context.buffer; - int maxStack = readUnsignedShort(u); - int maxLocals = readUnsignedShort(u + 2); - int codeLength = readInt(u + 4); - u += 8; - - // reads the bytecode to find the labels - int codeStart = u; - int codeEnd = u + codeLength; - Label[] labels = new Label[codeLength + 2]; - readLabel(codeLength + 1, labels); - while (u < codeEnd) { - int offset = u - codeStart; - int opcode = b[u] & 0xFF; - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - case ClassWriter.IMPLVAR_INSN: - u += 1; - break; - case ClassWriter.LABEL_INSN: - readLabel(offset + readShort(u + 1), labels); - u += 3; - break; - case ClassWriter.LABELW_INSN: - readLabel(offset + readInt(u + 1), labels); - u += 5; - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - u += 6; - } else { - u += 4; - } - break; - case ClassWriter.TABL_INSN: - // skips 0 to 3 padding bytes - u = u + 4 - (offset & 3); - // reads instruction - readLabel(offset + readInt(u), labels); - for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) { - readLabel(offset + readInt(u + 12), labels); - u += 4; - } - u += 12; - break; - case ClassWriter.LOOK_INSN: - // skips 0 to 3 padding bytes - u = u + 4 - (offset & 3); - // reads instruction - readLabel(offset + readInt(u), labels); - for (int i = readInt(u + 4); i > 0; --i) { - readLabel(offset + readInt(u + 12), labels); - u += 8; - } - u += 8; - break; - case ClassWriter.VAR_INSN: - case ClassWriter.SBYTE_INSN: - case ClassWriter.LDC_INSN: - u += 2; - break; - case ClassWriter.SHORT_INSN: - case ClassWriter.LDCW_INSN: - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.TYPE_INSN: - case ClassWriter.IINC_INSN: - u += 3; - break; - case ClassWriter.ITFMETH_INSN: - case ClassWriter.INDYMETH_INSN: - u += 5; - break; - // case MANA_INSN: - default: - u += 4; - break; - } + if (stackMapFrameOffset < stackMapTableEndOffset) { + stackMapFrameOffset = + readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context); + } else { + stackMapFrameOffset = 0; } + } - // reads the try catch entries to find the labels, and also visits them - for (int i = readUnsignedShort(u); i > 0; --i) { - Label start = readLabel(readUnsignedShort(u + 2), labels); - Label end = readLabel(readUnsignedShort(u + 4), labels); - Label handler = readLabel(readUnsignedShort(u + 6), labels); - String type = readUTF8(items[readUnsignedShort(u + 8)], c); - mv.visitTryCatchBlock(start, end, handler, type); - u += 8; + // Insert a stack map frame for this bytecode offset, if requested by setting insertFrame to + // true during the previous iteration. The actual frame content is computed in MethodWriter. + if (insertFrame) { + if ((context.parsingOptions & EXPAND_FRAMES) != 0) { + methodVisitor.visitFrame(Constants.F_INSERT, 0, null, 0, null); } - u += 2; - - // reads the code attributes - int varTable = 0; - int varTypeTable = 0; - boolean zip = true; - boolean unzip = (context.flags & EXPAND_FRAMES) != 0; - int stackMap = 0; - int stackMapSize = 0; - int frameCount = 0; - Context frame = null; - Attribute attributes = null; - - for (int i = readUnsignedShort(u); i > 0; --i) { - String attrName = readUTF8(u + 2, c); - if ("LocalVariableTable".equals(attrName)) { - if ((context.flags & SKIP_DEBUG) == 0) { - varTable = u + 8; - for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { - int label = readUnsignedShort(v + 10); - if (labels[label] == null) { - readLabel(label, labels).status |= Label.DEBUG; - } - label += readUnsignedShort(v + 12); - if (labels[label] == null) { - readLabel(label, labels).status |= Label.DEBUG; - } - v += 10; - } - } - } else if ("LocalVariableTypeTable".equals(attrName)) { - varTypeTable = u + 8; - } else if ("LineNumberTable".equals(attrName)) { - if ((context.flags & SKIP_DEBUG) == 0) { - for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { - int label = readUnsignedShort(v + 10); - if (labels[label] == null) { - readLabel(label, labels).status |= Label.DEBUG; - } - labels[label].line = readUnsignedShort(v + 12); - v += 4; - } - } - } else if (FRAMES && "StackMapTable".equals(attrName)) { - if ((context.flags & SKIP_FRAMES) == 0) { - stackMap = u + 10; - stackMapSize = readInt(u + 4); - frameCount = readUnsignedShort(u + 8); - } - /* - * here we do not extract the labels corresponding to the - * attribute content. This would require a full parsing of the - * attribute, which would need to be repeated in the second - * phase (see below). Instead the content of the attribute is - * read one frame at a time (i.e. after a frame has been - * visited, the next frame is read), and the labels it contains - * are also extracted one frame at a time. Thanks to the - * ordering of frames, having only a "one frame lookahead" is - * not a problem, i.e. it is not possible to see an offset - * smaller than the offset of the current insn and for which no - * Label exist. - */ - /* - * This is not true for UNINITIALIZED type offsets. We solve - * this by parsing the stack map table without a full decoding - * (see below). - */ - } else if (FRAMES && "StackMap".equals(attrName)) { - if ((context.flags & SKIP_FRAMES) == 0) { - zip = false; - stackMap = u + 10; - stackMapSize = readInt(u + 4); - frameCount = readUnsignedShort(u + 8); - } - /* - * IMPORTANT! here we assume that the frames are ordered, as in - * the StackMapTable attribute, although this is not guaranteed - * by the attribute format. - */ + insertFrame = false; + } + + // Visit the instruction at this bytecode offset. + int opcode = classFileBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Constants.NOP: + case Constants.ACONST_NULL: + case Constants.ICONST_M1: + case Constants.ICONST_0: + case Constants.ICONST_1: + case Constants.ICONST_2: + case Constants.ICONST_3: + case Constants.ICONST_4: + case Constants.ICONST_5: + case Constants.LCONST_0: + case Constants.LCONST_1: + case Constants.FCONST_0: + case Constants.FCONST_1: + case Constants.FCONST_2: + case Constants.DCONST_0: + case Constants.DCONST_1: + case Constants.IALOAD: + case Constants.LALOAD: + case Constants.FALOAD: + case Constants.DALOAD: + case Constants.AALOAD: + case Constants.BALOAD: + case Constants.CALOAD: + case Constants.SALOAD: + case Constants.IASTORE: + case Constants.LASTORE: + case Constants.FASTORE: + case Constants.DASTORE: + case Constants.AASTORE: + case Constants.BASTORE: + case Constants.CASTORE: + case Constants.SASTORE: + case Constants.POP: + case Constants.POP2: + case Constants.DUP: + case Constants.DUP_X1: + case Constants.DUP_X2: + case Constants.DUP2: + case Constants.DUP2_X1: + case Constants.DUP2_X2: + case Constants.SWAP: + case Constants.IADD: + case Constants.LADD: + case Constants.FADD: + case Constants.DADD: + case Constants.ISUB: + case Constants.LSUB: + case Constants.FSUB: + case Constants.DSUB: + case Constants.IMUL: + case Constants.LMUL: + case Constants.FMUL: + case Constants.DMUL: + case Constants.IDIV: + case Constants.LDIV: + case Constants.FDIV: + case Constants.DDIV: + case Constants.IREM: + case Constants.LREM: + case Constants.FREM: + case Constants.DREM: + case Constants.INEG: + case Constants.LNEG: + case Constants.FNEG: + case Constants.DNEG: + case Constants.ISHL: + case Constants.LSHL: + case Constants.ISHR: + case Constants.LSHR: + case Constants.IUSHR: + case Constants.LUSHR: + case Constants.IAND: + case Constants.LAND: + case Constants.IOR: + case Constants.LOR: + case Constants.IXOR: + case Constants.LXOR: + case Constants.I2L: + case Constants.I2F: + case Constants.I2D: + case Constants.L2I: + case Constants.L2F: + case Constants.L2D: + case Constants.F2I: + case Constants.F2L: + case Constants.F2D: + case Constants.D2I: + case Constants.D2L: + case Constants.D2F: + case Constants.I2B: + case Constants.I2C: + case Constants.I2S: + case Constants.LCMP: + case Constants.FCMPL: + case Constants.FCMPG: + case Constants.DCMPL: + case Constants.DCMPG: + case Constants.IRETURN: + case Constants.LRETURN: + case Constants.FRETURN: + case Constants.DRETURN: + case Constants.ARETURN: + case Constants.RETURN: + case Constants.ARRAYLENGTH: + case Constants.ATHROW: + case Constants.MONITORENTER: + case Constants.MONITOREXIT: + methodVisitor.visitInsn(opcode); + currentOffset += 1; + break; + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + opcode -= Constants.ILOAD_0; + methodVisitor.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + opcode -= Constants.ISTORE_0; + methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Constants.IFEQ: + case Constants.IFNE: + case Constants.IFLT: + case Constants.IFGE: + case Constants.IFGT: + case Constants.IFLE: + case Constants.IF_ICMPEQ: + case Constants.IF_ICMPNE: + case Constants.IF_ICMPLT: + case Constants.IF_ICMPGE: + case Constants.IF_ICMPGT: + case Constants.IF_ICMPLE: + case Constants.IF_ACMPEQ: + case Constants.IF_ACMPNE: + case Constants.GOTO: + case Constants.JSR: + case Constants.IFNULL: + case Constants.IFNONNULL: + methodVisitor.visitJumpInsn( + opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + methodVisitor.visitJumpInsn( + opcode - wideJumpOpcodeDelta, + labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + currentOffset += 5; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + { + // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO + // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:..., + // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and + // where designates the instruction just after the GOTO_W. + // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and + // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL. + opcode = + opcode < Constants.ASM_IFNULL + ? opcode - Constants.ASM_OPCODE_DELTA + : opcode - Constants.ASM_IFNULL_OPCODE_DELTA; + Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)]; + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + // Replace GOTO with GOTO_W and JSR with JSR_W. + methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target); } else { - for (int j = 0; j < context.attrs.length; ++j) { - if (context.attrs[j].type.equals(attrName)) { - Attribute attr = context.attrs[j].read(this, u + 8, - readInt(u + 4), c, codeStart - 8, labels); - if (attr != null) { - attr.next = attributes; - attributes = attr; - } - } - } - } - u += 6 + readInt(u + 4); - } - u += 2; - - // generates the first (implicit) stack map frame - if (FRAMES && stackMap != 0) { - /* - * for the first explicit frame the offset is not offset_delta + 1 - * but only offset_delta; setting the implicit frame offset to -1 - * allow the use of the "offset_delta + 1" rule in all cases - */ - frame = context; - frame.offset = -1; - frame.mode = 0; - frame.localCount = 0; - frame.localDiff = 0; - frame.stackCount = 0; - frame.local = new Object[maxLocals]; - frame.stack = new Object[maxStack]; - if (unzip) { - getImplicitFrame(context); - } - /* - * Finds labels for UNINITIALIZED frame types. Instead of decoding - * each element of the stack map table, we look for 3 consecutive - * bytes that "look like" an UNINITIALIZED type (tag 8, offset - * within code bounds, NEW instruction at this offset). We may find - * false positives (i.e. not real UNINITIALIZED types), but this - * should be rare, and the only consequence will be the creation of - * an unneeded label. This is better than creating a label for each - * NEW instruction, and faster than fully decoding the whole stack - * map table. - */ - for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) { - if (b[i] == 8) { // UNINITIALIZED FRAME TYPE - int v = readUnsignedShort(i + 1); - if (v >= 0 && v < codeLength) { - if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) { - readLabel(v, labels); - } - } - } + // Compute the "opposite" of opcode. This can be done by flipping the least + // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ + // (with a pre and post offset by 1). + opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1; + Label endif = createLabel(currentBytecodeOffset + 3, labels); + methodVisitor.visitJumpInsn(opcode, endif); + methodVisitor.visitJumpInsn(Constants.GOTO_W, target); + // endif designates the instruction just after GOTO_W, and is visited as part of the + // next instruction. Since it is a jump target, we need to insert a frame here. + insertFrame = true; } - } - - // visits the instructions - u = codeStart; - while (u < codeEnd) { - int offset = u - codeStart; - - // visits the label and line number for this offset, if any - Label l = labels[offset]; - if (l != null) { - mv.visitLabel(l); - if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) { - mv.visitLineNumber(l.line, l); - } - } - - // visits the frame for this offset, if any - while (FRAMES && frame != null - && (frame.offset == offset || frame.offset == -1)) { - // if there is a frame for this offset, makes the visitor visit - // it, and reads the next frame if there is one. - if (frame.offset != -1) { - if (!zip || unzip) { - mv.visitFrame(Opcodes.F_NEW, frame.localCount, - frame.local, frame.stackCount, frame.stack); - } else { - mv.visitFrame(frame.mode, frame.localDiff, frame.local, - frame.stackCount, frame.stack); - } - } - if (frameCount > 0) { - stackMap = readFrame(stackMap, zip, unzip, labels, frame); - --frameCount; - } else { - frame = null; - } - } - - // visits the instruction at this offset - int opcode = b[u] & 0xFF; - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - mv.visitInsn(opcode); - u += 1; - break; - case ClassWriter.IMPLVAR_INSN: - if (opcode > Opcodes.ISTORE) { - opcode -= 59; // ISTORE_0 - mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), - opcode & 0x3); - } else { - opcode -= 26; // ILOAD_0 - mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); - } - u += 1; - break; - case ClassWriter.LABEL_INSN: - mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]); - u += 3; - break; - case ClassWriter.LABELW_INSN: - mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]); - u += 5; - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4)); - u += 6; - } else { - mv.visitVarInsn(opcode, readUnsignedShort(u + 2)); - u += 4; - } - break; - case ClassWriter.TABL_INSN: { - // skips 0 to 3 padding bytes - u = u + 4 - (offset & 3); - // reads instruction - int label = offset + readInt(u); - int min = readInt(u + 4); - int max = readInt(u + 8); - Label[] table = new Label[max - min + 1]; - u += 12; - for (int i = 0; i < table.length; ++i) { - table[i] = labels[offset + readInt(u)]; - u += 4; - } - mv.visitTableSwitchInsn(min, max, labels[label], table); - break; + currentOffset += 3; + break; + } + case Constants.ASM_GOTO_W: + { + // Replace ASM_GOTO_W with GOTO_W. + methodVisitor.visitJumpInsn( + Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns + // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame + // here. + insertFrame = true; + currentOffset += 5; + break; + } + case Constants.WIDE: + opcode = classFileBuffer[currentOffset + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + methodVisitor.visitIincInsn( + readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4)); + currentOffset += 6; + } else { + methodVisitor.visitVarInsn(opcode, readUnsignedShort(currentOffset + 2)); + currentOffset += 4; + } + break; + case Constants.TABLESWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int low = readInt(currentOffset + 4); + int high = readInt(currentOffset + 8); + currentOffset += 12; + Label[] table = new Label[high - low + 1]; + for (int i = 0; i < table.length; ++i) { + table[i] = labels[currentBytecodeOffset + readInt(currentOffset)]; + currentOffset += 4; } - case ClassWriter.LOOK_INSN: { - // skips 0 to 3 padding bytes - u = u + 4 - (offset & 3); - // reads instruction - int label = offset + readInt(u); - int len = readInt(u + 4); - int[] keys = new int[len]; - Label[] values = new Label[len]; - u += 8; - for (int i = 0; i < len; ++i) { - keys[i] = readInt(u); - values[i] = labels[offset + readInt(u + 4)]; - u += 8; - } - mv.visitLookupSwitchInsn(labels[label], keys, values); - break; + methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table); + break; + } + case Constants.LOOKUPSWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int nPairs = readInt(currentOffset + 4); + currentOffset += 8; + int[] keys = new int[nPairs]; + Label[] values = new Label[nPairs]; + for (int i = 0; i < nPairs; ++i) { + keys[i] = readInt(currentOffset); + values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)]; + currentOffset += 8; } - case ClassWriter.VAR_INSN: - mv.visitVarInsn(opcode, b[u + 1] & 0xFF); - u += 2; - break; - case ClassWriter.SBYTE_INSN: - mv.visitIntInsn(opcode, b[u + 1]); - u += 2; - break; - case ClassWriter.SHORT_INSN: - mv.visitIntInsn(opcode, readShort(u + 1)); - u += 3; - break; - case ClassWriter.LDC_INSN: - mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c)); - u += 2; - break; - case ClassWriter.LDCW_INSN: - mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c)); - u += 3; - break; - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.ITFMETH_INSN: { - int cpIndex = items[readUnsignedShort(u + 1)]; - String iowner = readClass(cpIndex, c); - cpIndex = items[readUnsignedShort(cpIndex + 2)]; - String iname = readUTF8(cpIndex, c); - String idesc = readUTF8(cpIndex + 2, c); - if (opcode < Opcodes.INVOKEVIRTUAL) { - mv.visitFieldInsn(opcode, iowner, iname, idesc); - } else { - mv.visitMethodInsn(opcode, iowner, iname, idesc); - } - if (opcode == Opcodes.INVOKEINTERFACE) { - u += 5; - } else { - u += 3; - } - break; + methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values); + break; + } + case Constants.ILOAD: + case Constants.LLOAD: + case Constants.FLOAD: + case Constants.DLOAD: + case Constants.ALOAD: + case Constants.ISTORE: + case Constants.LSTORE: + case Constants.FSTORE: + case Constants.DSTORE: + case Constants.ASTORE: + case Constants.RET: + methodVisitor.visitVarInsn(opcode, classFileBuffer[currentOffset + 1] & 0xFF); + currentOffset += 2; + break; + case Constants.BIPUSH: + case Constants.NEWARRAY: + methodVisitor.visitIntInsn(opcode, classFileBuffer[currentOffset + 1]); + currentOffset += 2; + break; + case Constants.SIPUSH: + methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1)); + currentOffset += 3; + break; + case Constants.LDC: + methodVisitor.visitLdcInsn( + readConst(classFileBuffer[currentOffset + 1] & 0xFF, charBuffer)); + currentOffset += 2; + break; + case Constants.LDC_W: + case Constants.LDC2_W: + methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer)); + currentOffset += 3; + break; + case Constants.GETSTATIC: + case Constants.PUTSTATIC: + case Constants.GETFIELD: + case Constants.PUTFIELD: + case Constants.INVOKEVIRTUAL: + case Constants.INVOKESPECIAL: + case Constants.INVOKESTATIC: + case Constants.INVOKEINTERFACE: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String owner = readClass(cpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + if (opcode < Opcodes.INVOKEVIRTUAL) { + methodVisitor.visitFieldInsn(opcode, owner, name, descriptor); + } else { + boolean isInterface = + classFileBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } - case ClassWriter.INDYMETH_INSN: { - int cpIndex = items[readUnsignedShort(u + 1)]; - int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)]; - Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c); - int bsmArgCount = readUnsignedShort(bsmIndex + 2); - Object[] bsmArgs = new Object[bsmArgCount]; - bsmIndex += 4; - for (int i = 0; i < bsmArgCount; i++) { - bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c); - bsmIndex += 2; - } - cpIndex = items[readUnsignedShort(cpIndex + 2)]; - String iname = readUTF8(cpIndex, c); - String idesc = readUTF8(cpIndex + 2, c); - mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs); - u += 5; - break; + if (opcode == Opcodes.INVOKEINTERFACE) { + currentOffset += 5; + } else { + currentOffset += 3; } - case ClassWriter.TYPE_INSN: - mv.visitTypeInsn(opcode, readClass(u + 1, c)); - u += 3; - break; - case ClassWriter.IINC_INSN: - mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]); - u += 3; - break; - // case MANA_INSN: - default: - mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF); - u += 4; - break; + break; + } + case Constants.INVOKEDYNAMIC: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = + (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = + new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = + readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; } + methodVisitor.visitInvokeDynamicInsn( + name, descriptor, handle, bootstrapMethodArguments); + currentOffset += 5; + break; + } + case Constants.NEW: + case Constants.ANEWARRAY: + case Constants.CHECKCAST: + case Constants.INSTANCEOF: + methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer)); + currentOffset += 3; + break; + case Constants.IINC: + methodVisitor.visitIincInsn( + classFileBuffer[currentOffset + 1] & 0xFF, classFileBuffer[currentOffset + 2]); + currentOffset += 3; + break; + case Constants.MULTIANEWARRAY: + methodVisitor.visitMultiANewArrayInsn( + readClass(currentOffset + 1, charBuffer), classFileBuffer[currentOffset + 3] & 0xFF); + currentOffset += 4; + break; + default: + throw new AssertionError(); + } + + // Visit the runtime visible instruction annotations, if any. + while (visibleTypeAnnotationOffsets != null + && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length + && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); } - if (labels[codeLength] != null) { - mv.visitLabel(labels[codeLength]); + currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex); + } + + // Visit the runtime invisible instruction annotations, if any. + while (invisibleTypeAnnotationOffsets != null + && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length + && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); } + currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex); + } + } + if (labels[codeLength] != null) { + methodVisitor.visitLabel(labels[codeLength]); + } - // visits the local variable tables - if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) { - int[] typeTable = null; - if (varTypeTable != 0) { - u = varTypeTable + 2; - typeTable = new int[readUnsignedShort(varTypeTable) * 3]; - for (int i = typeTable.length; i > 0;) { - typeTable[--i] = u + 6; // signature - typeTable[--i] = readUnsignedShort(u + 8); // index - typeTable[--i] = readUnsignedShort(u); // start - u += 10; - } - } - u = varTable + 2; - for (int i = readUnsignedShort(varTable); i > 0; --i) { - int start = readUnsignedShort(u); - int length = readUnsignedShort(u + 2); - int index = readUnsignedShort(u + 8); - String vsignature = null; - if (typeTable != null) { - for (int j = 0; j < typeTable.length; j += 3) { - if (typeTable[j] == start && typeTable[j + 1] == index) { - vsignature = readUTF8(typeTable[j + 2], c); - break; - } - } - } - mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c), - vsignature, labels[start], labels[start + length], - index); - u += 10; + // Visit LocalVariableTable and LocalVariableTypeTable attributes. + if (localVariableTableOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { + // The (start_pc, index, signature_index) fields of each entry of the LocalVariableTypeTable. + int[] typeTable = null; + if (localVariableTypeTableOffset != 0) { + typeTable = new int[readUnsignedShort(localVariableTypeTableOffset) * 3]; + currentOffset = localVariableTypeTableOffset + 2; + int typeTableIndex = typeTable.length; + while (typeTableIndex > 0) { + // Store the offset of 'signature_index', and the value of 'index' and 'start_pc'. + typeTable[--typeTableIndex] = currentOffset + 6; + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset + 8); + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset); + currentOffset += 10; + } + } + int localVariableTableLength = readUnsignedShort(localVariableTableOffset); + currentOffset = localVariableTableOffset + 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + String name = readUTF8(currentOffset + 4, charBuffer); + String descriptor = readUTF8(currentOffset + 6, charBuffer); + int index = readUnsignedShort(currentOffset + 8); + currentOffset += 10; + String signature = null; + if (typeTable != null) { + for (int i = 0; i < typeTable.length; i += 3) { + if (typeTable[i] == startPc && typeTable[i + 1] == index) { + signature = readUTF8(typeTable[i + 2], charBuffer); + break; } + } } + methodVisitor.visitLocalVariable( + name, descriptor, signature, labels[startPc], labels[startPc + length], index); + } + } - // visits the code attributes - while (attributes != null) { - Attribute attr = attributes.next; - attributes.next = null; - mv.visitAttribute(attributes); - attributes = attr; + // Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute. + if (visibleTypeAnnotationOffsets != null) { + for (int i = 0; i < visibleTypeAnnotationOffsets.length; ++i) { + int targetType = readByte(visibleTypeAnnotationOffsets[i]); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, visibleTypeAnnotationOffsets[i]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ true), + currentOffset, + /* named = */ true, + charBuffer); } + } + } - // visits the max stack and max locals values - mv.visitMaxs(maxStack, maxLocals); - } - - /** - * Reads parameter annotations and makes the given visitor visit them. - * - * @param v - * start offset in {@link #b b} of the annotations to be read. - * @param desc - * the method descriptor. - * @param buf - * buffer to be used to call {@link #readUTF8 readUTF8}, - * {@link #readClass(int,char[]) readClass} or {@link #readConst - * readConst}. - * @param visible - * true if the annotations to be read are visible at - * runtime. - * @param mv - * the visitor that must visit the annotations. - */ - private void readParameterAnnotations(int v, final String desc, - final char[] buf, final boolean visible, final MethodVisitor mv) { - int i; - int n = b[v++] & 0xFF; - // workaround for a bug in javac (javac compiler generates a parameter - // annotation array whose size is equal to the number of parameters in - // the Java source file, while it should generate an array whose size is - // equal to the number of parameters in the method descriptor - which - // includes the synthetic parameters added by the compiler). This work- - // around supposes that the synthetic parameters are the first ones. - int synthetics = Type.getArgumentTypes(desc).length - n; - AnnotationVisitor av; - for (i = 0; i < synthetics; ++i) { - // virtual annotation to detect synthetic parameters in MethodWriter - av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false); - if (av != null) { - av.visitEnd(); - } - } - for (; i < n + synthetics; ++i) { - int j = readUnsignedShort(v); - v += 2; - for (; j > 0; --j) { - av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible); - v = readAnnotationValues(v + 2, buf, true, av); - } + // Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute. + if (invisibleTypeAnnotationOffsets != null) { + for (int i = 0; i < invisibleTypeAnnotationOffsets.length; ++i) { + int targetType = readByte(invisibleTypeAnnotationOffsets[i]); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, invisibleTypeAnnotationOffsets[i]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ false), + currentOffset, + /* named = */ true, + charBuffer); } + } } - /** - * Reads the values of an annotation and makes the given visitor visit them. - * - * @param v - * the start offset in {@link #b b} of the values to be read - * (including the unsigned short that gives the number of - * values). - * @param buf - * buffer to be used to call {@link #readUTF8 readUTF8}, - * {@link #readClass(int,char[]) readClass} or {@link #readConst - * readConst}. - * @param named - * if the annotation values are named or not. - * @param av - * the visitor that must visit the values. - * @return the end offset of the annotation values. - */ - private int readAnnotationValues(int v, final char[] buf, - final boolean named, final AnnotationVisitor av) { - int i = readUnsignedShort(v); - v += 2; - if (named) { - for (; i > 0; --i) { - v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av); - } - } else { - for (; i > 0; --i) { - v = readAnnotationValue(v, buf, null, av); - } - } - if (av != null) { - av.visitEnd(); - } - return v; - } - - /** - * Reads a value of an annotation and makes the given visitor visit it. - * - * @param v - * the start offset in {@link #b b} of the value to be read - * (not including the value name constant pool index). - * @param buf - * buffer to be used to call {@link #readUTF8 readUTF8}, - * {@link #readClass(int,char[]) readClass} or {@link #readConst - * readConst}. - * @param name - * the name of the value to be read. - * @param av - * the visitor that must visit the value. - * @return the end offset of the annotation value. - */ - private int readAnnotationValue(int v, final char[] buf, final String name, - final AnnotationVisitor av) { - int i; - if (av == null) { - switch (b[v] & 0xFF) { - case 'e': // enum_const_value - return v + 5; - case '@': // annotation_value - return readAnnotationValues(v + 3, buf, true, null); - case '[': // array_value - return readAnnotationValues(v + 1, buf, false, null); - default: - return v + 3; - } + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the max stack and max locals values. + methodVisitor.visitMaxs(maxStack, maxLocals); + } + + /** + * Returns the label corresponding to the given bytecode offset. The default implementation of + * this method creates a label for the given offset if it has not been already created. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a label already exists + * for bytecodeOffset this method must not create a new one. Otherwise it must store the new + * label in this array. + * @return a non null Label, which must be equal to labels[bytecodeOffset]. + */ + protected Label readLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + labels[bytecodeOffset] = new Label(); + } + return labels[bytecodeOffset]; + } + + /** + * Creates a label without the {@link Label#FLAG_DEBUG_ONLY} flag set, for the given bytecode + * offset. The label is created with a call to {@link #readLabel} and its {@link + * Label#FLAG_DEBUG_ONLY} flag is cleared. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + * @return a Label without the {@link Label#FLAG_DEBUG_ONLY} flag set. + */ + private Label createLabel(final int bytecodeOffset, final Label[] labels) { + Label label = readLabel(bytecodeOffset, labels); + label.flags &= ~Label.FLAG_DEBUG_ONLY; + return label; + } + + /** + * Creates a label with the {@link Label#FLAG_DEBUG_ONLY} flag set, if there is no already + * existing label for the given bytecode offset (otherwise does nothing). The label is created + * with a call to {@link #readLabel}. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + */ + private void createDebugLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + readLabel(bytecodeOffset, labels).flags |= Label.FLAG_DEBUG_ONLY; + } + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse annotations, type annotations and parameter annotations + // ---------------------------------------------------------------------------------------------- + + /** + * Parses a Runtime[In]VisibleTypeAnnotations attribute to find the offset of each type_annotation + * entry it contains, to find the corresponding labels, and to visit the try catch block + * annotations. + * + * @param methodVisitor the method visitor to be used to visit the try catch block annotations. + * @param context information about the class being parsed. + * @param runtimeTypeAnnotationsOffset the start offset of a Runtime[In]VisibleTypeAnnotations + * attribute, excluding the attribute_info's attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleTypeAnnotations attribute, + * false it is a RuntimeInvisibleTypeAnnotations attribute. + * @return the start offset of each entry of the Runtime[In]VisibleTypeAnnotations_attribute's + * 'annotations' array field. + */ + private int[] readTypeAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeTypeAnnotationsOffset, + final boolean visible) { + char[] charBuffer = context.charBuffer; + int currentOffset = runtimeTypeAnnotationsOffset; + // Read the num_annotations field and create an array to store the type_annotation offsets. + int[] typeAnnotationsOffsets = new int[readUnsignedShort(currentOffset)]; + currentOffset += 2; + // Parse the 'annotations' array field. + for (int i = 0; i < typeAnnotationsOffsets.length; ++i) { + typeAnnotationsOffsets[i] = currentOffset; + // Parse the type_annotation's target_type and the target_info fields. The size of the + // target_info field depends on the value of target_type. + int targetType = readInt(currentOffset); + switch (targetType >>> 24) { + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + // A localvar_target has a variable size, which depends on the value of their table_length + // field. It also references bytecode offsets, for which we need labels. + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + while (tableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + // Skip the index field (2 bytes). + currentOffset += 6; + createLabel(startPc, context.currentMethodLabels); + createLabel(startPc + length, context.currentMethodLabels); + } + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + currentOffset += 3; + break; + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + default: + // TypeReference type which can't be used in Code attribute, or which is unknown. + throw new IllegalArgumentException(); + } + // Parse the rest of the type_annotation structure, starting with the target_path structure + // (whose size depends on its path_length field). + int pathLength = readByte(currentOffset); + if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) { + // Parse the target_path structure and create a corresponding TypePath. + TypePath path = pathLength == 0 ? null : new TypePath(b, currentOffset); + currentOffset += 1 + 2 * pathLength; + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitTryCatchAnnotation( + targetType & 0xFFFFFF00, path, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } else { + // We don't want to visit the other target_type annotations, so we just skip them (which + // requires some parsing because the element_value_pairs array has a variable size). First, + // skip the target_path structure: + currentOffset += 3 + 2 * pathLength; + // Then skip the num_element_value_pairs and element_value_pairs fields (by reading them + // with a null AnnotationVisitor). + currentOffset = + readElementValues( + /* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer); + } + } + return typeAnnotationsOffsets; + } + + /** + * Returns the bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or + * -1 if there is no such type_annotation of if it does not have a bytecode offset. + * + * @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a + * Runtime[In]VisibleTypeAnnotations attribute, or null. + * @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets. + * @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1 + * if there is no such type_annotation of if it does not have a bytecode offset. + */ + private int getTypeAnnotationBytecodeOffset( + final int[] typeAnnotationOffsets, final int typeAnnotationIndex) { + if (typeAnnotationOffsets == null + || typeAnnotationIndex >= typeAnnotationOffsets.length + || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) { + return -1; + } + return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1); + } + + /** + * Parses the header of a JVMS type_annotation structure to extract its target_type, target_info + * and target_path (the result is stored in the given context), and returns the start offset of + * the rest of the type_annotation structure. + * + * @param context information about the class being parsed. This is where the extracted + * target_type and target_path must be stored. + * @param typeAnnotationOffset the start offset of a type_annotation structure. + * @return the start offset of the rest of the type_annotation structure. + */ + private int readTypeAnnotationTarget(final Context context, final int typeAnnotationOffset) { + int currentOffset = typeAnnotationOffset; + // Parse and store the target_type structure. + int targetType = readInt(typeAnnotationOffset); + switch (targetType >>> 24) { + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + targetType &= 0xFFFF0000; + currentOffset += 2; + break; + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + targetType &= 0xFF000000; + currentOffset += 1; + break; + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + targetType &= 0xFF000000; + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + context.currentLocalVariableAnnotationRangeStarts = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeEnds = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeIndices = new int[tableLength]; + for (int i = 0; i < tableLength; ++i) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + int index = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + context.currentLocalVariableAnnotationRangeStarts[i] = + createLabel(startPc, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeEnds[i] = + createLabel(startPc + length, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeIndices[i] = index; } - switch (b[v++] & 0xFF) { - case 'I': // pointer to CONSTANT_Integer - case 'J': // pointer to CONSTANT_Long - case 'F': // pointer to CONSTANT_Float - case 'D': // pointer to CONSTANT_Double - av.visit(name, readConst(readUnsignedShort(v), buf)); - v += 2; - break; - case 'B': // pointer to CONSTANT_Byte - av.visit(name, - new Byte((byte) readInt(items[readUnsignedShort(v)]))); - v += 2; - break; - case 'Z': // pointer to CONSTANT_Boolean - av.visit(name, - readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE - : Boolean.TRUE); - v += 2; - break; - case 'S': // pointer to CONSTANT_Short - av.visit(name, new Short( - (short) readInt(items[readUnsignedShort(v)]))); - v += 2; - break; - case 'C': // pointer to CONSTANT_Char - av.visit(name, new Character( - (char) readInt(items[readUnsignedShort(v)]))); - v += 2; - break; - case 's': // pointer to CONSTANT_Utf8 - av.visit(name, readUTF8(v, buf)); - v += 2; - break; + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + targetType &= 0xFF0000FF; + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + targetType &= 0xFFFFFF00; + currentOffset += 3; + break; + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + targetType &= 0xFF000000; + currentOffset += 3; + break; + default: + throw new IllegalArgumentException(); + } + context.currentTypeAnnotationTarget = targetType; + // Parse and store the target_path structure. + int pathLength = readByte(currentOffset); + context.currentTypeAnnotationTargetPath = + pathLength == 0 ? null : new TypePath(b, currentOffset); + // Return the start offset of the rest of the type_annotation structure. + return currentOffset + 1 + 2 * pathLength; + } + + /** + * Reads a Runtime[In]VisibleParameterAnnotations attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the parameter annotations. + * @param context information about the class being parsed. + * @param runtimeParameterAnnotationsOffset the start offset of a + * Runtime[In]VisibleParameterAnnotations attribute, excluding the attribute_info's + * attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleParameterAnnotations + * attribute, false it is a RuntimeInvisibleParameterAnnotations attribute. + */ + private void readParameterAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeParameterAnnotationsOffset, + final boolean visible) { + int currentOffset = runtimeParameterAnnotationsOffset; + int numParameters = b[currentOffset++] & 0xFF; + methodVisitor.visitAnnotableParameterCount(numParameters, visible); + char[] charBuffer = context.charBuffer; + for (int i = 0; i < numParameters; ++i) { + int numAnnotations = readUnsignedShort(currentOffset); + currentOffset += 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + /** + * Reads the element values of a JVMS 'annotation' structure and makes the given visitor visit + * them. This method can also be used to read the values of the JVMS 'array_value' field of an + * annotation's 'element_value'. + * + * @param annotationVisitor the visitor that must visit the values. + * @param annotationOffset the start offset of an 'annotation' structure (excluding its type_index + * field) or of an 'array_value' structure. + * @param named if the annotation values are named or not. This should be true to parse the values + * of a JVMS 'annotation' structure, and false to parse the JVMS 'array_value' of an + * annotation's element_value. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'annotation' or 'array_value' structure. + */ + private int readElementValues( + final AnnotationVisitor annotationVisitor, + final int annotationOffset, + final boolean named, + final char[] charBuffer) { + int currentOffset = annotationOffset; + // Read the num_element_value_pairs field (or num_values field for an array_value). + int numElementValuePairs = readUnsignedShort(currentOffset); + currentOffset += 2; + if (named) { + // Parse the element_value_pairs array. + while (numElementValuePairs-- > 0) { + String elementName = readUTF8(currentOffset, charBuffer); + currentOffset = + readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer); + } + } else { + // Parse the array_value array. + while (numElementValuePairs-- > 0) { + currentOffset = + readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer); + } + } + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + return currentOffset; + } + + /** + * Reads a JVMS 'element_value' structure and makes the given visitor visit it. + * + * @param annotationVisitor the visitor that must visit the element_value structure. + * @param elementValueOffset the start offset in {@link #b} of the element_value structure to be + * read. + * @param elementName the name of the element_value structure to be read, or null. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'element_value' structure. + */ + private int readElementValue( + final AnnotationVisitor annotationVisitor, + final int elementValueOffset, + final String elementName, + final char[] charBuffer) { + int currentOffset = elementValueOffset; + if (annotationVisitor == null) { + switch (b[currentOffset] & 0xFF) { case 'e': // enum_const_value - av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf)); - v += 4; - break; - case 'c': // class_info - av.visit(name, Type.getType(readUTF8(v, buf))); - v += 2; - break; + return currentOffset + 5; case '@': // annotation_value - v = readAnnotationValues(v + 2, buf, true, - av.visitAnnotation(name, readUTF8(v, buf))); - break; + return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer); case '[': // array_value - int size = readUnsignedShort(v); - v += 2; - if (size == 0) { - return readAnnotationValues(v - 2, buf, false, - av.visitArray(name)); - } - switch (this.b[v++] & 0xFF) { - case 'B': - byte[] bv = new byte[size]; - for (i = 0; i < size; i++) { - bv[i] = (byte) readInt(items[readUnsignedShort(v)]); - v += 3; - } - av.visit(name, bv); - --v; - break; - case 'Z': - boolean[] zv = new boolean[size]; - for (i = 0; i < size; i++) { - zv[i] = readInt(items[readUnsignedShort(v)]) != 0; - v += 3; - } - av.visit(name, zv); - --v; - break; - case 'S': - short[] sv = new short[size]; - for (i = 0; i < size; i++) { - sv[i] = (short) readInt(items[readUnsignedShort(v)]); - v += 3; - } - av.visit(name, sv); - --v; - break; - case 'C': - char[] cv = new char[size]; - for (i = 0; i < size; i++) { - cv[i] = (char) readInt(items[readUnsignedShort(v)]); - v += 3; - } - av.visit(name, cv); - --v; - break; - case 'I': - int[] iv = new int[size]; - for (i = 0; i < size; i++) { - iv[i] = readInt(items[readUnsignedShort(v)]); - v += 3; - } - av.visit(name, iv); - --v; - break; - case 'J': - long[] lv = new long[size]; - for (i = 0; i < size; i++) { - lv[i] = readLong(items[readUnsignedShort(v)]); - v += 3; - } - av.visit(name, lv); - --v; - break; - case 'F': - float[] fv = new float[size]; - for (i = 0; i < size; i++) { - fv[i] = Float - .intBitsToFloat(readInt(items[readUnsignedShort(v)])); - v += 3; - } - av.visit(name, fv); - --v; - break; - case 'D': - double[] dv = new double[size]; - for (i = 0; i < size; i++) { - dv[i] = Double - .longBitsToDouble(readLong(items[readUnsignedShort(v)])); - v += 3; - } - av.visit(name, dv); - --v; - break; - default: - v = readAnnotationValues(v - 3, buf, false, av.visitArray(name)); - } - } - return v; - } - - /** - * Computes the implicit frame of the method currently being parsed (as - * defined in the given {@link Context}) and stores it in the given context. - * - * @param frame - * information about the class being parsed. - */ - private void getImplicitFrame(final Context frame) { - String desc = frame.desc; - Object[] locals = frame.local; - int local = 0; - if ((frame.access & Opcodes.ACC_STATIC) == 0) { - if ("".equals(frame.name)) { - locals[local++] = Opcodes.UNINITIALIZED_THIS; - } else { - locals[local++] = readClass(header + 2, frame.buffer); - } + return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer); + default: + return currentOffset + 3; + } + } + switch (b[currentOffset++] & 0xFF) { + case 'B': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'C': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'D': // const_value_index, CONSTANT_Double + case 'F': // const_value_index, CONSTANT_Float + case 'I': // const_value_index, CONSTANT_Integer + case 'J': // const_value_index, CONSTANT_Long + annotationVisitor.visit( + elementName, readConst(readUnsignedShort(currentOffset), charBuffer)); + currentOffset += 2; + break; + case 'S': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + + case 'Z': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, + readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0 + ? Boolean.FALSE + : Boolean.TRUE); + currentOffset += 2; + break; + case 's': // const_value_index, CONSTANT_Utf8 + annotationVisitor.visit(elementName, readUTF8(currentOffset, charBuffer)); + currentOffset += 2; + break; + case 'e': // enum_const_value + annotationVisitor.visitEnum( + elementName, + readUTF8(currentOffset, charBuffer), + readUTF8(currentOffset + 2, charBuffer)); + currentOffset += 4; + break; + case 'c': // class_info + annotationVisitor.visit(elementName, Type.getType(readUTF8(currentOffset, charBuffer))); + currentOffset += 2; + break; + case '@': // annotation_value + currentOffset = + readElementValues( + annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)), + currentOffset + 2, + true, + charBuffer); + break; + case '[': // array_value + int numValues = readUnsignedShort(currentOffset); + currentOffset += 2; + if (numValues == 0) { + return readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); } - int i = 1; - loop: while (true) { - int j = i; - switch (desc.charAt(i++)) { - case 'Z': - case 'C': - case 'B': - case 'S': - case 'I': - locals[local++] = Opcodes.INTEGER; - break; - case 'F': - locals[local++] = Opcodes.FLOAT; - break; - case 'J': - locals[local++] = Opcodes.LONG; - break; - case 'D': - locals[local++] = Opcodes.DOUBLE; - break; - case '[': - while (desc.charAt(i) == '[') { - ++i; - } - if (desc.charAt(i) == 'L') { - ++i; - while (desc.charAt(i) != ';') { - ++i; - } - } - locals[local++] = desc.substring(j, ++i); - break; - case 'L': - while (desc.charAt(i) != ';') { - ++i; - } - locals[local++] = desc.substring(j + 1, i++); - break; - default: - break loop; + switch (b[currentOffset] & 0xFF) { + case 'B': + byte[] byteValues = new byte[numValues]; + for (int i = 0; i < numValues; i++) { + byteValues[i] = (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; } - } - frame.localCount = local; - } - - /** - * Reads a stack map frame and stores the result in the given - * {@link Context} object. - * - * @param stackMap - * the start offset of a stack map frame in the class file. - * @param zip - * if the stack map frame at stackMap is compressed or not. - * @param unzip - * if the stack map frame must be uncompressed. - * @param labels - * the labels of the method currently being parsed, indexed by - * their offset. A new label for the parsed stack map frame is - * stored in this array if it does not already exist. - * @param frame - * where the parsed stack map frame must be stored. - * @return the offset of the first byte following the parsed frame. - */ - private int readFrame(int stackMap, boolean zip, boolean unzip, - Label[] labels, Context frame) { - char[] c = frame.buffer; - int tag; - int delta; - if (zip) { - tag = b[stackMap++] & 0xFF; - } else { - tag = MethodWriter.FULL_FRAME; - frame.offset = -1; - } - frame.localDiff = 0; - if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) { - delta = tag; - frame.mode = Opcodes.F_SAME; - frame.stackCount = 0; - } else if (tag < MethodWriter.RESERVED) { - delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME; - stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); - frame.mode = Opcodes.F_SAME1; - frame.stackCount = 1; - } else { - delta = readUnsignedShort(stackMap); - stackMap += 2; - if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { - stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); - frame.mode = Opcodes.F_SAME1; - frame.stackCount = 1; - } else if (tag >= MethodWriter.CHOP_FRAME - && tag < MethodWriter.SAME_FRAME_EXTENDED) { - frame.mode = Opcodes.F_CHOP; - frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag; - frame.localCount -= frame.localDiff; - frame.stackCount = 0; - } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) { - frame.mode = Opcodes.F_SAME; - frame.stackCount = 0; - } else if (tag < MethodWriter.FULL_FRAME) { - int local = unzip ? frame.localCount : 0; - for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) { - stackMap = readFrameType(frame.local, local++, stackMap, c, - labels); - } - frame.mode = Opcodes.F_APPEND; - frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED; - frame.localCount += frame.localDiff; - frame.stackCount = 0; - } else { // if (tag == FULL_FRAME) { - frame.mode = Opcodes.F_FULL; - int n = readUnsignedShort(stackMap); - stackMap += 2; - frame.localDiff = n; - frame.localCount = n; - for (int local = 0; n > 0; n--) { - stackMap = readFrameType(frame.local, local++, stackMap, c, - labels); - } - n = readUnsignedShort(stackMap); - stackMap += 2; - frame.stackCount = n; - for (int stack = 0; n > 0; n--) { - stackMap = readFrameType(frame.stack, stack++, stackMap, c, - labels); - } + annotationVisitor.visit(elementName, byteValues); + break; + case 'Z': + boolean[] booleanValues = new boolean[numValues]; + for (int i = 0; i < numValues; i++) { + booleanValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]) != 0; + currentOffset += 3; } - } - frame.offset += delta + 1; - readLabel(frame.offset, labels); - return stackMap; - } - - /** - * Reads a stack map frame type and stores it at the given index in the - * given array. - * - * @param frame - * the array where the parsed type must be stored. - * @param index - * the index in 'frame' where the parsed type must be stored. - * @param v - * the start offset of the stack map frame type to read. - * @param buf - * a buffer to read strings. - * @param labels - * the labels of the method currently being parsed, indexed by - * their offset. If the parsed type is an Uninitialized type, a - * new label for the corresponding NEW instruction is stored in - * this array if it does not already exist. - * @return the offset of the first byte after the parsed type. - */ - private int readFrameType(final Object[] frame, final int index, int v, - final char[] buf, final Label[] labels) { - int type = b[v++] & 0xFF; - switch (type) { - case 0: - frame[index] = Opcodes.TOP; + annotationVisitor.visit(elementName, booleanValues); break; - case 1: - frame[index] = Opcodes.INTEGER; + case 'S': + short[] shortValues = new short[numValues]; + for (int i = 0; i < numValues; i++) { + shortValues[i] = (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, shortValues); break; - case 2: - frame[index] = Opcodes.FLOAT; + case 'C': + char[] charValues = new char[numValues]; + for (int i = 0; i < numValues; i++) { + charValues[i] = (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, charValues); break; - case 3: - frame[index] = Opcodes.DOUBLE; + case 'I': + int[] intValues = new int[numValues]; + for (int i = 0; i < numValues; i++) { + intValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, intValues); break; - case 4: - frame[index] = Opcodes.LONG; + case 'J': + long[] longValues = new long[numValues]; + for (int i = 0; i < numValues; i++) { + longValues[i] = readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, longValues); break; - case 5: - frame[index] = Opcodes.NULL; + case 'F': + float[] floatValues = new float[numValues]; + for (int i = 0; i < numValues; i++) { + floatValues[i] = + Float.intBitsToFloat( + readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, floatValues); break; - case 6: - frame[index] = Opcodes.UNINITIALIZED_THIS; + case 'D': + double[] doubleValues = new double[numValues]; + for (int i = 0; i < numValues; i++) { + doubleValues[i] = + Double.longBitsToDouble( + readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, doubleValues); break; - case 7: // Object - frame[index] = readClass(v, buf); - v += 2; + default: + currentOffset = + readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); break; - default: // Uninitialized - frame[index] = readLabel(readUnsignedShort(v), labels); - v += 2; - } - return v; - } - - /** - * Returns the label corresponding to the given offset. The default - * implementation of this method creates a label for the given offset if it - * has not been already created. - * - * @param offset - * a bytecode offset in a method. - * @param labels - * the already created labels, indexed by their offset. If a - * label already exists for offset this method must not create a - * new one. Otherwise it must store the new label in this array. - * @return a non null Label, which must be equal to labels[offset]. - */ - protected Label readLabel(int offset, Label[] labels) { - if (labels[offset] == null) { - labels[offset] = new Label(); - } - return labels[offset]; - } - - /** - * Returns the start index of the attribute_info structure of this class. - * - * @return the start index of the attribute_info structure of this class. - */ - private int getAttributes() { - // skips the header - int u = header + 8 + readUnsignedShort(header + 6) * 2; - // skips fields and methods - for (int i = readUnsignedShort(u); i > 0; --i) { - for (int j = readUnsignedShort(u + 8); j > 0; --j) { - u += 6 + readInt(u + 12); - } - u += 8; } - u += 2; - for (int i = readUnsignedShort(u); i > 0; --i) { - for (int j = readUnsignedShort(u + 8); j > 0; --j) { - u += 6 + readInt(u + 12); - } - u += 8; - } - // the attribute_info structure starts just after the methods - return u + 2; - } - - /** - * Reads an attribute in {@link #b b}. - * - * @param attrs - * prototypes of the attributes that must be parsed during the - * visit of the class. Any attribute whose type is not equal to - * the type of one the prototypes is ignored (i.e. an empty - * {@link Attribute} instance is returned). - * @param type - * the type of the attribute. - * @param off - * index of the first byte of the attribute's content in - * {@link #b b}. The 6 attribute header bytes, containing the - * type and the length of the attribute, are not taken into - * account here (they have already been read). - * @param len - * the length of the attribute's content. - * @param buf - * buffer to be used to call {@link #readUTF8 readUTF8}, - * {@link #readClass(int,char[]) readClass} or {@link #readConst - * readConst}. - * @param codeOff - * index of the first byte of code's attribute content in - * {@link #b b}, or -1 if the attribute to be read is not a code - * attribute. The 6 attribute header bytes, containing the type - * and the length of the attribute, are not taken into account - * here. - * @param labels - * the labels of the method's code, or null if the - * attribute to be read is not a code attribute. - * @return the attribute that has been read, or null to skip this - * attribute. - */ - private Attribute readAttribute(final Attribute[] attrs, final String type, - final int off, final int len, final char[] buf, final int codeOff, - final Label[] labels) { - for (int i = 0; i < attrs.length; ++i) { - if (attrs[i].type.equals(type)) { - return attrs[i].read(this, off, len, buf, codeOff, labels); + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse stack map frames + // ---------------------------------------------------------------------------------------------- + + /** + * Computes the implicit frame of the method currently being parsed (as defined in the given + * {@link Context}) and stores it in the given context. + * + * @param context information about the class being parsed. + */ + private void computeImplicitFrame(final Context context) { + String methodDescriptor = context.currentMethodDescriptor; + Object[] locals = context.currentFrameLocalTypes; + int nLocal = 0; + if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) { + if ("".equals(context.currentMethodName)) { + locals[nLocal++] = Opcodes.UNINITIALIZED_THIS; + } else { + locals[nLocal++] = readClass(header + 2, context.charBuffer); + } + } + // Parse the method descriptor, one argument type descriptor at each iteration. Start by + // skipping the first method descriptor character, which is always '('. + int currentMethodDescritorOffset = 1; + while (true) { + int currentArgumentDescriptorStartOffset = currentMethodDescritorOffset; + switch (methodDescriptor.charAt(currentMethodDescritorOffset++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + locals[nLocal++] = Opcodes.INTEGER; + break; + case 'F': + locals[nLocal++] = Opcodes.FLOAT; + break; + case 'J': + locals[nLocal++] = Opcodes.LONG; + break; + case 'D': + locals[nLocal++] = Opcodes.DOUBLE; + break; + case '[': + while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') { + ++currentMethodDescritorOffset; + } + if (methodDescriptor.charAt(currentMethodDescritorOffset) == 'L') { + ++currentMethodDescritorOffset; + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; } + } + locals[nLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset); + break; + case 'L': + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; + } + locals[nLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++); + break; + default: + context.currentFrameLocalCount = nLocal; + return; + } + } + } + + /** + * Reads a JVMS 'stack_map_frame' structure and stores the result in the given {@link Context} + * object. This method can also be used to read a full_frame structure, excluding its frame_type + * field (this is used to parse the legacy StackMap attributes). + * + * @param stackMapFrameOffset the start offset in {@link #b} of the stack_map_frame_value + * structure to be read, or the start offset of a full_frame structure (excluding its + * frame_type field). + * @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame' + * structure without its frame_type field. + * @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}. + * @param context where the parsed stack map frame must be stored. + * @return the end offset of the JVMS 'stack_map_frame' or 'full_frame' structure. + */ + private int readStackMapFrame( + final int stackMapFrameOffset, + final boolean compressed, + final boolean expand, + final Context context) { + int currentOffset = stackMapFrameOffset; + final char[] charBuffer = context.charBuffer; + final Label[] labels = context.currentMethodLabels; + int frameType; + if (compressed) { + // Read the frame_type field. + frameType = b[currentOffset++] & 0xFF; + } else { + frameType = Frame.FULL_FRAME; + context.currentFrameOffset = -1; + } + int offsetDelta; + context.currentFrameLocalCountDelta = 0; + if (frameType < Frame.SAME_LOCALS_1_STACK_ITEM_FRAME) { + offsetDelta = frameType; + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.RESERVED) { + offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME; + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + offsetDelta = readUnsignedShort(currentOffset); + currentOffset += 2; + if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.CHOP_FRAME && frameType < Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_CHOP; + context.currentFrameLocalCountDelta = Frame.SAME_FRAME_EXTENDED - frameType; + context.currentFrameLocalCount -= context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else if (frameType == Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.FULL_FRAME) { + int local = expand ? context.currentFrameLocalCount : 0; + for (int k = frameType - Frame.SAME_FRAME_EXTENDED; k > 0; k--) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels); } - return new Attribute(type).read(this, off, len, null, -1, null); - } - - // ------------------------------------------------------------------------ - // Utility methods: low level parsing - // ------------------------------------------------------------------------ - - /** - * Returns the number of constant pool items in {@link #b b}. - * - * @return the number of constant pool items in {@link #b b}. - */ - public int getItemCount() { - return items.length; - } - - /** - * Returns the start index of the constant pool item in {@link #b b}, plus - * one. This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param item - * the index a constant pool item. - * @return the start index of the constant pool item in {@link #b b}, plus - * one. - */ - public int getItem(final int item) { - return items[item]; - } - - /** - * Returns the maximum length of the strings contained in the constant pool - * of the class. - * - * @return the maximum length of the strings contained in the constant pool - * of the class. - */ - public int getMaxStringLength() { - return maxStringLength; - } - - /** - * Reads a byte value in {@link #b b}. This method is intended for - * {@link Attribute} sub classes, and is normally not needed by class - * generators or adapters. - * - * @param index - * the start index of the value to be read in {@link #b b}. - * @return the read value. - */ - public int readByte(final int index) { - return b[index] & 0xFF; - } - - /** - * Reads an unsigned short value in {@link #b b}. This method is intended - * for {@link Attribute} sub classes, and is normally not needed by class - * generators or adapters. - * - * @param index - * the start index of the value to be read in {@link #b b}. - * @return the read value. - */ - public int readUnsignedShort(final int index) { - byte[] b = this.b; - return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); - } - - /** - * Reads a signed short value in {@link #b b}. This method is intended - * for {@link Attribute} sub classes, and is normally not needed by class - * generators or adapters. - * - * @param index - * the start index of the value to be read in {@link #b b}. - * @return the read value. - */ - public short readShort(final int index) { - byte[] b = this.b; - return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); - } - - /** - * Reads a signed int value in {@link #b b}. This method is intended for - * {@link Attribute} sub classes, and is normally not needed by class - * generators or adapters. - * - * @param index - * the start index of the value to be read in {@link #b b}. - * @return the read value. - */ - public int readInt(final int index) { - byte[] b = this.b; - return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) - | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); - } - - /** - * Reads a signed long value in {@link #b b}. This method is intended for - * {@link Attribute} sub classes, and is normally not needed by class - * generators or adapters. - * - * @param index - * the start index of the value to be read in {@link #b b}. - * @return the read value. - */ - public long readLong(final int index) { - long l1 = readInt(index); - long l0 = readInt(index + 4) & 0xFFFFFFFFL; - return (l1 << 32) | l0; - } - - /** - * Reads an UTF8 string constant pool item in {@link #b b}. This method - * is intended for {@link Attribute} sub classes, and is normally not needed - * by class generators or adapters. - * - * @param index - * the start index of an unsigned short value in {@link #b b}, - * whose value is the index of an UTF8 constant pool item. - * @param buf - * buffer to be used to read the item. This buffer must be - * sufficiently large. It is not automatically resized. - * @return the String corresponding to the specified UTF8 item. - */ - public String readUTF8(int index, final char[] buf) { - int item = readUnsignedShort(index); - if (index == 0 || item == 0) { - return null; - } - String s = strings[item]; - if (s != null) { - return s; - } - index = items[item]; - return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf); - } - - /** - * Reads UTF8 string in {@link #b b}. - * - * @param index - * start offset of the UTF8 string to be read. - * @param utfLen - * length of the UTF8 string to be read. - * @param buf - * buffer to be used to read the string. This buffer must be - * sufficiently large. It is not automatically resized. - * @return the String corresponding to the specified UTF8 string. - */ - private String readUTF(int index, final int utfLen, final char[] buf) { - int endIndex = index + utfLen; - byte[] b = this.b; - int strLen = 0; - int c; - int st = 0; - char cc = 0; - while (index < endIndex) { - c = b[index++]; - switch (st) { - case 0: - c = c & 0xFF; - if (c < 0x80) { // 0xxxxxxx - buf[strLen++] = (char) c; - } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx - cc = (char) (c & 0x1F); - st = 1; - } else { // 1110 xxxx 10xx xxxx 10xx xxxx - cc = (char) (c & 0x0F); - st = 2; - } - break; - - case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char - buf[strLen++] = (char) ((cc << 6) | (c & 0x3F)); - st = 0; - break; - - case 2: // byte 2 of 3-byte char - cc = (char) ((cc << 6) | (c & 0x3F)); - st = 1; - break; - } + context.currentFrameType = Opcodes.F_APPEND; + context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED; + context.currentFrameLocalCount += context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else { + final int numberOfLocals = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameType = Opcodes.F_FULL; + context.currentFrameLocalCountDelta = numberOfLocals; + context.currentFrameLocalCount = numberOfLocals; + for (int local = 0; local < numberOfLocals; ++local) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels); } - return new String(buf, 0, strLen); - } - - /** - * Reads a class constant pool item in {@link #b b}. This method is - * intended for {@link Attribute} sub classes, and is normally not needed by - * class generators or adapters. - * - * @param index - * the start index of an unsigned short value in {@link #b b}, - * whose value is the index of a class constant pool item. - * @param buf - * buffer to be used to read the item. This buffer must be - * sufficiently large. It is not automatically resized. - * @return the String corresponding to the specified class item. - */ - public String readClass(final int index, final char[] buf) { - // computes the start index of the CONSTANT_Class item in b - // and reads the CONSTANT_Utf8 item designated by - // the first two bytes of this CONSTANT_Class item - return readUTF8(items[readUnsignedShort(index)], buf); - } - - /** - * Reads a numeric or string constant pool item in {@link #b b}. This - * method is intended for {@link Attribute} sub classes, and is normally not - * needed by class generators or adapters. - * - * @param item - * the index of a constant pool item. - * @param buf - * buffer to be used to read the item. This buffer must be - * sufficiently large. It is not automatically resized. - * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, - * {@link String}, {@link Type} or {@link Handle} corresponding to - * the given constant pool item. - */ - public Object readConst(final int item, final char[] buf) { - int index = items[item]; - switch (b[index - 1]) { - case ClassWriter.INT: - return new Integer(readInt(index)); - case ClassWriter.FLOAT: - return new Float(Float.intBitsToFloat(readInt(index))); - case ClassWriter.LONG: - return new Long(readLong(index)); - case ClassWriter.DOUBLE: - return new Double(Double.longBitsToDouble(readLong(index))); - case ClassWriter.CLASS: - return Type.getObjectType(readUTF8(index, buf)); - case ClassWriter.STR: - return readUTF8(index, buf); - case ClassWriter.MTYPE: - return Type.getMethodType(readUTF8(index, buf)); - default: // case ClassWriter.HANDLE_BASE + [1..9]: - int tag = readByte(index); - int[] items = this.items; - int cpIndex = items[readUnsignedShort(index + 1)]; - String owner = readClass(cpIndex, buf); - cpIndex = items[readUnsignedShort(cpIndex + 2)]; - String name = readUTF8(cpIndex, buf); - String desc = readUTF8(cpIndex + 2, buf); - return new Handle(tag, owner, name, desc); + final int numberOfStackItems = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameStackCount = numberOfStackItems; + for (int stack = 0; stack < numberOfStackItems; ++stack) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels); } + } + } else { + throw new IllegalArgumentException(); + } + context.currentFrameOffset += offsetDelta + 1; + createLabel(context.currentFrameOffset, labels); + return currentOffset; + } + + /** + * Reads a JVMS 'verification_type_info' structure and stores it at the given index in the given + * array. + * + * @param verificationTypeInfoOffset the start offset of the 'verification_type_info' structure to + * read. + * @param frame the array where the parsed type must be stored. + * @param index the index in 'frame' where the parsed type must be stored. + * @param charBuffer the buffer used to read strings in the constant pool. + * @param labels the labels of the method currently being parsed, indexed by their offset. If the + * parsed type is an ITEM_Uninitialized, a new label for the corresponding NEW instruction is + * stored in this array if it does not already exist. + * @return the end offset of the JVMS 'verification_type_info' structure. + */ + private int readVerificationTypeInfo( + final int verificationTypeInfoOffset, + final Object[] frame, + final int index, + final char[] charBuffer, + final Label[] labels) { + int currentOffset = verificationTypeInfoOffset; + int tag = b[currentOffset++] & 0xFF; + switch (tag) { + case Frame.ITEM_TOP: + frame[index] = Opcodes.TOP; + break; + case Frame.ITEM_INTEGER: + frame[index] = Opcodes.INTEGER; + break; + case Frame.ITEM_FLOAT: + frame[index] = Opcodes.FLOAT; + break; + case Frame.ITEM_DOUBLE: + frame[index] = Opcodes.DOUBLE; + break; + case Frame.ITEM_LONG: + frame[index] = Opcodes.LONG; + break; + case Frame.ITEM_NULL: + frame[index] = Opcodes.NULL; + break; + case Frame.ITEM_UNINITIALIZED_THIS: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case Frame.ITEM_OBJECT: + frame[index] = readClass(currentOffset, charBuffer); + currentOffset += 2; + break; + case Frame.ITEM_UNINITIALIZED: + frame[index] = createLabel(readUnsignedShort(currentOffset), labels); + currentOffset += 2; + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse attributes + // ---------------------------------------------------------------------------------------------- + + /** @return the offset in {@link #b} of the first ClassFile's 'attributes' array field entry. */ + final int getFirstAttributeOffset() { + // Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes + // each), as well as the interfaces array field (2 bytes per interface). + int currentOffset = header + 8 + readUnsignedShort(header + 6) * 2; + + // Read the fields_count field. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + // Skip the 'fields' array field. + while (fieldsCount-- > 0) { + // Invariant: currentOffset is the offset of a field_info structure. + // Skip the access_flags, name_index and descriptor_index fields (2 bytes each), and read the + // attributes_count field. + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + // Skip the 'attributes' array field. + while (attributesCount-- > 0) { + // Invariant: currentOffset is the offset of an attribute_info structure. + // Read the attribute_length field (2 bytes after the start of the attribute_info) and skip + // this many bytes, plus 6 for the attribute_name_index and attribute_length fields + // (yielding the total size of the attribute_info structure). + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the methods_count and 'methods' fields, using the same method as above. + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + while (attributesCount-- > 0) { + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the ClassFile's attributes_count field. + return currentOffset + 2; + } + + /** + * Reads a non standard JVMS 'attribute' structure in {@link #b}. + * + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. + * @param type the type of the attribute. + * @param offset the start offset of the JVMS 'attribute' structure in {@link #b}. The 6 attribute + * header bytes (attribute_name_index and attribute_length) are not taken into account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to read strings in the constant pool. + * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link #b}, or + * -1 if the attribute to be read is not a code attribute. The 6 attribute header bytes + * (attribute_name_index and attribute_length) are not taken into account here. + * @param labels the labels of the method's code, or null if the attribute to be read is + * not a code attribute. + * @return the attribute that has been read. + */ + private Attribute readAttribute( + final Attribute[] attributePrototypes, + final String type, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + for (int i = 0; i < attributePrototypes.length; ++i) { + if (attributePrototypes[i].type.equals(type)) { + return attributePrototypes[i].read( + this, offset, length, charBuffer, codeAttributeOffset, labels); + } + } + return new Attribute(type).read(this, offset, length, null, -1, null); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: low level parsing + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the number of entries in the class's constant pool table. + * + * @return the number of entries in the class's constant pool table. + */ + public int getItemCount() { + return cpInfoOffsets.length; + } + + /** + * Returns the start offset in {@link #b} of a JVMS 'cp_info' structure (i.e. a constant pool + * entry), plus one. This method is intended for {@link Attribute} sub classes, and is normally + * not needed by class generators or adapters. + * + * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool + * table. + * @return the start offset in {@link #b} of the corresponding JVMS 'cp_info' structure, plus one. + */ + public int getItem(final int constantPoolEntryIndex) { + return cpInfoOffsets[constantPoolEntryIndex]; + } + + /** + * Returns a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + * + * @return a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in {@link #b}. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in {@link #b}. + * @return the read value. + */ + public int readByte(final int offset) { + return b[offset] & 0xFF; + } + + /** + * Reads an unsigned short value in {@link #b}. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start index of the value to be read in {@link #b}. + * @return the read value. + */ + public int readUnsignedShort(final int offset) { + byte[] classFileBuffer = b; + return ((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF); + } + + /** + * Reads a signed short value in {@link #b}. This method is intended for {@link Attribute} sub + * classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in {@link #b}. + * @return the read value. + */ + public short readShort(final int offset) { + byte[] classFileBuffer = b; + return (short) (((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF)); + } + + /** + * Reads a signed int value in {@link #b}. This method is intended for {@link Attribute} sub + * classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in {@link #b}. + * @return the read value. + */ + public int readInt(final int offset) { + byte[] classFileBuffer = b; + return ((classFileBuffer[offset] & 0xFF) << 24) + | ((classFileBuffer[offset + 1] & 0xFF) << 16) + | ((classFileBuffer[offset + 2] & 0xFF) << 8) + | (classFileBuffer[offset + 3] & 0xFF); + } + + /** + * Reads a signed long value in {@link #b}. This method is intended for {@link Attribute} sub + * classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in {@link #b}. + * @return the read value. + */ + public long readLong(final int offset) { + long l1 = readInt(offset); + long l0 = readInt(offset + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in {@link #b}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the + * index of a CONSTANT_Utf8 entry in the class's constant pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + public String readUTF8(final int offset, final char[] charBuffer) { + int constantPoolEntryIndex = readUnsignedShort(offset); + if (offset == 0 || constantPoolEntryIndex == 0) { + return null; + } + return readUTF(constantPoolEntryIndex, charBuffer); + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in {@link #b}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool + * table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + final String readUTF(final int constantPoolEntryIndex, final char[] charBuffer) { + String value = (String) cpInfoValues[constantPoolEntryIndex]; + if (value != null) { + return value; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + value = readUTF(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer); + cpInfoValues[constantPoolEntryIndex] = value; + return value; + } + + /** + * Reads an UTF8 string in {@link #b}. + * + * @param utfOffset the start offset of the UTF8 string to be read. + * @param utfLength the length of the UTF8 string to be read. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUTF(final int utfOffset, final int utfLength, final char[] charBuffer) { + int currentOffset = utfOffset; + int endOffset = currentOffset + utfLength; + int strLength = 0; + byte[] classFileBuffer = b; + while (currentOffset < endOffset) { + int currentByte = classFileBuffer[currentOffset++]; + if ((currentByte & 0x80) == 0) { + charBuffer[strLength++] = (char) (currentByte & 0x7F); + } else if ((currentByte & 0xE0) == 0xC0) { + charBuffer[strLength++] = + (char) (((currentByte & 0x1F) << 6) + (classFileBuffer[currentOffset++] & 0x3F)); + } else { + charBuffer[strLength++] = + (char) + (((currentByte & 0xF) << 12) + + ((classFileBuffer[currentOffset++] & 0x3F) << 6) + + (classFileBuffer[currentOffset++] & 0x3F)); + } + } + return new String(charBuffer, 0, strLength); + } + + /** + * Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or + * CONSTANT_Package constant pool entry in {@link #b}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the + * index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or + * CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified constant pool entry. + */ + private String readStringish(final int offset, final char[] charBuffer) { + // Get the start offset of the cp_info structure (plus one), and read the CONSTANT_Utf8 entry + // designated by the first two bytes of this cp_info. + return readUTF8(cpInfoOffsets[readUnsignedShort(offset)], charBuffer); + } + + /** + * Reads a CONSTANT_Class constant pool entry in {@link #b}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the + * index of a CONSTANT_Class entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Class entry. + */ + public String readClass(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Module constant pool entry in {@link #b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the + * index of a CONSTANT_Module entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Module entry. + */ + public String readModule(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Package constant pool entry in {@link #b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the + * index of a CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Package entry. + */ + public String readPackage(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Dynamic constant pool entry in {@link #b}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant + * pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry. + */ + private ConstantDynamic readConstantDynamic( + final int constantPoolEntryIndex, final char[] charBuffer) { + ConstantDynamic constantDynamic = (ConstantDynamic) cpInfoValues[constantPoolEntryIndex]; + if (constantDynamic != null) { + return constantDynamic; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; + } + constantDynamic = new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments); + cpInfoValues[constantPoolEntryIndex] = constantDynamic; + return constantDynamic; + } + + /** + * Reads a numeric or string constant pool entry in {@link #b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, + * CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_MethodHandle or CONSTANT_Dynamic entry in the class's constant pool. + * @param charBuffer the buffer to be used to read strings. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, + * {@link Type}, {@link Handle} or {@link ConstantDynamic} corresponding to the specified + * constant pool entry. + */ + public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) { + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + switch (b[cpInfoOffset - 1]) { + case Symbol.CONSTANT_INTEGER_TAG: + return readInt(cpInfoOffset); + case Symbol.CONSTANT_FLOAT_TAG: + return Float.intBitsToFloat(readInt(cpInfoOffset)); + case Symbol.CONSTANT_LONG_TAG: + return readLong(cpInfoOffset); + case Symbol.CONSTANT_DOUBLE_TAG: + return Double.longBitsToDouble(readLong(cpInfoOffset)); + case Symbol.CONSTANT_CLASS_TAG: + return Type.getObjectType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_STRING_TAG: + return readUTF8(cpInfoOffset, charBuffer); + case Symbol.CONSTANT_METHOD_TYPE_TAG: + return Type.getMethodType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + int referenceKind = readByte(cpInfoOffset); + int referenceCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(referenceCpInfoOffset + 2)]; + String owner = readClass(referenceCpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + boolean isInterface = + b[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + return new Handle(referenceKind, owner, name, descriptor, isInterface); + case Symbol.CONSTANT_DYNAMIC_TAG: + return readConstantDynamic(constantPoolEntryIndex, charBuffer); + default: + throw new IllegalArgumentException(); } + } } diff --git a/src/jvm/clojure/asm/ClassVisitor.java b/src/jvm/clojure/asm/ClassVisitor.java index bc5919bf82..dc20a8107a 100644 --- a/src/jvm/clojure/asm/ClassVisitor.java +++ b/src/jvm/clojure/asm/ClassVisitor.java @@ -1,286 +1,341 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A visitor to visit a Java class. The methods of this class must be called in - * the following order: visit [ visitSource ] [ - * visitOuterClass ] ( visitAnnotation | - * visitAttribute )* ( visitInnerClass | visitField | - * visitMethod )* visitEnd. + * A visitor to visit a Java class. The methods of this class must be called in the following order: + * visit [ visitSource ] [ visitModule ][ visitNestHost ][ + * visitOuterClass ] ( visitAnnotation | visitTypeAnnotation | + * visitAttribute )* ( visitNestMember | visitInnerClass | + * visitField | visitMethod )* visitEnd. * * @author Eric Bruneton */ public abstract class ClassVisitor { - /** - * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. - */ - protected final int api; + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + */ + protected final int api; + + /** The class visitor to which this visitor must delegate method calls. May be null. */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + */ + public ClassVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + * @param classVisitor the class visitor to which this visitor must delegate method calls. May be + * null. + */ + public ClassVisitor(final int api, final ClassVisitor classVisitor) { + if (api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM7_EXPERIMENTAL) { + throw new IllegalArgumentException(); + } + this.api = api; + this.cv = classVisitor; + } + + /** + * Visits the header of the class. + * + * @param version the class version. The minor version is stored in the 16 most significant bits, + * and the major version in the 16 least significant bits. + * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if + * the class is deprecated. + * @param name the internal name of the class (see {@link Type#getInternalName()}). + * @param signature the signature of this class. May be null if the class is not a + * generic one, and does not extend or implement generic classes or interfaces. + * @param superName the internal of name of the super class (see {@link Type#getInternalName()}). + * For interfaces, the super class is {@link Object}. May be null, but only for the + * {@link Object} class. + * @param interfaces the internal names of the class's interfaces (see {@link + * Type#getInternalName()}). May be null. + */ + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + if (cv != null) { + cv.visit(version, access, name, signature, superName, interfaces); + } + } - /** - * The class visitor to which this visitor must delegate method calls. May - * be null. - */ - protected ClassVisitor cv; + /** + * Visits the source of the class. + * + * @param source the name of the source file from which the class was compiled. May be + * null. + * @param debug additional debug information to compute the correspondence between source and + * compiled elements of the class. May be null. + */ + public void visitSource(final String source, final String debug) { + if (cv != null) { + cv.visitSource(source, debug); + } + } - /** - * Constructs a new {@link ClassVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - */ - public ClassVisitor(final int api) { - this(api, null); + /** + * Visit the module corresponding to the class. + * + * @param name the fully qualified name (using dots) of the module. + * @param access the module access flags, among {@code ACC_OPEN}, {@code ACC_SYNTHETIC} and {@code + * ACC_MANDATED}. + * @param version the module version, or null. + * @return a visitor to visit the module values, or null if this visitor is not + * interested in visiting this module. + */ + public ModuleVisitor visitModule(final String name, final int access, final String version) { + if (api < Opcodes.ASM6) { + throw new UnsupportedOperationException(); + } + if (cv != null) { + return cv.visitModule(name, access, version); } + return null; + } - /** - * Constructs a new {@link ClassVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - * @param cv - * the class visitor to which this visitor must delegate method - * calls. May be null. - */ - public ClassVisitor(final int api, final ClassVisitor cv) { - if (api != Opcodes.ASM4) { - throw new IllegalArgumentException(); - } - this.api = api; - this.cv = cv; + /** + * Experimental, use at your own risk. This method will be renamed when it becomes stable, this + * will break existing code using it. Visits the nest host class of the class. A nest is a set + * of classes of the same package that share access to their private members. One of these + * classes, called the host, lists the other members of the nest, which in turn should link to the + * host of their nest. This method must be called only once and only if the visited class is a + * non-host member of a nest. A class is implicitly its own nest, so it's invalid to call this + * method with the visited class name as argument. + * + * @param nestHost the internal name of the host class of the nest. + * @deprecated This API is experimental. + */ + @Deprecated + public void visitNestHostExperimental(final String nestHost) { + if (api < Opcodes.ASM7_EXPERIMENTAL) { + throw new UnsupportedOperationException(); } + if (cv != null) { + cv.visitNestHostExperimental(nestHost); + } + } - /** - * Visits the header of the class. - * - * @param version - * the class version. - * @param access - * the class's access flags (see {@link Opcodes}). This parameter - * also indicates if the class is deprecated. - * @param name - * the internal name of the class (see - * {@link Type#getInternalName() getInternalName}). - * @param signature - * the signature of this class. May be null if the class - * is not a generic one, and does not extend or implement generic - * classes or interfaces. - * @param superName - * the internal of name of the super class (see - * {@link Type#getInternalName() getInternalName}). For - * interfaces, the super class is {@link Object}. May be - * null, but only for the {@link Object} class. - * @param interfaces - * the internal names of the class's interfaces (see - * {@link Type#getInternalName() getInternalName}). May be - * null. - */ - public void visit(int version, int access, String name, String signature, - String superName, String[] interfaces) { - if (cv != null) { - cv.visit(version, access, name, signature, superName, interfaces); - } + /** + * Visits the enclosing class of the class. This method must be called only if the class has an + * enclosing class. + * + * @param owner internal name of the enclosing class of the class. + * @param name the name of the method that contains the class, or null if the class is + * not enclosed in a method of its enclosing class. + * @param descriptor the descriptor of the method that contains the class, or null if the + * class is not enclosed in a method of its enclosing class. + */ + public void visitOuterClass(final String owner, final String name, final String descriptor) { + if (cv != null) { + cv.visitOuterClass(owner, name, descriptor); } + } - /** - * Visits the source of the class. - * - * @param source - * the name of the source file from which the class was compiled. - * May be null. - * @param debug - * additional debug information to compute the correspondance - * between source and compiled elements of the class. May be - * null. - */ - public void visitSource(String source, String debug) { - if (cv != null) { - cv.visitSource(source, debug); - } + /** + * Visits an annotation of the class. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (cv != null) { + return cv.visitAnnotation(descriptor, visible); } + return null; + } - /** - * Visits the enclosing class of the class. This method must be called only - * if the class has an enclosing class. - * - * @param owner - * internal name of the enclosing class of the class. - * @param name - * the name of the method that contains the class, or - * null if the class is not enclosed in a method of its - * enclosing class. - * @param desc - * the descriptor of the method that contains the class, or - * null if the class is not enclosed in a method of its - * enclosing class. - */ - public void visitOuterClass(String owner, String name, String desc) { - if (cv != null) { - cv.visitOuterClass(owner, name, desc); - } + /** + * Visits an annotation on a type in the class signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be null if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(); + } + if (cv != null) { + return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); } + return null; + } - /** - * Visits an annotation of the class. - * - * @param desc - * the class descriptor of the annotation class. - * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if - * this visitor is not interested in visiting this annotation. - */ - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - if (cv != null) { - return cv.visitAnnotation(desc, visible); - } - return null; + /** + * Visits a non standard attribute of the class. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (cv != null) { + cv.visitAttribute(attribute); } + } - /** - * Visits a non standard attribute of the class. - * - * @param attr - * an attribute. - */ - public void visitAttribute(Attribute attr) { - if (cv != null) { - cv.visitAttribute(attr); - } + /** + * Experimental, use at your own risk. This method will be renamed when it becomes stable, this + * will break existing code using it. Visits a member of the nest. A nest is a set of classes + * of the same package that share access to their private members. One of these classes, called + * the host, lists the other members of the nest, which in turn should link to the host of their + * nest. This method must be called only if the visited class is the host of a nest. A nest host + * is implicitly a member of its own nest, so it's invalid to call this method with the visited + * class name as argument. + * + * @param nestMember the internal name of a nest member. + * @deprecated This API is experimental. + */ + @Deprecated + public void visitNestMemberExperimental(final String nestMember) { + if (api < Opcodes.ASM7_EXPERIMENTAL) { + throw new UnsupportedOperationException(); + } + if (cv != null) { + cv.visitNestMemberExperimental(nestMember); } + } - /** - * Visits information about an inner class. This inner class is not - * necessarily a member of the class being visited. - * - * @param name - * the internal name of an inner class (see - * {@link Type#getInternalName() getInternalName}). - * @param outerName - * the internal name of the class to which the inner class - * belongs (see {@link Type#getInternalName() getInternalName}). - * May be null for not member classes. - * @param innerName - * the (simple) name of the inner class inside its enclosing - * class. May be null for anonymous inner classes. - * @param access - * the access flags of the inner class as originally declared in - * the enclosing class. - */ - public void visitInnerClass(String name, String outerName, - String innerName, int access) { - if (cv != null) { - cv.visitInnerClass(name, outerName, innerName, access); - } + /** + * Visits information about an inner class. This inner class is not necessarily a member of the + * class being visited. + * + * @param name the internal name of an inner class (see {@link Type#getInternalName()}). + * @param outerName the internal name of the class to which the inner class belongs (see {@link + * Type#getInternalName()}). May be null for not member classes. + * @param innerName the (simple) name of the inner class inside its enclosing class. May be + * null for anonymous inner classes. + * @param access the access flags of the inner class as originally declared in the enclosing + * class. + */ + public void visitInnerClass( + final String name, final String outerName, final String innerName, final int access) { + if (cv != null) { + cv.visitInnerClass(name, outerName, innerName, access); } + } - /** - * Visits a field of the class. - * - * @param access - * the field's access flags (see {@link Opcodes}). This parameter - * also indicates if the field is synthetic and/or deprecated. - * @param name - * the field's name. - * @param desc - * the field's descriptor (see {@link Type Type}). - * @param signature - * the field's signature. May be null if the field's - * type does not use generic types. - * @param value - * the field's initial value. This parameter, which may be - * null if the field does not have an initial value, - * must be an {@link Integer}, a {@link Float}, a {@link Long}, a - * {@link Double} or a {@link String} (for int, - * float, long or String fields - * respectively). This parameter is only used for static - * fields. Its value is ignored for non static fields, which - * must be initialized through bytecode instructions in - * constructors or methods. - * @return a visitor to visit field annotations and attributes, or - * null if this class visitor is not interested in visiting - * these annotations and attributes. - */ - public FieldVisitor visitField(int access, String name, String desc, - String signature, Object value) { - if (cv != null) { - return cv.visitField(access, name, desc, signature, value); - } - return null; + /** + * Visits a field of the class. + * + * @param access the field's access flags (see {@link Opcodes}). This parameter also indicates if + * the field is synthetic and/or deprecated. + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be null if the field's type does not use + * generic types. + * @param value the field's initial value. This parameter, which may be null if the field + * does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link + * Long}, a {@link Double} or a {@link String} (for int, float, + * long or String fields respectively). This parameter is only used for + * static fields. Its value is ignored for non static fields, which must be initialized + * through bytecode instructions in constructors or methods. + * @return a visitor to visit field annotations and attributes, or null if this class + * visitor is not interested in visiting these annotations and attributes. + */ + public FieldVisitor visitField( + final int access, + final String name, + final String descriptor, + final String signature, + final Object value) { + if (cv != null) { + return cv.visitField(access, name, descriptor, signature, value); } + return null; + } - /** - * Visits a method of the class. This method must return a new - * {@link MethodVisitor} instance (or null) each time it is called, - * i.e., it should not return a previously returned visitor. - * - * @param access - * the method's access flags (see {@link Opcodes}). This - * parameter also indicates if the method is synthetic and/or - * deprecated. - * @param name - * the method's name. - * @param desc - * the method's descriptor (see {@link Type Type}). - * @param signature - * the method's signature. May be null if the method - * parameters, return type and exceptions do not use generic - * types. - * @param exceptions - * the internal names of the method's exception classes (see - * {@link Type#getInternalName() getInternalName}). May be - * null. - * @return an object to visit the byte code of the method, or null - * if this class visitor is not interested in visiting the code of - * this method. - */ - public MethodVisitor visitMethod(int access, String name, String desc, - String signature, String[] exceptions) { - if (cv != null) { - return cv.visitMethod(access, name, desc, signature, exceptions); - } - return null; + /** + * Visits a method of the class. This method must return a new {@link MethodVisitor} + * instance (or null) each time it is called, i.e., it should not return a previously + * returned visitor. + * + * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if + * the method is synthetic and/or deprecated. + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be null if the method parameters, return + * type and exceptions do not use generic types. + * @param exceptions the internal names of the method's exception classes (see {@link + * Type#getInternalName()}). May be null. + * @return an object to visit the byte code of the method, or null if this class visitor + * is not interested in visiting the code of this method. + */ + public MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + if (cv != null) { + return cv.visitMethod(access, name, descriptor, signature, exceptions); } + return null; + } - /** - * Visits the end of the class. This method, which is the last one to be - * called, is used to inform the visitor that all the fields and methods of - * the class have been visited. - */ - public void visitEnd() { - if (cv != null) { - cv.visitEnd(); - } + /** + * Visits the end of the class. This method, which is the last one to be called, is used to inform + * the visitor that all the fields and methods of the class have been visited. + */ + public void visitEnd() { + if (cv != null) { + cv.visitEnd(); } + } } diff --git a/src/jvm/clojure/asm/ClassWriter.java b/src/jvm/clojure/asm/ClassWriter.java index 3d78868dee..4727fe376c 100644 --- a/src/jvm/clojure/asm/ClassWriter.java +++ b/src/jvm/clojure/asm/ClassWriter.java @@ -1,1693 +1,971 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A {@link ClassVisitor} that generates classes in bytecode form. More - * precisely this visitor generates a byte array conforming to the Java class - * file format. It can be used alone, to generate a Java class "from scratch", - * or with one or more {@link ClassReader ClassReader} and adapter class visitor - * to generate a modified class from one or more existing Java classes. + * A {@link ClassVisitor} that generates a corresponding ClassFile structure, as defined in the Java + * Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from + * scratch", or with one or more {@link ClassReader} and adapter {@link ClassVisitor} to generate a + * modified class from one or more existing Java classes. * + * @see JVMS 4 * @author Eric Bruneton */ public class ClassWriter extends ClassVisitor { - /** - * Flag to automatically compute the maximum stack size and the maximum - * number of local variables of methods. If this flag is set, then the - * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the - * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod} - * method will be ignored, and computed automatically from the signature and - * the bytecode of each method. - * - * @see #ClassWriter(int) - */ - public static final int COMPUTE_MAXS = 1; - - /** - * Flag to automatically compute the stack map frames of methods from - * scratch. If this flag is set, then the calls to the - * {@link MethodVisitor#visitFrame} method are ignored, and the stack map - * frames are recomputed from the methods bytecode. The arguments of the - * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and - * recomputed from the bytecode. In other words, computeFrames implies - * computeMaxs. - * - * @see #ClassWriter(int) - */ - public static final int COMPUTE_FRAMES = 2; - - /** - * Pseudo access flag to distinguish between the synthetic attribute and the - * synthetic access flag. - */ - static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000; - - /** - * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC. - */ - static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE - / Opcodes.ACC_SYNTHETIC; - - /** - * The type of instructions without any argument. - */ - static final int NOARG_INSN = 0; - - /** - * The type of instructions with an signed byte argument. - */ - static final int SBYTE_INSN = 1; - - /** - * The type of instructions with an signed short argument. - */ - static final int SHORT_INSN = 2; - - /** - * The type of instructions with a local variable index argument. - */ - static final int VAR_INSN = 3; - - /** - * The type of instructions with an implicit local variable index argument. - */ - static final int IMPLVAR_INSN = 4; - - /** - * The type of instructions with a type descriptor argument. - */ - static final int TYPE_INSN = 5; - - /** - * The type of field and method invocations instructions. - */ - static final int FIELDORMETH_INSN = 6; - - /** - * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction. - */ - static final int ITFMETH_INSN = 7; - - /** - * The type of the INVOKEDYNAMIC instruction. - */ - static final int INDYMETH_INSN = 8; - - /** - * The type of instructions with a 2 bytes bytecode offset label. - */ - static final int LABEL_INSN = 9; - - /** - * The type of instructions with a 4 bytes bytecode offset label. - */ - static final int LABELW_INSN = 10; - - /** - * The type of the LDC instruction. - */ - static final int LDC_INSN = 11; - - /** - * The type of the LDC_W and LDC2_W instructions. - */ - static final int LDCW_INSN = 12; - - /** - * The type of the IINC instruction. - */ - static final int IINC_INSN = 13; - - /** - * The type of the TABLESWITCH instruction. - */ - static final int TABL_INSN = 14; - - /** - * The type of the LOOKUPSWITCH instruction. - */ - static final int LOOK_INSN = 15; - - /** - * The type of the MULTIANEWARRAY instruction. - */ - static final int MANA_INSN = 16; - - /** - * The type of the WIDE instruction. - */ - static final int WIDE_INSN = 17; - - /** - * The instruction types of all JVM opcodes. - */ - static final byte[] TYPE; - - /** - * The type of CONSTANT_Class constant pool items. - */ - static final int CLASS = 7; - - /** - * The type of CONSTANT_Fieldref constant pool items. - */ - static final int FIELD = 9; - - /** - * The type of CONSTANT_Methodref constant pool items. - */ - static final int METH = 10; - - /** - * The type of CONSTANT_InterfaceMethodref constant pool items. - */ - static final int IMETH = 11; - - /** - * The type of CONSTANT_String constant pool items. - */ - static final int STR = 8; - - /** - * The type of CONSTANT_Integer constant pool items. - */ - static final int INT = 3; - - /** - * The type of CONSTANT_Float constant pool items. - */ - static final int FLOAT = 4; - - /** - * The type of CONSTANT_Long constant pool items. - */ - static final int LONG = 5; - - /** - * The type of CONSTANT_Double constant pool items. - */ - static final int DOUBLE = 6; - - /** - * The type of CONSTANT_NameAndType constant pool items. - */ - static final int NAME_TYPE = 12; - - /** - * The type of CONSTANT_Utf8 constant pool items. - */ - static final int UTF8 = 1; - - /** - * The type of CONSTANT_MethodType constant pool items. - */ - static final int MTYPE = 16; - - /** - * The type of CONSTANT_MethodHandle constant pool items. - */ - static final int HANDLE = 15; - - /** - * The type of CONSTANT_InvokeDynamic constant pool items. - */ - static final int INDY = 18; - - /** - * The base value for all CONSTANT_MethodHandle constant pool items. - * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9 - * different items. - */ - static final int HANDLE_BASE = 20; - - /** - * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable}, - * instead of the constant pool, in order to avoid clashes with normal - * constant pool items in the ClassWriter constant pool's hash table. - */ - static final int TYPE_NORMAL = 30; - - /** - * Uninitialized type Item stored in the ClassWriter - * {@link ClassWriter#typeTable}, instead of the constant pool, in order to - * avoid clashes with normal constant pool items in the ClassWriter constant - * pool's hash table. - */ - static final int TYPE_UNINIT = 31; - - /** - * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable}, - * instead of the constant pool, in order to avoid clashes with normal - * constant pool items in the ClassWriter constant pool's hash table. - */ - static final int TYPE_MERGED = 32; - - /** - * The type of BootstrapMethods items. These items are stored in a special - * class attribute named BootstrapMethods and not in the constant pool. - */ - static final int BSM = 33; - - /** - * The class reader from which this class writer was constructed, if any. - */ - ClassReader cr; - - /** - * Minor and major version numbers of the class to be generated. - */ - int version; - - /** - * Index of the next item to be added in the constant pool. - */ - int index; - - /** - * The constant pool of this class. - */ - final ByteVector pool; - - /** - * The constant pool's hash table data. - */ - Item[] items; - - /** - * The threshold of the constant pool's hash table. - */ - int threshold; - - /** - * A reusable key used to look for items in the {@link #items} hash table. - */ - final Item key; - - /** - * A reusable key used to look for items in the {@link #items} hash table. - */ - final Item key2; - - /** - * A reusable key used to look for items in the {@link #items} hash table. - */ - final Item key3; - - /** - * A reusable key used to look for items in the {@link #items} hash table. - */ - final Item key4; - - /** - * A type table used to temporarily store internal names that will not - * necessarily be stored in the constant pool. This type table is used by - * the control flow and data flow analysis algorithm used to compute stack - * map frames from scratch. This array associates to each index i - * the Item whose index is i. All Item objects stored in this array - * are also stored in the {@link #items} hash table. These two arrays allow - * to retrieve an Item from its index or, conversely, to get the index of an - * Item from its value. Each Item stores an internal name in its - * {@link Item#strVal1} field. - */ - Item[] typeTable; - - /** - * Number of elements in the {@link #typeTable} array. - */ - private short typeCount; - - /** - * The access flags of this class. - */ - private int access; - - /** - * The constant pool item that contains the internal name of this class. - */ - private int name; - - /** - * The internal name of this class. - */ - String thisName; - - /** - * The constant pool item that contains the signature of this class. - */ - private int signature; - - /** - * The constant pool item that contains the internal name of the super class - * of this class. - */ - private int superName; - - /** - * Number of interfaces implemented or extended by this class or interface. - */ - private int interfaceCount; - - /** - * The interfaces implemented or extended by this class or interface. More - * precisely, this array contains the indexes of the constant pool items - * that contain the internal names of these interfaces. - */ - private int[] interfaces; - - /** - * The index of the constant pool item that contains the name of the source - * file from which this class was compiled. - */ - private int sourceFile; - - /** - * The SourceDebug attribute of this class. - */ - private ByteVector sourceDebug; - - /** - * The constant pool item that contains the name of the enclosing class of - * this class. - */ - private int enclosingMethodOwner; - - /** - * The constant pool item that contains the name and descriptor of the - * enclosing method of this class. - */ - private int enclosingMethod; - - /** - * The runtime visible annotations of this class. - */ - private AnnotationWriter anns; - - /** - * The runtime invisible annotations of this class. - */ - private AnnotationWriter ianns; - - /** - * The non standard attributes of this class. - */ - private Attribute attrs; - - /** - * The number of entries in the InnerClasses attribute. - */ - private int innerClassesCount; - - /** - * The InnerClasses attribute. - */ - private ByteVector innerClasses; - - /** - * The number of entries in the BootstrapMethods attribute. - */ - int bootstrapMethodsCount; - - /** - * The BootstrapMethods attribute. - */ - ByteVector bootstrapMethods; - - /** - * The fields of this class. These fields are stored in a linked list of - * {@link FieldWriter} objects, linked to each other by their - * {@link FieldWriter#fv} field. This field stores the first element of this - * list. - */ - FieldWriter firstField; - - /** - * The fields of this class. These fields are stored in a linked list of - * {@link FieldWriter} objects, linked to each other by their - * {@link FieldWriter#fv} field. This field stores the last element of this - * list. - */ - FieldWriter lastField; - - /** - * The methods of this class. These methods are stored in a linked list of - * {@link MethodWriter} objects, linked to each other by their - * {@link MethodWriter#mv} field. This field stores the first element of - * this list. - */ - MethodWriter firstMethod; - - /** - * The methods of this class. These methods are stored in a linked list of - * {@link MethodWriter} objects, linked to each other by their - * {@link MethodWriter#mv} field. This field stores the last element of this - * list. - */ - MethodWriter lastMethod; - - /** - * true if the maximum stack size and number of local variables - * must be automatically computed. - */ - private final boolean computeMaxs; - - /** - * true if the stack map frames must be recomputed from scratch. - */ - private final boolean computeFrames; - - /** - * true if the stack map tables of this class are invalid. The - * {@link MethodWriter#resizeInstructions} method cannot transform existing - * stack map tables, and so produces potentially invalid classes when it is - * executed. In this case the class is reread and rewritten with the - * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize - * stack map tables when this option is used). - */ - boolean invalidFrames; - - // ------------------------------------------------------------------------ - // Static initializer - // ------------------------------------------------------------------------ - - /** - * Computes the instruction types of JVM opcodes. - */ - static { - int i; - byte[] b = new byte[220]; - String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" - + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" - + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; - for (i = 0; i < b.length; ++i) { - b[i] = (byte) (s.charAt(i) - 'A'); - } - TYPE = b; - - // code to generate the above string - // - // // SBYTE_INSN instructions - // b[Constants.NEWARRAY] = SBYTE_INSN; - // b[Constants.BIPUSH] = SBYTE_INSN; - // - // // SHORT_INSN instructions - // b[Constants.SIPUSH] = SHORT_INSN; - // - // // (IMPL)VAR_INSN instructions - // b[Constants.RET] = VAR_INSN; - // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) { - // b[i] = VAR_INSN; - // } - // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) { - // b[i] = VAR_INSN; - // } - // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3 - // b[i] = IMPLVAR_INSN; - // } - // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3 - // b[i] = IMPLVAR_INSN; - // } - // - // // TYPE_INSN instructions - // b[Constants.NEW] = TYPE_INSN; - // b[Constants.ANEWARRAY] = TYPE_INSN; - // b[Constants.CHECKCAST] = TYPE_INSN; - // b[Constants.INSTANCEOF] = TYPE_INSN; - // - // // (Set)FIELDORMETH_INSN instructions - // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) { - // b[i] = FIELDORMETH_INSN; - // } - // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN; - // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN; - // - // // LABEL(W)_INSN instructions - // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) { - // b[i] = LABEL_INSN; - // } - // b[Constants.IFNULL] = LABEL_INSN; - // b[Constants.IFNONNULL] = LABEL_INSN; - // b[200] = LABELW_INSN; // GOTO_W - // b[201] = LABELW_INSN; // JSR_W - // // temporary opcodes used internally by ASM - see Label and - // MethodWriter - // for (i = 202; i < 220; ++i) { - // b[i] = LABEL_INSN; - // } - // - // // LDC(_W) instructions - // b[Constants.LDC] = LDC_INSN; - // b[19] = LDCW_INSN; // LDC_W - // b[20] = LDCW_INSN; // LDC2_W - // - // // special instructions - // b[Constants.IINC] = IINC_INSN; - // b[Constants.TABLESWITCH] = TABL_INSN; - // b[Constants.LOOKUPSWITCH] = LOOK_INSN; - // b[Constants.MULTIANEWARRAY] = MANA_INSN; - // b[196] = WIDE_INSN; // WIDE - // - // for (i = 0; i < b.length; ++i) { - // System.err.print((char)('A' + b[i])); - // } - // System.err.println(); - } - - // ------------------------------------------------------------------------ - // Constructor - // ------------------------------------------------------------------------ - - /** - * Constructs a new {@link ClassWriter} object. - * - * @param flags - * option flags that can be used to modify the default behavior - * of this class. See {@link #COMPUTE_MAXS}, - * {@link #COMPUTE_FRAMES}. - */ - public ClassWriter(final int flags) { - super(Opcodes.ASM4); - index = 1; - pool = new ByteVector(); - items = new Item[256]; - threshold = (int) (0.75d * items.length); - key = new Item(); - key2 = new Item(); - key3 = new Item(); - key4 = new Item(); - this.computeMaxs = (flags & COMPUTE_MAXS) != 0; - this.computeFrames = (flags & COMPUTE_FRAMES) != 0; - } - - /** - * Constructs a new {@link ClassWriter} object and enables optimizations for - * "mostly add" bytecode transformations. These optimizations are the - * following: - * - *

    - *
  • The constant pool from the original class is copied as is in the new - * class, which saves time. New constant pool entries will be added at the - * end if necessary, but unused constant pool entries won't be - * removed.
  • - *
  • Methods that are not transformed are copied as is in the new class, - * directly from the original class bytecode (i.e. without emitting visit - * events for all the method instructions), which saves a lot of - * time. Untransformed methods are detected by the fact that the - * {@link ClassReader} receives {@link MethodVisitor} objects that come from - * a {@link ClassWriter} (and not from any other {@link ClassVisitor} - * instance).
  • - *
- * - * @param classReader - * the {@link ClassReader} used to read the original class. It - * will be used to copy the entire constant pool from the - * original class and also to copy other fragments of original - * bytecode where applicable. - * @param flags - * option flags that can be used to modify the default behavior - * of this class. These option flags do not affect methods - * that are copied as is in the new class. This means that the - * maximum stack size nor the stack frames will be computed for - * these methods. See {@link #COMPUTE_MAXS}, - * {@link #COMPUTE_FRAMES}. - */ - public ClassWriter(final ClassReader classReader, final int flags) { - this(flags); - classReader.copyPool(this); - this.cr = classReader; - } - - // ------------------------------------------------------------------------ - // Implementation of the ClassVisitor abstract class - // ------------------------------------------------------------------------ - - @Override - public final void visit(final int version, final int access, - final String name, final String signature, final String superName, - final String[] interfaces) { - this.version = version; - this.access = access; - this.name = newClass(name); - thisName = name; - if (ClassReader.SIGNATURES && signature != null) { - this.signature = newUTF8(signature); - } - this.superName = superName == null ? 0 : newClass(superName); - if (interfaces != null && interfaces.length > 0) { - interfaceCount = interfaces.length; - this.interfaces = new int[interfaceCount]; - for (int i = 0; i < interfaceCount; ++i) { - this.interfaces[i] = newClass(interfaces[i]); - } - } - } - - @Override - public final void visitSource(final String file, final String debug) { - if (file != null) { - sourceFile = newUTF8(file); - } - if (debug != null) { - sourceDebug = new ByteVector().putUTF8(debug); - } - } - - @Override - public final void visitOuterClass(final String owner, final String name, - final String desc) { - enclosingMethodOwner = newClass(owner); - if (name != null && desc != null) { - enclosingMethod = newNameType(name, desc); - } - } - - @Override - public final AnnotationVisitor visitAnnotation(final String desc, - final boolean visible) { - if (!ClassReader.ANNOTATIONS) { - return null; - } - ByteVector bv = new ByteVector(); - // write type, and reserve space for values count - bv.putShort(newUTF8(desc)).putShort(0); - AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2); - if (visible) { - aw.next = anns; - anns = aw; - } else { - aw.next = ianns; - ianns = aw; - } - return aw; - } - - @Override - public final void visitAttribute(final Attribute attr) { - attr.next = attrs; - attrs = attr; - } - - @Override - public final void visitInnerClass(final String name, - final String outerName, final String innerName, final int access) { - if (innerClasses == null) { - innerClasses = new ByteVector(); - } - ++innerClassesCount; - innerClasses.putShort(name == null ? 0 : newClass(name)); - innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); - innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); - innerClasses.putShort(access); - } - - @Override - public final FieldVisitor visitField(final int access, final String name, - final String desc, final String signature, final Object value) { - return new FieldWriter(this, access, name, desc, signature, value); - } - - @Override - public final MethodVisitor visitMethod(final int access, final String name, - final String desc, final String signature, final String[] exceptions) { - return new MethodWriter(this, access, name, desc, signature, - exceptions, computeMaxs, computeFrames); - } - - @Override - public final void visitEnd() { - } - - // ------------------------------------------------------------------------ - // Other public methods - // ------------------------------------------------------------------------ - - /** - * Returns the bytecode of the class that was build with this class writer. - * - * @return the bytecode of the class that was build with this class writer. - */ - public byte[] toByteArray() { - if (index > 0xFFFF) { - throw new RuntimeException("Class file too large!"); - } - // computes the real size of the bytecode of this class - int size = 24 + 2 * interfaceCount; - int nbFields = 0; - FieldWriter fb = firstField; - while (fb != null) { - ++nbFields; - size += fb.getSize(); - fb = (FieldWriter) fb.fv; - } - int nbMethods = 0; - MethodWriter mb = firstMethod; - while (mb != null) { - ++nbMethods; - size += mb.getSize(); - mb = (MethodWriter) mb.mv; - } - int attributeCount = 0; - if (bootstrapMethods != null) { - // we put it as first attribute in order to improve a bit - // ClassReader.copyBootstrapMethods - ++attributeCount; - size += 8 + bootstrapMethods.length; - newUTF8("BootstrapMethods"); - } - if (ClassReader.SIGNATURES && signature != 0) { - ++attributeCount; - size += 8; - newUTF8("Signature"); - } - if (sourceFile != 0) { - ++attributeCount; - size += 8; - newUTF8("SourceFile"); - } - if (sourceDebug != null) { - ++attributeCount; - size += sourceDebug.length + 4; - newUTF8("SourceDebugExtension"); - } - if (enclosingMethodOwner != 0) { - ++attributeCount; - size += 10; - newUTF8("EnclosingMethod"); - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - ++attributeCount; - size += 6; - newUTF8("Deprecated"); - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((version & 0xFFFF) < Opcodes.V1_5 - || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { - ++attributeCount; - size += 6; - newUTF8("Synthetic"); - } - } - if (innerClasses != null) { - ++attributeCount; - size += 8 + innerClasses.length; - newUTF8("InnerClasses"); - } - if (ClassReader.ANNOTATIONS && anns != null) { - ++attributeCount; - size += 8 + anns.getSize(); - newUTF8("RuntimeVisibleAnnotations"); - } - if (ClassReader.ANNOTATIONS && ianns != null) { - ++attributeCount; - size += 8 + ianns.getSize(); - newUTF8("RuntimeInvisibleAnnotations"); - } - if (attrs != null) { - attributeCount += attrs.getCount(); - size += attrs.getSize(this, null, 0, -1, -1); - } - size += pool.length; - // allocates a byte vector of this size, in order to avoid unnecessary - // arraycopy operations in the ByteVector.enlarge() method - ByteVector out = new ByteVector(size); - out.putInt(0xCAFEBABE).putInt(version); - out.putShort(index).putByteArray(pool.data, 0, pool.length); - int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE - | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC); - out.putShort(access & ~mask).putShort(name).putShort(superName); - out.putShort(interfaceCount); - for (int i = 0; i < interfaceCount; ++i) { - out.putShort(interfaces[i]); - } - out.putShort(nbFields); - fb = firstField; - while (fb != null) { - fb.put(out); - fb = (FieldWriter) fb.fv; - } - out.putShort(nbMethods); - mb = firstMethod; - while (mb != null) { - mb.put(out); - mb = (MethodWriter) mb.mv; - } - out.putShort(attributeCount); - if (bootstrapMethods != null) { - out.putShort(newUTF8("BootstrapMethods")); - out.putInt(bootstrapMethods.length + 2).putShort( - bootstrapMethodsCount); - out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); - } - if (ClassReader.SIGNATURES && signature != 0) { - out.putShort(newUTF8("Signature")).putInt(2).putShort(signature); - } - if (sourceFile != 0) { - out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); - } - if (sourceDebug != null) { - int len = sourceDebug.length - 2; - out.putShort(newUTF8("SourceDebugExtension")).putInt(len); - out.putByteArray(sourceDebug.data, 2, len); - } - if (enclosingMethodOwner != 0) { - out.putShort(newUTF8("EnclosingMethod")).putInt(4); - out.putShort(enclosingMethodOwner).putShort(enclosingMethod); - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - out.putShort(newUTF8("Deprecated")).putInt(0); - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((version & 0xFFFF) < Opcodes.V1_5 - || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { - out.putShort(newUTF8("Synthetic")).putInt(0); - } - } - if (innerClasses != null) { - out.putShort(newUTF8("InnerClasses")); - out.putInt(innerClasses.length + 2).putShort(innerClassesCount); - out.putByteArray(innerClasses.data, 0, innerClasses.length); - } - if (ClassReader.ANNOTATIONS && anns != null) { - out.putShort(newUTF8("RuntimeVisibleAnnotations")); - anns.put(out); - } - if (ClassReader.ANNOTATIONS && ianns != null) { - out.putShort(newUTF8("RuntimeInvisibleAnnotations")); - ianns.put(out); - } - if (attrs != null) { - attrs.put(this, null, 0, -1, -1, out); - } - if (invalidFrames) { - ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); - new ClassReader(out.data).accept(cw, ClassReader.SKIP_FRAMES); - return cw.toByteArray(); - } - return out.data; - } - - // ------------------------------------------------------------------------ - // Utility methods: constant pool management - // ------------------------------------------------------------------------ - - /** - * Adds a number or string constant to the constant pool of the class being - * build. Does nothing if the constant pool already contains a similar item. - * - * @param cst - * the value of the constant to be added to the constant pool. - * This parameter must be an {@link Integer}, a {@link Float}, a - * {@link Long}, a {@link Double}, a {@link String} or a - * {@link Type}. - * @return a new or already existing constant item with the given value. - */ - Item newConstItem(final Object cst) { - if (cst instanceof Integer) { - int val = ((Integer) cst).intValue(); - return newInteger(val); - } else if (cst instanceof Byte) { - int val = ((Byte) cst).intValue(); - return newInteger(val); - } else if (cst instanceof Character) { - int val = ((Character) cst).charValue(); - return newInteger(val); - } else if (cst instanceof Short) { - int val = ((Short) cst).intValue(); - return newInteger(val); - } else if (cst instanceof Boolean) { - int val = ((Boolean) cst).booleanValue() ? 1 : 0; - return newInteger(val); - } else if (cst instanceof Float) { - float val = ((Float) cst).floatValue(); - return newFloat(val); - } else if (cst instanceof Long) { - long val = ((Long) cst).longValue(); - return newLong(val); - } else if (cst instanceof Double) { - double val = ((Double) cst).doubleValue(); - return newDouble(val); - } else if (cst instanceof String) { - return newString((String) cst); - } else if (cst instanceof Type) { - Type t = (Type) cst; - int s = t.getSort(); - if (s == Type.OBJECT) { - return newClassItem(t.getInternalName()); - } else if (s == Type.METHOD) { - return newMethodTypeItem(t.getDescriptor()); - } else { // s == primitive type or array - return newClassItem(t.getDescriptor()); - } - } else if (cst instanceof Handle) { - Handle h = (Handle) cst; - return newHandleItem(h.tag, h.owner, h.name, h.desc); - } else { - throw new IllegalArgumentException("value " + cst); - } - } - - /** - * Adds a number or string constant to the constant pool of the class being - * build. Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param cst - * the value of the constant to be added to the constant pool. - * This parameter must be an {@link Integer}, a {@link Float}, a - * {@link Long}, a {@link Double} or a {@link String}. - * @return the index of a new or already existing constant item with the - * given value. - */ - public int newConst(final Object cst) { - return newConstItem(cst).index; - } - - /** - * Adds an UTF8 string to the constant pool of the class being build. Does - * nothing if the constant pool already contains a similar item. This - * method is intended for {@link Attribute} sub classes, and is normally not - * needed by class generators or adapters. - * - * @param value - * the String value. - * @return the index of a new or already existing UTF8 item. - */ - public int newUTF8(final String value) { - key.set(UTF8, value, null, null); - Item result = get(key); - if (result == null) { - pool.putByte(UTF8).putUTF8(value); - result = new Item(index++, key); - put(result); - } - return result.index; - } - - /** - * Adds a class reference to the constant pool of the class being build. - * Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param value - * the internal name of the class. - * @return a new or already existing class reference item. - */ - Item newClassItem(final String value) { - key2.set(CLASS, value, null, null); - Item result = get(key2); - if (result == null) { - pool.put12(CLASS, newUTF8(value)); - result = new Item(index++, key2); - put(result); - } - return result; - } - - /** - * Adds a class reference to the constant pool of the class being build. - * Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param value - * the internal name of the class. - * @return the index of a new or already existing class reference item. - */ - public int newClass(final String value) { - return newClassItem(value).index; - } - - /** - * Adds a method type reference to the constant pool of the class being - * build. Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param methodDesc - * method descriptor of the method type. - * @return a new or already existing method type reference item. - */ - Item newMethodTypeItem(final String methodDesc) { - key2.set(MTYPE, methodDesc, null, null); - Item result = get(key2); - if (result == null) { - pool.put12(MTYPE, newUTF8(methodDesc)); - result = new Item(index++, key2); - put(result); - } - return result; - } - - /** - * Adds a method type reference to the constant pool of the class being - * build. Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param methodDesc - * method descriptor of the method type. - * @return the index of a new or already existing method type reference - * item. - */ - public int newMethodType(final String methodDesc) { - return newMethodTypeItem(methodDesc).index; - } - - /** - * Adds a handle to the constant pool of the class being build. Does nothing - * if the constant pool already contains a similar item. This method is - * intended for {@link Attribute} sub classes, and is normally not needed by - * class generators or adapters. - * - * @param tag - * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, - * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, - * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, - * {@link Opcodes#H_INVOKESTATIC}, - * {@link Opcodes#H_INVOKESPECIAL}, - * {@link Opcodes#H_NEWINVOKESPECIAL} or - * {@link Opcodes#H_INVOKEINTERFACE}. - * @param owner - * the internal name of the field or method owner class. - * @param name - * the name of the field or method. - * @param desc - * the descriptor of the field or method. - * @return a new or an already existing method type reference item. - */ - Item newHandleItem(final int tag, final String owner, final String name, - final String desc) { - key4.set(HANDLE_BASE + tag, owner, name, desc); - Item result = get(key4); - if (result == null) { - if (tag <= Opcodes.H_PUTSTATIC) { - put112(HANDLE, tag, newField(owner, name, desc)); - } else { - put112(HANDLE, - tag, - newMethod(owner, name, desc, - tag == Opcodes.H_INVOKEINTERFACE)); - } - result = new Item(index++, key4); - put(result); - } - return result; - } - - /** - * Adds a handle to the constant pool of the class being build. Does nothing - * if the constant pool already contains a similar item. This method is - * intended for {@link Attribute} sub classes, and is normally not needed by - * class generators or adapters. - * - * @param tag - * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, - * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, - * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, - * {@link Opcodes#H_INVOKESTATIC}, - * {@link Opcodes#H_INVOKESPECIAL}, - * {@link Opcodes#H_NEWINVOKESPECIAL} or - * {@link Opcodes#H_INVOKEINTERFACE}. - * @param owner - * the internal name of the field or method owner class. - * @param name - * the name of the field or method. - * @param desc - * the descriptor of the field or method. - * @return the index of a new or already existing method type reference - * item. - */ - public int newHandle(final int tag, final String owner, final String name, - final String desc) { - return newHandleItem(tag, owner, name, desc).index; - } - - /** - * Adds an invokedynamic reference to the constant pool of the class being - * build. Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param name - * name of the invoked method. - * @param desc - * descriptor of the invoke method. - * @param bsm - * the bootstrap method. - * @param bsmArgs - * the bootstrap method constant arguments. - * - * @return a new or an already existing invokedynamic type reference item. - */ - Item newInvokeDynamicItem(final String name, final String desc, - final Handle bsm, final Object... bsmArgs) { - // cache for performance - ByteVector bootstrapMethods = this.bootstrapMethods; - if (bootstrapMethods == null) { - bootstrapMethods = this.bootstrapMethods = new ByteVector(); - } - - int position = bootstrapMethods.length; // record current position - - int hashCode = bsm.hashCode(); - bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name, - bsm.desc)); - - int argsLength = bsmArgs.length; - bootstrapMethods.putShort(argsLength); - - for (int i = 0; i < argsLength; i++) { - Object bsmArg = bsmArgs[i]; - hashCode ^= bsmArg.hashCode(); - bootstrapMethods.putShort(newConst(bsmArg)); - } - - byte[] data = bootstrapMethods.data; - int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments) - hashCode &= 0x7FFFFFFF; - Item result = items[hashCode % items.length]; - loop: while (result != null) { - if (result.type != BSM || result.hashCode != hashCode) { - result = result.next; - continue; - } - - // because the data encode the size of the argument - // we don't need to test if these size are equals - int resultPosition = result.intVal; - for (int p = 0; p < length; p++) { - if (data[position + p] != data[resultPosition + p]) { - result = result.next; - continue loop; - } - } - break; - } - - int bootstrapMethodIndex; - if (result != null) { - bootstrapMethodIndex = result.index; - bootstrapMethods.length = position; // revert to old position - } else { - bootstrapMethodIndex = bootstrapMethodsCount++; - result = new Item(bootstrapMethodIndex); - result.set(position, hashCode); - put(result); - } - - // now, create the InvokeDynamic constant - key3.set(name, desc, bootstrapMethodIndex); - result = get(key3); - if (result == null) { - put122(INDY, bootstrapMethodIndex, newNameType(name, desc)); - result = new Item(index++, key3); - put(result); - } - return result; - } - - /** - * Adds an invokedynamic reference to the constant pool of the class being - * build. Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param name - * name of the invoked method. - * @param desc - * descriptor of the invoke method. - * @param bsm - * the bootstrap method. - * @param bsmArgs - * the bootstrap method constant arguments. - * - * @return the index of a new or already existing invokedynamic reference - * item. - */ - public int newInvokeDynamic(final String name, final String desc, - final Handle bsm, final Object... bsmArgs) { - return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index; - } - - /** - * Adds a field reference to the constant pool of the class being build. - * Does nothing if the constant pool already contains a similar item. - * - * @param owner - * the internal name of the field's owner class. - * @param name - * the field's name. - * @param desc - * the field's descriptor. - * @return a new or already existing field reference item. - */ - Item newFieldItem(final String owner, final String name, final String desc) { - key3.set(FIELD, owner, name, desc); - Item result = get(key3); - if (result == null) { - put122(FIELD, newClass(owner), newNameType(name, desc)); - result = new Item(index++, key3); - put(result); - } - return result; - } - - /** - * Adds a field reference to the constant pool of the class being build. - * Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param owner - * the internal name of the field's owner class. - * @param name - * the field's name. - * @param desc - * the field's descriptor. - * @return the index of a new or already existing field reference item. - */ - public int newField(final String owner, final String name, final String desc) { - return newFieldItem(owner, name, desc).index; - } - - /** - * Adds a method reference to the constant pool of the class being build. - * Does nothing if the constant pool already contains a similar item. - * - * @param owner - * the internal name of the method's owner class. - * @param name - * the method's name. - * @param desc - * the method's descriptor. - * @param itf - * true if owner is an interface. - * @return a new or already existing method reference item. - */ - Item newMethodItem(final String owner, final String name, - final String desc, final boolean itf) { - int type = itf ? IMETH : METH; - key3.set(type, owner, name, desc); - Item result = get(key3); - if (result == null) { - put122(type, newClass(owner), newNameType(name, desc)); - result = new Item(index++, key3); - put(result); - } - return result; - } - - /** - * Adds a method reference to the constant pool of the class being build. - * Does nothing if the constant pool already contains a similar item. - * This method is intended for {@link Attribute} sub classes, and is - * normally not needed by class generators or adapters. - * - * @param owner - * the internal name of the method's owner class. - * @param name - * the method's name. - * @param desc - * the method's descriptor. - * @param itf - * true if owner is an interface. - * @return the index of a new or already existing method reference item. - */ - public int newMethod(final String owner, final String name, - final String desc, final boolean itf) { - return newMethodItem(owner, name, desc, itf).index; - } - - /** - * Adds an integer to the constant pool of the class being build. Does - * nothing if the constant pool already contains a similar item. - * - * @param value - * the int value. - * @return a new or already existing int item. - */ - Item newInteger(final int value) { - key.set(value); - Item result = get(key); - if (result == null) { - pool.putByte(INT).putInt(value); - result = new Item(index++, key); - put(result); - } - return result; - } - - /** - * Adds a float to the constant pool of the class being build. Does nothing - * if the constant pool already contains a similar item. - * - * @param value - * the float value. - * @return a new or already existing float item. - */ - Item newFloat(final float value) { - key.set(value); - Item result = get(key); - if (result == null) { - pool.putByte(FLOAT).putInt(key.intVal); - result = new Item(index++, key); - put(result); - } - return result; - } - - /** - * Adds a long to the constant pool of the class being build. Does nothing - * if the constant pool already contains a similar item. - * - * @param value - * the long value. - * @return a new or already existing long item. - */ - Item newLong(final long value) { - key.set(value); - Item result = get(key); - if (result == null) { - pool.putByte(LONG).putLong(value); - result = new Item(index, key); - index += 2; - put(result); - } - return result; - } - - /** - * Adds a double to the constant pool of the class being build. Does nothing - * if the constant pool already contains a similar item. - * - * @param value - * the double value. - * @return a new or already existing double item. - */ - Item newDouble(final double value) { - key.set(value); - Item result = get(key); - if (result == null) { - pool.putByte(DOUBLE).putLong(key.longVal); - result = new Item(index, key); - index += 2; - put(result); - } - return result; - } - - /** - * Adds a string to the constant pool of the class being build. Does nothing - * if the constant pool already contains a similar item. - * - * @param value - * the String value. - * @return a new or already existing string item. - */ - private Item newString(final String value) { - key2.set(STR, value, null, null); - Item result = get(key2); - if (result == null) { - pool.put12(STR, newUTF8(value)); - result = new Item(index++, key2); - put(result); - } - return result; - } - - /** - * Adds a name and type to the constant pool of the class being build. Does - * nothing if the constant pool already contains a similar item. This - * method is intended for {@link Attribute} sub classes, and is normally not - * needed by class generators or adapters. - * - * @param name - * a name. - * @param desc - * a type descriptor. - * @return the index of a new or already existing name and type item. - */ - public int newNameType(final String name, final String desc) { - return newNameTypeItem(name, desc).index; - } - - /** - * Adds a name and type to the constant pool of the class being build. Does - * nothing if the constant pool already contains a similar item. - * - * @param name - * a name. - * @param desc - * a type descriptor. - * @return a new or already existing name and type item. - */ - Item newNameTypeItem(final String name, final String desc) { - key2.set(NAME_TYPE, name, desc, null); - Item result = get(key2); - if (result == null) { - put122(NAME_TYPE, newUTF8(name), newUTF8(desc)); - result = new Item(index++, key2); - put(result); - } - return result; - } - - /** - * Adds the given internal name to {@link #typeTable} and returns its index. - * Does nothing if the type table already contains this internal name. - * - * @param type - * the internal name to be added to the type table. - * @return the index of this internal name in the type table. - */ - int addType(final String type) { - key.set(TYPE_NORMAL, type, null, null); - Item result = get(key); - if (result == null) { - result = addType(key); - } - return result.index; - } - - /** - * Adds the given "uninitialized" type to {@link #typeTable} and returns its - * index. This method is used for UNINITIALIZED types, made of an internal - * name and a bytecode offset. - * - * @param type - * the internal name to be added to the type table. - * @param offset - * the bytecode offset of the NEW instruction that created this - * UNINITIALIZED type value. - * @return the index of this internal name in the type table. - */ - int addUninitializedType(final String type, final int offset) { - key.type = TYPE_UNINIT; - key.intVal = offset; - key.strVal1 = type; - key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset); - Item result = get(key); - if (result == null) { - result = addType(key); - } - return result.index; - } - - /** - * Adds the given Item to {@link #typeTable}. - * - * @param item - * the value to be added to the type table. - * @return the added Item, which a new Item instance with the same value as - * the given Item. - */ - private Item addType(final Item item) { - ++typeCount; - Item result = new Item(typeCount, key); - put(result); - if (typeTable == null) { - typeTable = new Item[16]; - } - if (typeCount == typeTable.length) { - Item[] newTable = new Item[2 * typeTable.length]; - System.arraycopy(typeTable, 0, newTable, 0, typeTable.length); - typeTable = newTable; - } - typeTable[typeCount] = result; - return result; - } - - /** - * Returns the index of the common super type of the two given types. This - * method calls {@link #getCommonSuperClass} and caches the result in the - * {@link #items} hash table to speedup future calls with the same - * parameters. - * - * @param type1 - * index of an internal name in {@link #typeTable}. - * @param type2 - * index of an internal name in {@link #typeTable}. - * @return the index of the common super type of the two given types. - */ - int getMergedType(final int type1, final int type2) { - key2.type = TYPE_MERGED; - key2.longVal = type1 | (((long) type2) << 32); - key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2); - Item result = get(key2); - if (result == null) { - String t = typeTable[type1].strVal1; - String u = typeTable[type2].strVal1; - key2.intVal = addType(getCommonSuperClass(t, u)); - result = new Item((short) 0, key2); - put(result); - } - return result.intVal; - } - - /** - * Returns the common super type of the two given types. The default - * implementation of this method loads the two given classes and uses - * the java.lang.Class methods to find the common super class. It can be - * overridden to compute this common super type in other ways, in particular - * without actually loading any class, or to take into account the class - * that is currently being generated by this ClassWriter, which can of - * course not be loaded since it is under construction. - * - * @param type1 - * the internal name of a class. - * @param type2 - * the internal name of another class. - * @return the internal name of the common super class of the two given - * classes. - */ - protected String getCommonSuperClass(final String type1, final String type2) { - Class c, d; - ClassLoader classLoader = getClass().getClassLoader(); - try { - c = Class.forName(type1.replace('/', '.'), false, classLoader); - d = Class.forName(type2.replace('/', '.'), false, classLoader); - } catch (Exception e) { - throw new RuntimeException(e.toString()); - } - if (c.isAssignableFrom(d)) { - return type1; - } - if (d.isAssignableFrom(c)) { - return type2; - } - if (c.isInterface() || d.isInterface()) { - return "java/lang/Object"; - } else { - do { - c = c.getSuperclass(); - } while (!c.isAssignableFrom(d)); - return c.getName().replace('.', '/'); - } - } - - /** - * Returns the constant pool's hash table item which is equal to the given - * item. - * - * @param key - * a constant pool item. - * @return the constant pool's hash table item which is equal to the given - * item, or null if there is no such item. - */ - private Item get(final Item key) { - Item i = items[key.hashCode % items.length]; - while (i != null && (i.type != key.type || !key.isEqualTo(i))) { - i = i.next; - } - return i; - } - - /** - * Puts the given item in the constant pool's hash table. The hash table - * must not already contains this item. - * - * @param i - * the item to be added to the constant pool's hash table. - */ - private void put(final Item i) { - if (index + typeCount > threshold) { - int ll = items.length; - int nl = ll * 2 + 1; - Item[] newItems = new Item[nl]; - for (int l = ll - 1; l >= 0; --l) { - Item j = items[l]; - while (j != null) { - int index = j.hashCode % newItems.length; - Item k = j.next; - j.next = newItems[index]; - newItems[index] = j; - j = k; - } - } - items = newItems; - threshold = (int) (nl * 0.75); - } - int index = i.hashCode % items.length; - i.next = items[index]; - items[index] = i; - } - - /** - * Puts one byte and two shorts into the constant pool. - * - * @param b - * a byte. - * @param s1 - * a short. - * @param s2 - * another short. - */ - private void put122(final int b, final int s1, final int s2) { - pool.put12(b, s1).putShort(s2); - } - - /** - * Puts two bytes and one short into the constant pool. - * - * @param b1 - * a byte. - * @param b2 - * another byte. - * @param s - * a short. - */ - private void put112(final int b1, final int b2, final int s) { - pool.put11(b1, b2).putShort(s); - } + /** + * A flag to automatically compute the maximum stack size and the maximum number of local + * variables of methods. If this flag is set, then the arguments of the {@link + * MethodVisitor#visitMaxs} method of the {@link MethodVisitor} returned by the {@link + * #visitMethod} method will be ignored, and computed automatically from the signature and the + * bytecode of each method. + * + *

Note: for classes whose version is {@link Opcodes#V1_7} of more, this option requires + * valid stack map frames. The maximum stack size is then computed from these frames, and from the + * bytecode instructions in between. If stack map frames are not present or must be recomputed, + * used {@link #COMPUTE_FRAMES} instead. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * A flag to automatically compute the stack map frames of methods from scratch. If this flag is + * set, then the calls to the {@link MethodVisitor#visitFrame} method are ignored, and the stack + * map frames are recomputed from the methods bytecode. The arguments of the {@link + * MethodVisitor#visitMaxs} method are also ignored and recomputed from the bytecode. In other + * words, {@link #COMPUTE_FRAMES} implies {@link #COMPUTE_MAXS}. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + // Note: fields are ordered as in the ClassFile structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The minor_version and major_version fields of the JVMS ClassFile structure. minor_version is + * stored in the 16 most significant bits, and major_version in the 16 least significant bits. + */ + private int version; + + /** The symbol table for this class (contains the constant_pool and the BootstrapMethods). */ + private final SymbolTable symbolTable; + + /** + * The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the + * ClassFile structure. + */ + private int accessFlags; + + /** The this_class field of the JVMS ClassFile structure. */ + private int thisClass; + + /** The super_class field of the JVMS ClassFile structure. */ + private int superClass; + + /** The interface_count field of the JVMS ClassFile structure. */ + private int interfaceCount; + + /** The 'interfaces' array of the JVMS ClassFile structure. */ + private int[] interfaces; + + /** + * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their + * {@link FieldWriter#fv} field. This field stores the first element of this list. + */ + private FieldWriter firstField; + + /** + * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their + * {@link FieldWriter#fv} field. This field stores the last element of this list. + */ + private FieldWriter lastField; + + /** + * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their + * {@link MethodWriter#mv} field. This field stores the first element of this list. + */ + private MethodWriter firstMethod; + + /** + * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their + * {@link MethodWriter#mv} field. This field stores the last element of this list. + */ + private MethodWriter lastMethod; + + /** The number_of_classes field of the InnerClasses attribute, or 0. */ + private int numberOfInnerClasses; + + /** The 'classes' array of the InnerClasses attribute, or null. */ + private ByteVector innerClasses; + + /** The class_index field of the EnclosingMethod attribute, or 0. */ + private int enclosingClassIndex; + + /** The method_index field of the EnclosingMethod attribute. */ + private int enclosingMethodIndex; + + /** The signature_index field of the Signature attribute, or 0. */ + private int signatureIndex; + + /** The source_file_index field of the SourceFile attribute, or 0. */ + private int sourceFileIndex; + + /** The debug_extension field of the SourceDebugExtension attribute, or null. */ + private ByteVector debugExtension; + + /** + * The last runtime visible annotation of this class. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this class. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this class. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this class. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** The Module attribute of this class, or null. */ + private ModuleWriter moduleWriter; + + /** The host_class_index field of the NestHost attribute, or 0. */ + private int nestHostClassIndex; + + /** The number_of_classes field of the NestMembers attribute, or 0. */ + private int numberOfNestMemberClasses; + + /** The 'classes' array of the NestMembers attribute, or null. */ + private ByteVector nestMemberClasses; + + /** + * The first non standard attribute of this class. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be null. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #toByteArray} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + /** + * Indicates what must be automatically computed in {@link MethodWriter}. Must be one of {@link + * MethodWriter#COMPUTE_NOTHING}, {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL}, {@link + * MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}. + */ + private int compute; + + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags option flags that can be used to modify the default behavior of this class. Must + * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + this(null, flags); + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for "mostly add" bytecode + * transformations. These optimizations are the following: + * + *

    + *
  • The constant pool and bootstrap methods from the original class are copied as is in the + * new class, which saves time. New constant pool entries and new bootstrap methods will be + * added at the end if necessary, but unused constant pool entries or bootstrap methods + * won't be removed. + *
  • Methods that are not transformed are copied as is in the new class, directly from the + * original class bytecode (i.e. without emitting visit events for all the method + * instructions), which saves a lot of time. Untransformed methods are detected by + * the fact that the {@link ClassReader} receives {@link MethodVisitor} objects that come + * from a {@link ClassWriter} (and not from any other {@link ClassVisitor} instance). + *
+ * + * @param classReader the {@link ClassReader} used to read the original class. It will be used to + * copy the entire constant pool and bootstrap methods from the original class and also to + * copy other fragments of original bytecode where applicable. + * @param flags option flags that can be used to modify the default behavior of this class.Must be + * zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. These option flags do + * not affect methods that are copied as is in the new class. This means that neither the + * maximum stack size nor the stack frames will be computed for these methods. + */ + public ClassWriter(final ClassReader classReader, final int flags) { + super(Opcodes.ASM6); + symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); + if ((flags & COMPUTE_FRAMES) != 0) { + this.compute = MethodWriter.COMPUTE_ALL_FRAMES; + } else if ((flags & COMPUTE_MAXS) != 0) { + this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; + } else { + this.compute = MethodWriter.COMPUTE_NOTHING; + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the ClassVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public final void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + this.version = version; + this.accessFlags = access; + this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index; + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index; + } + } + if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) { + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES; + } + } + + @Override + public final void visitSource(final String file, final String debug) { + if (file != null) { + sourceFileIndex = symbolTable.addConstantUtf8(file); + } + if (debug != null) { + debugExtension = new ByteVector().encodeUTF8(debug, 0, Integer.MAX_VALUE); + } + } + + @Override + public final ModuleVisitor visitModule( + final String name, final int access, final String version) { + return moduleWriter = + new ModuleWriter( + symbolTable, + symbolTable.addConstantModule(name).index, + access, + version == null ? 0 : symbolTable.addConstantUtf8(version)); + } + + @Override + public void visitNestHostExperimental(final String nestHost) { + nestHostClassIndex = symbolTable.addConstantClass(nestHost).index; + } + + @Override + public final void visitOuterClass( + final String owner, final String name, final String descriptor) { + enclosingClassIndex = symbolTable.addConstantClass(owner).index; + if (name != null && descriptor != null) { + enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor); + } + } + + @Override + public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + // Create a ByteVector to hold an 'annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. + ByteVector annotation = new ByteVector(); + // Write type_index and reserve space for num_element_value_pairs. + annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastRuntimeVisibleAnnotation = + new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public final AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget(typeRef, typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public final void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public void visitNestMemberExperimental(final String nestMember) { + if (nestMemberClasses == null) { + nestMemberClasses = new ByteVector(); + } + ++numberOfNestMemberClasses; + nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index); + } + + @Override + public final void visitInnerClass( + final String name, final String outerName, final String innerName, final int access) { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + // Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the constant_pool table + // which represents a class or interface C that is not a package member must have exactly one + // corresponding entry in the classes array". To avoid duplicates we keep track in the info + // field of the Symbol of each CONSTANT_Class_info entry C whether an inner class entry has + // already been added for C. If so, we store the index of this inner class entry (plus one) in + // the info field. This trick allows duplicate detection in O(1) time. + Symbol nameSymbol = symbolTable.addConstantClass(name); + if (nameSymbol.info == 0) { + ++numberOfInnerClasses; + innerClasses.putShort(nameSymbol.index); + innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index); + innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName)); + innerClasses.putShort(access); + nameSymbol.info = numberOfInnerClasses; + } else { + // Compare the inner classes entry nameSymbol.info - 1 with the arguments of this method and + // throw an exception if there is a difference? + } + } + + @Override + public final FieldVisitor visitField( + final int access, + final String name, + final String descriptor, + final String signature, + final Object value) { + FieldWriter fieldWriter = + new FieldWriter(symbolTable, access, name, descriptor, signature, value); + if (firstField == null) { + firstField = fieldWriter; + } else { + lastField.fv = fieldWriter; + } + return lastField = fieldWriter; + } + + @Override + public final MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + MethodWriter methodWriter = + new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute); + if (firstMethod == null) { + firstMethod = methodWriter; + } else { + lastMethod.mv = methodWriter; + } + return lastMethod = methodWriter; + } + + @Override + public final void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Other public methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the content of the class file that was built by this ClassWriter. + * + * @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter. + */ + public byte[] toByteArray() { + // First step: compute the size in bytes of the ClassFile structure. + // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version, + // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count, + // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too. + int size = 24 + 2 * interfaceCount; + int fieldsCount = 0; + FieldWriter fieldWriter = firstField; + while (fieldWriter != null) { + ++fieldsCount; + size += fieldWriter.computeFieldInfoSize(); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + int methodsCount = 0; + MethodWriter methodWriter = firstMethod; + while (methodWriter != null) { + ++methodsCount; + size += methodWriter.computeMethodInfoSize(); + methodWriter = (MethodWriter) methodWriter.mv; + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (innerClasses != null) { + ++attributesCount; + size += 8 + innerClasses.length; + symbolTable.addConstantUtf8(Constants.INNER_CLASSES); + } + if (enclosingClassIndex != 0) { + ++attributesCount; + size += 10; + symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD); + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { + ++attributesCount; + size += 6; + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + } + if (signatureIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.SIGNATURE); + } + if (sourceFileIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.SOURCE_FILE); + } + if (debugExtension != null) { + ++attributesCount; + size += 6 + debugExtension.length; + symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributesCount; + size += 6; + symbolTable.addConstantUtf8(Constants.DEPRECATED); + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (symbolTable.computeBootstrapMethodsSize() > 0) { + ++attributesCount; + size += symbolTable.computeBootstrapMethodsSize(); + } + if (moduleWriter != null) { + attributesCount += moduleWriter.getAttributeCount(); + size += moduleWriter.computeAttributesSize(); + } + if (nestHostClassIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.NEST_HOST); + } + if (nestMemberClasses != null) { + ++attributesCount; + size += 8 + nestMemberClasses.length; + symbolTable.addConstantUtf8(Constants.NEST_MEMBERS); + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + size += firstAttribute.computeAttributesSize(symbolTable); + } + // IMPORTANT: this must be the last part of the ClassFile size computation, because the previous + // statements can add attribute names to the constant pool, thereby changing its size! + size += symbolTable.getConstantPoolLength(); + if (symbolTable.getConstantPoolCount() > 0xFFFF) { + throw new IndexOutOfBoundsException("Class file too large!"); + } + + // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in + // dynamic resizes) and fill it with the ClassFile content. + ByteVector result = new ByteVector(size); + result.putInt(0xCAFEBABE).putInt(version); + symbolTable.putConstantPool(result); + int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0; + result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass); + result.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + result.putShort(interfaces[i]); + } + result.putShort(fieldsCount); + fieldWriter = firstField; + while (fieldWriter != null) { + fieldWriter.putFieldInfo(result); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + result.putShort(methodsCount); + boolean hasFrames = false; + boolean hasAsmInstructions = false; + methodWriter = firstMethod; + while (methodWriter != null) { + hasFrames |= methodWriter.hasFrames(); + hasAsmInstructions |= methodWriter.hasAsmInstructions(); + methodWriter.putMethodInfo(result); + methodWriter = (MethodWriter) methodWriter.mv; + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + result.putShort(attributesCount); + if (innerClasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES)) + .putInt(innerClasses.length + 2) + .putShort(numberOfInnerClasses) + .putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (enclosingClassIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD)) + .putInt(4) + .putShort(enclosingClassIndex) + .putShort(enclosingMethodIndex); + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { + result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if (sourceFileIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE)) + .putInt(2) + .putShort(sourceFileIndex); + } + if (debugExtension != null) { + int length = debugExtension.length; + result + .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION)) + .putInt(length) + .putByteArray(debugExtension.data, 0, length); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + if (lastRuntimeVisibleAnnotation != null) { + lastRuntimeVisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), result); + } + if (lastRuntimeInvisibleAnnotation != null) { + lastRuntimeInvisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), result); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + lastRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), result); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + lastRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), result); + } + symbolTable.putBootstrapMethods(result); + if (moduleWriter != null) { + moduleWriter.putAttributes(result); + } + if (nestHostClassIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST)) + .putInt(2) + .putShort(nestHostClassIndex); + } + if (nestMemberClasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS)) + .putInt(nestMemberClasses.length + 2) + .putShort(numberOfNestMemberClasses) + .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length); + } + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, result); + } + + // Third step: replace the ASM specific instructions, if any. + if (hasAsmInstructions) { + return replaceAsmInstructions(result.data, hasFrames); + } else { + return result.data; + } + } + + /** + * Returns the equivalent of the given class file, with the ASM specific instructions replaced + * with standard ones. This is done with a ClassReader -> ClassWriter round trip. + * + * @param classFile a class file containing ASM specific instructions, generated by this + * ClassWriter. + * @param hasFrames whether there is at least one stack map frames in 'classFile'. + * @return an equivalent of 'classFile', with the ASM specific instructions replaced with standard + * ones. + */ + private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) { + Attribute[] attributes = getAttributePrototypes(); + firstField = null; + lastField = null; + firstMethod = null; + lastMethod = null; + lastRuntimeVisibleAnnotation = null; + lastRuntimeInvisibleAnnotation = null; + lastRuntimeVisibleTypeAnnotation = null; + lastRuntimeInvisibleTypeAnnotation = null; + moduleWriter = null; + nestHostClassIndex = 0; + numberOfNestMemberClasses = 0; + nestMemberClasses = null; + firstAttribute = null; + compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING; + new ClassReader(classFile, 0, /* checkClassVersion = */ false) + .accept( + this, + attributes, + (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS); + return toByteArray(); + } + + /** + * Returns the prototypes of the attributes used by this class, its fields and its methods. + * + * @return the prototypes of the attributes used by this class, its fields and its methods. + */ + private Attribute[] getAttributePrototypes() { + Attribute.Set attributePrototypes = new Attribute.Set(); + attributePrototypes.addAttributes(firstAttribute); + FieldWriter fieldWriter = firstField; + while (fieldWriter != null) { + fieldWriter.collectAttributePrototypes(attributePrototypes); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + MethodWriter methodWriter = firstMethod; + while (methodWriter != null) { + methodWriter.collectAttributePrototypes(attributePrototypes); + methodWriter = (MethodWriter) methodWriter.mv; + } + return attributePrototypes.toArray(); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: constant pool management for Attribute sub classes + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a number or string constant to the constant pool of the class being build. Does nothing if + * the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param value the value of the constant to be added to the constant pool. This parameter must be + * an {@link Integer}, a {@link Float}, a {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the given value. + */ + public int newConst(final Object value) { + return symbolTable.addConstant(value).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does nothing if the constant + * pool already contains a similar item. This method is intended for {@link Attribute} sub + * classes, and is normally not needed by class generators or adapters. + * + * @param value the String value. + * @return the index of a new or already existing UTF8 item. + */ + public int newUTF8(final String value) { + return symbolTable.addConstantUtf8(value); + } + + /** + * Adds a class reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param value the internal name of the class. + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return symbolTable.addConstantClass(value).index; + } + + /** + * Adds a method type reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param methodDescriptor method descriptor of the method type. + * @return the index of a new or already existing method type reference item. + */ + public int newMethodType(final String methodDescriptor) { + return symbolTable.addConstantMethodType(methodDescriptor).index; + } + + /** + * Adds a module reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param moduleName name of the module. + * @return the index of a new or already existing module reference item. + */ + public int newModule(final String moduleName) { + return symbolTable.addConstantModule(moduleName).index; + } + + /** + * Adds a package reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param packageName name of the package in its internal form. + * @return the index of a new or already existing module reference item. + */ + public int newPackage(final String packageName) { + return symbolTable.addConstantPackage(packageName).index; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool + * already contains a similar item. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link + * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class. + * @param name the name of the field or method. + * @param descriptor the descriptor of the field or method. + * @return the index of a new or already existing method type reference item. + * @deprecated this method is superseded by {@link #newHandle(int, String, String, String, + * boolean)}. + */ + @Deprecated + public int newHandle( + final int tag, final String owner, final String name, final String descriptor) { + return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool + * already contains a similar item. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link + * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class. + * @param name the name of the field or method. + * @param descriptor the descriptor of the field or method. + * @param isInterface true if the owner is an interface. + * @return the index of a new or already existing method type reference item. + */ + public int newHandle( + final int tag, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index; + } + + /** + * Adds a dynamic constant reference to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param name name of the invoked method. + * @param descriptor field descriptor of the constant type. + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. + * @return the index of a new or already existing dynamic constant reference item. + */ + public int newConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + return symbolTable.addConstantDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) + .index; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being build. Does nothing if + * the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param name name of the invoked method. + * @param descriptor descriptor of the invoke method. + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. + * @return the index of a new or already existing invokedynamic reference item. + */ + public int newInvokeDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + return symbolTable.addConstantInvokeDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) + .index; + } + + /** + * Adds a field reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param owner the internal name of the field's owner class. + * @param name the field's name. + * @param descriptor the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String descriptor) { + return symbolTable.addConstantFieldref(owner, name, descriptor).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param owner the internal name of the method's owner class. + * @param name the method's name. + * @param descriptor the method's descriptor. + * @param isInterface true if owner is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod( + final String owner, final String name, final String descriptor, final boolean isInterface) { + return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param name a name. + * @param descriptor a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String descriptor) { + return symbolTable.addConstantNameAndType(name, descriptor); + } + + // ----------------------------------------------------------------------------------------------- + // Default method to compute common super classes when computing stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the common super type of the two given types. The default implementation of this method + * loads the two given classes and uses the java.lang.Class methods to find the common + * super class. It can be overridden to compute this common super type in other ways, in + * particular without actually loading any class, or to take into account the class that is + * currently being generated by this ClassWriter, which can of course not be loaded since it is + * under construction. + * + * @param type1 the internal name of a class. + * @param type2 the internal name of another class. + * @return the internal name of the common super class of the two given classes. + */ + protected String getCommonSuperClass(final String type1, final String type2) { + ClassLoader classLoader = getClass().getClassLoader(); + Class class1; + try { + class1 = Class.forName(type1.replace('/', '.'), false, classLoader); + } catch (Exception e) { + throw new TypeNotPresentException(type1, e); + } + Class class2; + try { + class2 = Class.forName(type2.replace('/', '.'), false, classLoader); + } catch (Exception e) { + throw new TypeNotPresentException(type2, e); + } + if (class1.isAssignableFrom(class2)) { + return type1; + } + if (class2.isAssignableFrom(class1)) { + return type2; + } + if (class1.isInterface() || class2.isInterface()) { + return "java/lang/Object"; + } else { + do { + class1 = class1.getSuperclass(); + } while (!class1.isAssignableFrom(class2)); + return class1.getName().replace('.', '/'); + } + } } diff --git a/src/jvm/clojure/asm/ConstantDynamic.java b/src/jvm/clojure/asm/ConstantDynamic.java new file mode 100644 index 0000000000..d5306caf8a --- /dev/null +++ b/src/jvm/clojure/asm/ConstantDynamic.java @@ -0,0 +1,147 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +import java.util.Arrays; + +/** + * A constant whose value is computed at runtime, with a bootstrap method. + * + * @author Remi Forax + * @deprecated This API is experimental. + */ +@Deprecated +public final class ConstantDynamic { + + /** The constant name (can be arbitrary). */ + private final String name; + + /** The constant type (must be a field descriptor). */ + private final String descriptor; + + /** The bootstrap method to use to compute the constant value at runtime. */ + private final Handle bootstrapMethod; + + /** + * The arguments to pass to the bootstrap method, in order to compute the constant value at + * runtime. + */ + private final Object[] bootstrapMethodArguments; + + /** + * Constructs a new {@link ConstantDynamic}. + * + * @param name the constant name (can be arbitrary). + * @param descriptor the constant type (must be a field descriptor). + * @param bootstrapMethod the bootstrap method to use to compute the constant value at runtime. + * @param bootstrapMethodArguments the arguments to pass to the bootstrap method, in order to + * compute the constant value at runtime. + */ + public ConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethod, + final Object... bootstrapMethodArguments) { + this.name = name; + this.descriptor = descriptor; + this.bootstrapMethod = bootstrapMethod; + this.bootstrapMethodArguments = bootstrapMethodArguments; + } + + /** + * Returns the name of this constant. + * + * @return the name of this constant. + */ + public String getName() { + return name; + } + + /** + * Returns the type of this constant. + * + * @return the type of this constant, as a field descriptor. + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Returns the bootstrap method used to compute the value of this constant. + * + * @return the bootstrap method used to compute the value of this constant. + */ + public Handle getBootstrapMethod() { + return bootstrapMethod; + } + + /** + * Returns the arguments to pass to the bootstrap method, in order to compute the value of this + * constant. + * + * @return the arguments to pass to the bootstrap method, in order to compute the value of this + * constant. + */ + public Object[] getBootstrapMethodArguments() { + return bootstrapMethodArguments; + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ConstantDynamic)) { + return false; + } + ConstantDynamic constantDynamic = (ConstantDynamic) object; + return name.equals(constantDynamic.name) + && descriptor.equals(constantDynamic.descriptor) + && bootstrapMethod.equals(constantDynamic.bootstrapMethod) + && Arrays.equals(bootstrapMethodArguments, constantDynamic.bootstrapMethodArguments); + } + + @Override + public int hashCode() { + return name.hashCode() + ^ Integer.rotateLeft(descriptor.hashCode(), 8) + ^ Integer.rotateLeft(bootstrapMethod.hashCode(), 16) + ^ Integer.rotateLeft(Arrays.hashCode(bootstrapMethodArguments), 24); + } + + @Override + public String toString() { + return name + + " : " + + descriptor + + ' ' + + bootstrapMethod + + ' ' + + Arrays.toString(bootstrapMethodArguments); + } +} diff --git a/src/jvm/clojure/asm/Constants.java b/src/jvm/clojure/asm/Constants.java new file mode 100644 index 0000000000..38c5b87513 --- /dev/null +++ b/src/jvm/clojure/asm/Constants.java @@ -0,0 +1,177 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * Defines additional JVM opcodes, access flags and constants which are not part of the ASM public + * API. + * + * @see JVMS 6 + * @author Eric Bruneton + */ +final class Constants implements Opcodes { + + private Constants() {} + + // The ClassFile attribute names, in the order they are defined in + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300. + + static final String CONSTANT_VALUE = "ConstantValue"; + static final String CODE = "Code"; + static final String STACK_MAP_TABLE = "StackMapTable"; + static final String EXCEPTIONS = "Exceptions"; + static final String INNER_CLASSES = "InnerClasses"; + static final String ENCLOSING_METHOD = "EnclosingMethod"; + static final String SYNTHETIC = "Synthetic"; + static final String SIGNATURE = "Signature"; + static final String SOURCE_FILE = "SourceFile"; + static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension"; + static final String LINE_NUMBER_TABLE = "LineNumberTable"; + static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable"; + static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; + static final String DEPRECATED = "Deprecated"; + static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"; + static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations"; + static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations"; + static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = + "RuntimeInvisibleParameterAnnotations"; + static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations"; + static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations"; + static final String ANNOTATION_DEFAULT = "AnnotationDefault"; + static final String BOOTSTRAP_METHODS = "BootstrapMethods"; + static final String METHOD_PARAMETERS = "MethodParameters"; + static final String MODULE = "Module"; + static final String MODULE_PACKAGES = "ModulePackages"; + static final String MODULE_MAIN_CLASS = "ModuleMainClass"; + static final String NEST_HOST = "NestHost"; + static final String NEST_MEMBERS = "NestMembers"; + + // ASM specific access flags. + // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard + // access flags, and also to make sure that these flags are automatically filtered out when + // written in class files (because access flags are stored using 16 bits only). + + static final int ACC_CONSTRUCTOR = 0x40000; // method access flag. + + // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}. + + /** + * A frame inserted between already existing frames. This internal stack map frame type (in + * addition to the ones declared in {@link Opcodes}) can only be used if the frame content can be + * computed from the previous existing frame and from the instructions between this existing frame + * and the inserted one, without any knowledge of the type hierarchy. This kind of frame is only + * used when an unconditional jump is inserted in a method while expanding an ASM specific + * instruction. Keep in sync with Opcodes.java. + */ + static final int F_INSERT = 256; + + // The JVM opcode values which are not part of the ASM public API. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html. + + static final int LDC_W = 19; + static final int LDC2_W = 20; + static final int ILOAD_0 = 26; + static final int ILOAD_1 = 27; + static final int ILOAD_2 = 28; + static final int ILOAD_3 = 29; + static final int LLOAD_0 = 30; + static final int LLOAD_1 = 31; + static final int LLOAD_2 = 32; + static final int LLOAD_3 = 33; + static final int FLOAD_0 = 34; + static final int FLOAD_1 = 35; + static final int FLOAD_2 = 36; + static final int FLOAD_3 = 37; + static final int DLOAD_0 = 38; + static final int DLOAD_1 = 39; + static final int DLOAD_2 = 40; + static final int DLOAD_3 = 41; + static final int ALOAD_0 = 42; + static final int ALOAD_1 = 43; + static final int ALOAD_2 = 44; + static final int ALOAD_3 = 45; + static final int ISTORE_0 = 59; + static final int ISTORE_1 = 60; + static final int ISTORE_2 = 61; + static final int ISTORE_3 = 62; + static final int LSTORE_0 = 63; + static final int LSTORE_1 = 64; + static final int LSTORE_2 = 65; + static final int LSTORE_3 = 66; + static final int FSTORE_0 = 67; + static final int FSTORE_1 = 68; + static final int FSTORE_2 = 69; + static final int FSTORE_3 = 70; + static final int DSTORE_0 = 71; + static final int DSTORE_1 = 72; + static final int DSTORE_2 = 73; + static final int DSTORE_3 = 74; + static final int ASTORE_0 = 75; + static final int ASTORE_1 = 76; + static final int ASTORE_2 = 77; + static final int ASTORE_3 = 78; + static final int WIDE = 196; + static final int GOTO_W = 200; + static final int JSR_W = 201; + + // Constants to convert between normal and wide jump instructions. + + // The delta between the GOTO_W and JSR_W opcodes and GOTO and JUMP. + static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - GOTO; + + // Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa. + + // The delta between the ASM_IFEQ, ..., ASM_IF_ACMPNE, ASM_GOTO and ASM_JSR opcodes + // and IFEQ, ..., IF_ACMPNE, GOTO and JSR. + static final int ASM_OPCODE_DELTA = 49; + + // The delta between the ASM_IFNULL and ASM_IFNONNULL opcodes and IFNULL and IFNONNULL. + static final int ASM_IFNULL_OPCODE_DELTA = 20; + + // ASM specific opcodes, used for long forward jump instructions. + + static final int ASM_IFEQ = IFEQ + ASM_OPCODE_DELTA; + static final int ASM_IFNE = IFNE + ASM_OPCODE_DELTA; + static final int ASM_IFLT = IFLT + ASM_OPCODE_DELTA; + static final int ASM_IFGE = IFGE + ASM_OPCODE_DELTA; + static final int ASM_IFGT = IFGT + ASM_OPCODE_DELTA; + static final int ASM_IFLE = IFLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPEQ = IF_ICMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPNE = IF_ICMPNE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLT = IF_ICMPLT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGE = IF_ICMPGE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGT = IF_ICMPGT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLE = IF_ICMPLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPEQ = IF_ACMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPNE = IF_ACMPNE + ASM_OPCODE_DELTA; + static final int ASM_GOTO = GOTO + ASM_OPCODE_DELTA; + static final int ASM_JSR = JSR + ASM_OPCODE_DELTA; + static final int ASM_IFNULL = IFNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_IFNONNULL = IFNONNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_GOTO_W = 220; +} diff --git a/src/jvm/clojure/asm/Context.java b/src/jvm/clojure/asm/Context.java index d113ed96a9..5af8943ed7 100644 --- a/src/jvm/clojure/asm/Context.java +++ b/src/jvm/clojure/asm/Context.java @@ -1,32 +1,30 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; @@ -35,76 +33,105 @@ * * @author Eric Bruneton */ -class Context { - - /** - * Prototypes of the attributes that must be parsed for this class. - */ - Attribute[] attrs; - - /** - * The {@link ClassReader} option flags for the parsing of this class. - */ - int flags; - - /** - * The buffer used to read strings. - */ - char[] buffer; - - /** - * The start index of each bootstrap method. - */ - int[] bootstrapMethods; - - /** - * The access flags of the method currently being parsed. - */ - int access; - - /** - * The name of the method currently being parsed. - */ - String name; - - /** - * The descriptor of the method currently being parsed. - */ - String desc; - - /** - * The offset of the latest stack map frame that has been parsed. - */ - int offset; - - /** - * The encoding of the latest stack map frame that has been parsed. - */ - int mode; - - /** - * The number of locals in the latest stack map frame that has been parsed. - */ - int localCount; - - /** - * The number locals in the latest stack map frame that has been parsed, - * minus the number of locals in the previous frame. - */ - int localDiff; - - /** - * The local values of the latest stack map frame that has been parsed. - */ - Object[] local; - - /** - * The stack size of the latest stack map frame that has been parsed. - */ - int stackCount; - - /** - * The stack values of the latest stack map frame that has been parsed. - */ - Object[] stack; -} \ No newline at end of file +final class Context { + + /** The prototypes of the attributes that must be parsed in this class. */ + Attribute[] attributePrototypes; + + /** + * The options used to parse this class. One or more of {@link ClassReader#SKIP_CODE}, {@link + * ClassReader#SKIP_DEBUG}, {@link ClassReader#SKIP_FRAMES}, {@link ClassReader#EXPAND_FRAMES} or + * {@link ClassReader#EXPAND_ASM_INSNS}. + */ + int parsingOptions; + + /** The buffer used to read strings in the constant pool. */ + char[] charBuffer; + + // Information about the current method, i.e. the one read in the current (or latest) call + // to {@link ClassReader#readMethod()}. + + /** The access flags of the current method. */ + int currentMethodAccessFlags; + + /** The name of the current method. */ + String currentMethodName; + + /** The descriptor of the current method. */ + String currentMethodDescriptor; + + /** + * The labels of the current method, indexed by bytecode offset (only bytecode offsets for which a + * label is needed have a non null associated Label). + */ + Label[] currentMethodLabels; + + // Information about the current type annotation target, i.e. the one read in the current + // (or latest) call to {@link ClassReader#readAnnotationTarget()}. + + /** + * The target_type and target_info of the current type annotation target, encoded as described in + * {@link TypeReference}. + */ + int currentTypeAnnotationTarget; + + /** The target_path of the current type annotation target. */ + TypePath currentTypeAnnotationTargetPath; + + /** The start of each local variable range in the current local variable annotation. */ + Label[] currentLocalVariableAnnotationRangeStarts; + + /** The end of each local variable range in the current local variable annotation. */ + Label[] currentLocalVariableAnnotationRangeEnds; + + /** + * The local variable index of each local variable range in the current local variable annotation. + */ + int[] currentLocalVariableAnnotationRangeIndices; + + // Information about the current stack map frame, i.e. the one read in the current (or latest) + // call to {@link ClassReader#readFrame()}. + + /** The bytecode offset of the current stack map frame. */ + int currentFrameOffset; + + /** + * The type of the current stack map frame. One of {@link Opcodes#F_FULL}, {@link + * Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or {@link Opcodes#F_SAME1}. + */ + int currentFrameType; + + /** + * The number of local variable types in the current stack map frame. Each type is represented + * with a single array element (even long and double). + */ + int currentFrameLocalCount; + + /** + * The delta number of local variable types in the current stack map frame (each type is + * represented with a single array element - even long and double). This is the number of local + * variable types in this frame, minus the number of local variable types in the previous frame. + */ + int currentFrameLocalCountDelta; + + /** + * The types of the local variables in the current stack map frame. Each type is represented with + * a single array element (even long and double), using the format described in {@link + * MethodVisitor#visitFrame}. Depending on {@link #currentFrameType}, this contains the types of + * all the local variables, or only those of the additional ones (compared to the previous frame). + */ + Object[] currentFrameLocalTypes; + + /** + * The number stack element types in the current stack map frame. Each type is represented with a + * single array element (even long and double). + */ + int currentFrameStackCount; + + /** + * The types of the stack elements in the current stack map frame. Each type is represented with a + * single array element (even long and double), using the format described in {@link + * MethodVisitor#visitFrame}. + */ + Object[] currentFrameStackTypes; +} diff --git a/src/jvm/clojure/asm/CurrentFrame.java b/src/jvm/clojure/asm/CurrentFrame.java new file mode 100644 index 0000000000..f37b51cfe8 --- /dev/null +++ b/src/jvm/clojure/asm/CurrentFrame.java @@ -0,0 +1,56 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package clojure.asm; + +/** + * Information about the input stack map frame at the "current" instruction of a method. This is + * implemented as a Frame subclass for a "basic block" containing only one instruction. + * + * @author Eric Bruneton + */ +final class CurrentFrame extends Frame { + + CurrentFrame(final Label owner) { + super(owner); + } + + /** + * Sets this CurrentFrame to the input stack map frame of the next "current" instruction, i.e. the + * instruction just after the given one. It is assumed that the value of this object when this + * method is called is the stack map frame status just before the given instruction is executed. + */ + @Override + void execute( + final int opcode, final int arg, final Symbol symbolArg, final SymbolTable symbolTable) { + super.execute(opcode, arg, symbolArg, symbolTable); + Frame successor = new Frame(null); + merge(symbolTable, successor, 0); + copyFrom(successor); + } +} diff --git a/src/jvm/clojure/asm/Edge.java b/src/jvm/clojure/asm/Edge.java index 045cd46fc1..f9bdadf90b 100644 --- a/src/jvm/clojure/asm/Edge.java +++ b/src/jvm/clojure/asm/Edge.java @@ -1,75 +1,91 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * An edge in the control flow graph of a method body. See {@link Label Label}. + * An edge in the control flow graph of a method. Each node of this graph is a basic block, + * represented with the Label corresponding to its first instruction. Each edge goes from one node + * to another, i.e. from one basic block to another (called the predecessor and successor blocks, + * respectively). An edge corresponds either to a jump or ret instruction or to an exception + * handler. * + * @see Label * @author Eric Bruneton */ -class Edge { +final class Edge { + + /** + * A control flow graph edge corresponding to a jump or ret instruction. Only used with {@link + * ClassWriter#COMPUTE_FRAMES}. + */ + static final int JUMP = 0; - /** - * Denotes a normal control flow graph edge. - */ - static final int NORMAL = 0; + /** + * A control flow graph edge corresponding to an exception handler. Only used with {@link + * ClassWriter#COMPUTE_MAXS}. + */ + static final int EXCEPTION = 0x7FFFFFFF; - /** - * Denotes a control flow graph edge corresponding to an exception handler. - * More precisely any {@link Edge} whose {@link #info} is strictly positive - * corresponds to an exception handler. The actual value of {@link #info} is - * the index, in the {@link ClassWriter} type table, of the exception that - * is catched. - */ - static final int EXCEPTION = 0x7FFFFFFF; + /** + * Information about this control flow graph edge. + * + *
    + *
  • If {@link ClassWriter#COMPUTE_MAXS} is used, this field contains either a stack size + * delta (for an edge corresponding to a jump instruction), or the value EXCEPTION (for an + * edge corresponding to an exception handler). The stack size delta is the stack size just + * after the jump instruction, minus the stack size at the beginning of the predecessor + * basic block, i.e. the one containing the jump instruction. + *
  • If {@link ClassWriter#COMPUTE_FRAMES} is used, this field contains either the value JUMP + * (for an edge corresponding to a jump instruction), or the index, in the {@link + * ClassWriter} type table, of the exception type that is handled (for an edge corresponding + * to an exception handler). + *
+ */ + final int info; - /** - * Information about this control flow graph edge. If - * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative) - * stack size in the basic block from which this edge originates. This size - * is equal to the stack size at the "jump" instruction to which this edge - * corresponds, relatively to the stack size at the beginning of the - * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used, - * this field is the kind of this control flow graph edge (i.e. NORMAL or - * EXCEPTION). - */ - int info; + /** The successor block of this control flow graph edge. */ + final Label successor; - /** - * The successor block of the basic block from which this edge originates. - */ - Label successor; + /** + * The next edge in the list of outgoing edges of a basic block. See {@link Label#outgoingEdges}. + */ + Edge nextEdge; - /** - * The next edge in the list of successors of the originating basic block. - * See {@link Label#successors successors}. - */ - Edge next; + /** + * Constructs a new Edge. + * + * @param info see {@link #info}. + * @param successor see {@link #successor}. + * @param nextEdge see {@link #nextEdge}. + */ + Edge(final int info, final Label successor, final Edge nextEdge) { + this.info = info; + this.successor = successor; + this.nextEdge = nextEdge; + } } diff --git a/src/jvm/clojure/asm/FieldVisitor.java b/src/jvm/clojure/asm/FieldVisitor.java index b9df7385fe..f2f0e2ce8d 100644 --- a/src/jvm/clojure/asm/FieldVisitor.java +++ b/src/jvm/clojure/asm/FieldVisitor.java @@ -1,121 +1,138 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A visitor to visit a Java field. The methods of this class must be called in - * the following order: ( visitAnnotation | visitAttribute )* + * A visitor to visit a Java field. The methods of this class must be called in the following order: + * ( visitAnnotation | visitTypeAnnotation | visitAttribute )* * visitEnd. * * @author Eric Bruneton */ public abstract class FieldVisitor { - /** - * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. - */ - protected final int api; + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + */ + protected final int api; - /** - * The field visitor to which this visitor must delegate method calls. May - * be null. - */ - protected FieldVisitor fv; + /** The field visitor to which this visitor must delegate method calls. May be null. */ + protected FieldVisitor fv; - /** - * Constructs a new {@link FieldVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - */ - public FieldVisitor(final int api) { - this(api, null); + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + */ + public FieldVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be + * null. + */ + public FieldVisitor(final int api, final FieldVisitor fieldVisitor) { + if (api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM7_EXPERIMENTAL) { + throw new IllegalArgumentException(); } + this.api = api; + this.fv = fieldVisitor; + } - /** - * Constructs a new {@link FieldVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - * @param fv - * the field visitor to which this visitor must delegate method - * calls. May be null. - */ - public FieldVisitor(final int api, final FieldVisitor fv) { - if (api != Opcodes.ASM4) { - throw new IllegalArgumentException(); - } - this.api = api; - this.fv = fv; + /** + * Visits an annotation of the field. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (fv != null) { + return fv.visitAnnotation(descriptor, visible); } + return null; + } - /** - * Visits an annotation of the field. - * - * @param desc - * the class descriptor of the annotation class. - * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if - * this visitor is not interested in visiting this annotation. - */ - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - if (fv != null) { - return fv.visitAnnotation(desc, visible); - } - return null; + /** + * Visits an annotation on the type of the field. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#FIELD}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be null if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(); + } + if (fv != null) { + return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); } + return null; + } - /** - * Visits a non standard attribute of the field. - * - * @param attr - * an attribute. - */ - public void visitAttribute(Attribute attr) { - if (fv != null) { - fv.visitAttribute(attr); - } + /** + * Visits a non standard attribute of the field. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (fv != null) { + fv.visitAttribute(attribute); } + } - /** - * Visits the end of the field. This method, which is the last one to be - * called, is used to inform the visitor that all the annotations and - * attributes of the field have been visited. - */ - public void visitEnd() { - if (fv != null) { - fv.visitEnd(); - } + /** + * Visits the end of the field. This method, which is the last one to be called, is used to inform + * the visitor that all the annotations and attributes of the field have been visited. + */ + public void visitEnd() { + if (fv != null) { + fv.visitEnd(); } + } } diff --git a/src/jvm/clojure/asm/FieldWriter.java b/src/jvm/clojure/asm/FieldWriter.java index ccd32ec0a8..ed6449bf69 100644 --- a/src/jvm/clojure/asm/FieldWriter.java +++ b/src/jvm/clojure/asm/FieldWriter.java @@ -1,273 +1,346 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * An {@link FieldVisitor} that generates Java fields in bytecode form. + * A {@link FieldVisitor} that generates a corresponding 'field_info' structure, as defined in the + * Java Virtual Machine Specification (JVMS). * + * @see JVMS + * 4.5 * @author Eric Bruneton */ final class FieldWriter extends FieldVisitor { - /** - * The class writer to which this field must be added. - */ - private final ClassWriter cw; + /** Where the constants used in this FieldWriter must be stored. */ + private final SymbolTable symbolTable; - /** - * Access flags of this field. - */ - private final int access; + // Note: fields are ordered as in the field_info structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. - /** - * The index of the constant pool item that contains the name of this - * method. - */ - private final int name; + /** + * The access_flags field of the field_info JVMS structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the + * ClassFile structure. + */ + private final int accessFlags; - /** - * The index of the constant pool item that contains the descriptor of this - * field. - */ - private final int desc; + /** The name_index field of the field_info JVMS structure. */ + private final int nameIndex; - /** - * The index of the constant pool item that contains the signature of this - * field. - */ - private int signature; + /** The descriptor_index field of the field_info JVMS structure. */ + private final int descriptorIndex; - /** - * The index of the constant pool item that contains the constant value of - * this field. - */ - private int value; + /** + * The signature_index field of the Signature attribute of this field_info, or 0 if there is no + * Signature attribute. + */ + private int signatureIndex; - /** - * The runtime visible annotations of this field. May be null. - */ - private AnnotationWriter anns; + /** + * The constantvalue_index field of the ConstantValue attribute of this field_info, or 0 if there + * is no ConstantValue attribute. + */ + private int constantValueIndex; - /** - * The runtime invisible annotations of this field. May be null. - */ - private AnnotationWriter ianns; + /** + * The last runtime visible annotation of this field. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; - /** - * The non standard attributes of this field. May be null. - */ - private Attribute attrs; + /** + * The last runtime invisible annotation of this field. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; - // ------------------------------------------------------------------------ - // Constructor - // ------------------------------------------------------------------------ + /** + * The last runtime visible type annotation of this field. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; - /** - * Constructs a new {@link FieldWriter}. - * - * @param cw - * the class writer to which this field must be added. - * @param access - * the field's access flags (see {@link Opcodes}). - * @param name - * the field's name. - * @param desc - * the field's descriptor (see {@link Type}). - * @param signature - * the field's signature. May be null. - * @param value - * the field's constant value. May be null. - */ - FieldWriter(final ClassWriter cw, final int access, final String name, - final String desc, final String signature, final Object value) { - super(Opcodes.ASM4); - if (cw.firstField == null) { - cw.firstField = this; - } else { - cw.lastField.fv = this; - } - cw.lastField = this; - this.cw = cw; - this.access = access; - this.name = cw.newUTF8(name); - this.desc = cw.newUTF8(desc); - if (ClassReader.SIGNATURES && signature != null) { - this.signature = cw.newUTF8(signature); - } - if (value != null) { - this.value = cw.newConstItem(value).index; - } - } + /** + * The last runtime invisible type annotation of this field. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of this field. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be null. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putFieldInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; - // ------------------------------------------------------------------------ - // Implementation of the FieldVisitor abstract class - // ------------------------------------------------------------------------ + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- - @Override - public AnnotationVisitor visitAnnotation(final String desc, - final boolean visible) { - if (!ClassReader.ANNOTATIONS) { - return null; - } - ByteVector bv = new ByteVector(); - // write type, and reserve space for values count - bv.putShort(cw.newUTF8(desc)).putShort(0); - AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); - if (visible) { - aw.next = anns; - anns = aw; - } else { - aw.next = ianns; - ianns = aw; - } - return aw; + /** + * Constructs a new {@link FieldWriter}. + * + * @param symbolTable where the constants used in this FieldWriter must be stored. + * @param access the field's access flags (see {@link Opcodes}). + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be null. + * @param constantValue the field's constant value. May be null. + */ + FieldWriter( + final SymbolTable symbolTable, + final int access, + final String name, + final String descriptor, + final String signature, + final Object constantValue) { + super(Opcodes.ASM6); + this.symbolTable = symbolTable; + this.accessFlags = access; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); } + if (constantValue != null) { + this.constantValueIndex = symbolTable.addConstant(constantValue).index; + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the FieldVisitor abstract class + // ----------------------------------------------------------------------------------------------- - @Override - public void visitAttribute(final Attribute attr) { - attr.next = attrs; - attrs = attr; + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + // Create a ByteVector to hold an 'annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. + ByteVector annotation = new ByteVector(); + // Write type_index and reserve space for num_element_value_pairs. + annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastRuntimeVisibleAnnotation = + new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation); } + } - @Override - public void visitEnd() { + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget(typeRef, typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation); } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public void visitEnd() { + // Nothing to do. + } - // ------------------------------------------------------------------------ - // Utility methods - // ------------------------------------------------------------------------ + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- - /** - * Returns the size of this field. - * - * @return the size of this field. - */ - int getSize() { - int size = 8; - if (value != 0) { - cw.newUTF8("ConstantValue"); - size += 8; - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((cw.version & 0xFFFF) < Opcodes.V1_5 - || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { - cw.newUTF8("Synthetic"); - size += 6; - } - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - cw.newUTF8("Deprecated"); - size += 6; - } - if (ClassReader.SIGNATURES && signature != 0) { - cw.newUTF8("Signature"); - size += 8; - } - if (ClassReader.ANNOTATIONS && anns != null) { - cw.newUTF8("RuntimeVisibleAnnotations"); - size += 8 + anns.getSize(); - } - if (ClassReader.ANNOTATIONS && ianns != null) { - cw.newUTF8("RuntimeInvisibleAnnotations"); - size += 8 + ianns.getSize(); - } - if (attrs != null) { - size += attrs.getSize(cw, null, 0, -1, -1); - } - return size; + /** + * Returns the size of the field_info JVMS structure generated by this FieldWriter. Also adds the + * names of the attributes of this field in the constant pool. + * + * @return the size in bytes of the field_info JVMS structure. + */ + int computeFieldInfoSize() { + // The access_flags, name_index, descriptor_index and attributes_count fields use 8 bytes. + int size = 8; + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (constantValueIndex != 0) { + // ConstantValue attributes always use 8 bytes. + symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE); + size += 8; } + // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 + && symbolTable.getMajorVersion() < Opcodes.V1_5) { + // Synthetic attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + size += 6; + } + if (signatureIndex != 0) { + // Signature attributes always use 8 bytes. + symbolTable.addConstantUtf8(Constants.SIGNATURE); + size += 8; + } + // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead. + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + // Deprecated attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.DEPRECATED); + size += 6; + } + if (lastRuntimeVisibleAnnotation != null) { + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } - /** - * Puts the content of this field into the given byte vector. - * - * @param out - * where the content of this field must be put. - */ - void put(final ByteVector out) { - final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; - int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE - | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); - out.putShort(access & ~mask).putShort(name).putShort(desc); - int attributeCount = 0; - if (value != 0) { - ++attributeCount; - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((cw.version & 0xFFFF) < Opcodes.V1_5 - || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { - ++attributeCount; - } - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - ++attributeCount; - } - if (ClassReader.SIGNATURES && signature != 0) { - ++attributeCount; - } - if (ClassReader.ANNOTATIONS && anns != null) { - ++attributeCount; - } - if (ClassReader.ANNOTATIONS && ianns != null) { - ++attributeCount; - } - if (attrs != null) { - attributeCount += attrs.getCount(); - } - out.putShort(attributeCount); - if (value != 0) { - out.putShort(cw.newUTF8("ConstantValue")); - out.putInt(2).putShort(value); - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((cw.version & 0xFFFF) < Opcodes.V1_5 - || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { - out.putShort(cw.newUTF8("Synthetic")).putInt(0); - } - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - out.putShort(cw.newUTF8("Deprecated")).putInt(0); - } - if (ClassReader.SIGNATURES && signature != 0) { - out.putShort(cw.newUTF8("Signature")); - out.putInt(2).putShort(signature); - } - if (ClassReader.ANNOTATIONS && anns != null) { - out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); - anns.put(out); - } - if (ClassReader.ANNOTATIONS && ianns != null) { - out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); - ianns.put(out); - } - if (attrs != null) { - attrs.put(cw, null, 0, -1, -1, out); - } + /** + * Puts the content of the field_info JVMS structure generated by this FieldWriter into the given + * ByteVector. + * + * @param output where the field_info structure must be put. + */ + void putFieldInfo(final ByteVector output) { + boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; + // Put the access_flags, name_index and descriptor_index fields. + int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0; + output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); + // Compute and put the attributes_count field. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (constantValueIndex != 0) { + ++attributesCount; + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + ++attributesCount; } + if (signatureIndex != 0) { + ++attributesCount; + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributesCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + } + output.putShort(attributesCount); + // Put the field_info attributes. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (constantValueIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE)) + .putInt(2) + .putShort(constantValueIndex); + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + if (lastRuntimeVisibleAnnotation != null) { + lastRuntimeVisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleAnnotation != null) { + lastRuntimeInvisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + lastRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + lastRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this field into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + } } diff --git a/src/jvm/clojure/asm/Frame.java b/src/jvm/clojure/asm/Frame.java index 9c7e61dc8d..2c67f8cce2 100644 --- a/src/jvm/clojure/asm/Frame.java +++ b/src/jvm/clojure/asm/Frame.java @@ -1,1453 +1,1467 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * Information about the input and output stack map frames of a basic block. + * The input and output stack map frames of a basic block. + * + *

Stack map frames are computed in two steps: + * + *

    + *
  • During the visit of each instruction in MethodWriter, the state of the frame at the end of + * the current basic block is updated by simulating the action of the instruction on the + * previous state of this so called "output frame". + *
  • After all instructions have been visited, a fix point algorithm is used in MethodWriter to + * compute the "input frame" of each basic block (i.e. the stack map frame at the beginning of + * the basic block). See {@link MethodWriter#computeAllFrames}. + *
+ * + *

Output stack map frames are computed relatively to the input frame of the basic block, which + * is not yet known when output frames are computed. It is therefore necessary to be able to + * represent abstract types such as "the type at position x in the input frame locals" or "the type + * at position x from the top of the input frame stack" or even "the type at position x in the input + * frame, with y more (or less) array dimensions". This explains the rather complicated type format + * used in this class, explained below. + * + *

The local variables and the operand stack of input and output frames contain values called + * "abstract types" hereafter. An abstract type is represented with 4 fields named DIM, KIND, FLAGS + * and VALUE, packed in a single int value for better performance and memory efficiency: + * + *

+ *   =====================================
+ *   |.DIM|KIND|FLAG|...............VALUE|
+ *   =====================================
+ * 
+ * + *
    + *
  • the DIM field, stored in the 4 most significant bits, is a signed number of array + * dimensions (from -8 to 7, included). It can be retrieved with {@link #DIM_MASK} and a right + * shift of {@link #DIM_SHIFT}. + *
  • the KIND field, stored in 4 bits, indicates the kind of VALUE used. These 4 bits can be + * retrieved with {@link #KIND_MASK} and, without any shift, must be equal to {@link + * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link #LOCAL_KIND} + * or {@link #STACK_KIND}. + *
  • the FLAGS field, stored in 4 bits, contains up to 4 boolean flags. Currently only one flag + * is defined, namely {@link #TOP_IF_LONG_OR_DOUBLE_FLAG}. + *
  • the VALUE field, stored in the remaining 20 bits, contains either + *
      + *
    • one of the constants {@link #ITEM_TOP}, {@link #ITEM_ASM_BOOLEAN}, {@link + * #ITEM_ASM_BYTE}, {@link #ITEM_ASM_CHAR} or {@link #ITEM_ASM_SHORT}, {@link + * #ITEM_INTEGER}, {@link #ITEM_FLOAT}, {@link #ITEM_LONG}, {@link #ITEM_DOUBLE}, {@link + * #ITEM_NULL} or {@link #ITEM_UNINITIALIZED_THIS}, if KIND is equal to {@link + * #CONSTANT_KIND}. + *
    • the index of a {@link Symbol#TYPE_TAG} {@link Symbol} in the type table of a {@link + * SymbolTable}, if KIND is equal to {@link #REFERENCE_KIND}. + *
    • the index of an {@link Symbol#UNINITIALIZED_TYPE_TAG} {@link Symbol} in the type + * table of a SymbolTable, if KIND is equal to {@link #UNINITIALIZED_KIND}. + *
    • the index of a local variable in the input stack frame, if KIND is equal to {@link + * #LOCAL_KIND}. + *
    • a position relatively to the top of the stack of the input stack frame, if KIND is + * equal to {@link #STACK_KIND}, + *
    + *
+ * + *

Output frames can contain abstract types of any kind and with a positive or negative array + * dimension (and even unassigned types, represented by 0 - which does not correspond to any valid + * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND or + * UNINITIALIZED_KIND abstract types of positive or null array dimension. In all cases the type + * table contains only internal type names (array type descriptors are forbidden - array dimensions + * must be represented through the DIM field). + * + *

The LONG and DOUBLE types are always represented by using two slots (LONG + TOP or DOUBLE + + * TOP), for local variables as well as in the operand stack. This is necessary to be able to + * simulate DUPx_y instructions, whose effect would be dependent on the concrete types represented + * by the abstract types in the stack (which are not always known). * * @author Eric Bruneton */ -final class Frame { +class Frame { - /* - * Frames are computed in a two steps process: during the visit of each - * instruction, the state of the frame at the end of current basic block is - * updated by simulating the action of the instruction on the previous state - * of this so called "output frame". In visitMaxs, a fix point algorithm is - * used to compute the "input frame" of each basic block, i.e. the stack map - * frame at the beginning of the basic block, starting from the input frame - * of the first basic block (which is computed from the method descriptor), - * and by using the previously computed output frames to compute the input - * state of the other blocks. - * - * All output and input frames are stored as arrays of integers. Reference - * and array types are represented by an index into a type table (which is - * not the same as the constant pool of the class, in order to avoid adding - * unnecessary constants in the pool - not all computed frames will end up - * being stored in the stack map table). This allows very fast type - * comparisons. - * - * Output stack map frames are computed relatively to the input frame of the - * basic block, which is not yet known when output frames are computed. It - * is therefore necessary to be able to represent abstract types such as - * "the type at position x in the input frame locals" or "the type at - * position x from the top of the input frame stack" or even "the type at - * position x in the input frame, with y more (or less) array dimensions". - * This explains the rather complicated type format used in output frames. - * - * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a - * signed number of array dimensions (from -8 to 7). KIND is either BASE, - * LOCAL or STACK. BASE is used for types that are not relative to the input - * frame. LOCAL is used for types that are relative to the input local - * variable types. STACK is used for types that are relative to the input - * stack types. VALUE depends on KIND. For LOCAL types, it is an index in - * the input local variable types. For STACK types, it is a position - * relatively to the top of input frame stack. For BASE types, it is either - * one of the constants defined in FrameVisitor, or for OBJECT and - * UNINITIALIZED types, a tag and an index in the type table. - * - * Output frames can contain types of any kind and with a positive or - * negative dimension (and even unassigned types, represented by 0 - which - * does not correspond to any valid type value). Input frames can only - * contain BASE types of positive or null dimension. In all cases the type - * table contains only internal type names (array type descriptors are - * forbidden - dimensions must be represented through the DIM field). - * - * The LONG and DOUBLE types are always represented by using two slots (LONG - * + TOP or DOUBLE + TOP), for local variable types as well as in the - * operand stack. This is necessary to be able to simulate DUPx_y - * instructions, whose effect would be dependent on the actual type values - * if types were always represented by a single slot in the stack (and this - * is not possible, since actual type values are not always known - cf LOCAL - * and STACK type kinds). - */ + // Constants used in the StackMapTable attribute. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.4. - /** - * Mask to get the dimension of a frame type. This dimension is a signed - * integer between -8 and 7. - */ - static final int DIM = 0xF0000000; + static final int SAME_FRAME = 0; + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; + static final int RESERVED = 128; + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; + static final int CHOP_FRAME = 248; + static final int SAME_FRAME_EXTENDED = 251; + static final int APPEND_FRAME = 252; + static final int FULL_FRAME = 255; - /** - * Constant to be added to a type to get a type with one more dimension. - */ - static final int ARRAY_OF = 0x10000000; + static final int ITEM_TOP = 0; + static final int ITEM_INTEGER = 1; + static final int ITEM_FLOAT = 2; + static final int ITEM_DOUBLE = 3; + static final int ITEM_LONG = 4; + static final int ITEM_NULL = 5; + static final int ITEM_UNINITIALIZED_THIS = 6; + static final int ITEM_OBJECT = 7; + static final int ITEM_UNINITIALIZED = 8; + // Additional, ASM specific constants used in abstract types below. + private static final int ITEM_ASM_BOOLEAN = 9; + private static final int ITEM_ASM_BYTE = 10; + private static final int ITEM_ASM_CHAR = 11; + private static final int ITEM_ASM_SHORT = 12; - /** - * Constant to be added to a type to get a type with one less dimension. - */ - static final int ELEMENT_OF = 0xF0000000; + // Bitmasks to get each field of an abstract type. - /** - * Mask to get the kind of a frame type. - * - * @see #BASE - * @see #LOCAL - * @see #STACK - */ - static final int KIND = 0xF000000; + private static final int DIM_MASK = 0xF0000000; + private static final int KIND_MASK = 0x0F000000; + private static final int FLAGS_MASK = 0x00F00000; + private static final int VALUE_MASK = 0x000FFFFF; - /** - * Flag used for LOCAL and STACK types. Indicates that if this type happens - * to be a long or double type (during the computations of input frames), - * then it must be set to TOP because the second word of this value has been - * reused to store other data in the basic block. Hence the first word no - * longer stores a valid long or double value. - */ - static final int TOP_IF_LONG_OR_DOUBLE = 0x800000; + // Constants to manipulate the DIM field of an abstract type. - /** - * Mask to get the value of a frame type. - */ - static final int VALUE = 0x7FFFFF; + /** The number of right shift bits to use to get the array dimensions of an abstract type. */ + private static final int DIM_SHIFT = 28; - /** - * Mask to get the kind of base types. - */ - static final int BASE_KIND = 0xFF00000; + /** The constant to be added to an abstract type to get one with one more array dimension. */ + private static final int ARRAY_OF = +1 << DIM_SHIFT; - /** - * Mask to get the value of base types. - */ - static final int BASE_VALUE = 0xFFFFF; + /** The constant to be added to an abstract type to get one with one less array dimension. */ + private static final int ELEMENT_OF = -1 << DIM_SHIFT; - /** - * Kind of the types that are not relative to an input stack map frame. - */ - static final int BASE = 0x1000000; + // Possible values for the KIND field of an abstract type. - /** - * Base kind of the base reference types. The BASE_VALUE of such types is an - * index into the type table. - */ - static final int OBJECT = BASE | 0x700000; + private static final int CONSTANT_KIND = 0x01000000; + private static final int REFERENCE_KIND = 0x02000000; + private static final int UNINITIALIZED_KIND = 0x03000000; + private static final int LOCAL_KIND = 0x04000000; + private static final int STACK_KIND = 0x05000000; - /** - * Base kind of the uninitialized base types. The BASE_VALUE of such types - * in an index into the type table (the Item at that index contains both an - * instruction offset and an internal class name). - */ - static final int UNINITIALIZED = BASE | 0x800000; + // Possible flags for the FLAGS field of an abstract type. - /** - * Kind of the types that are relative to the local variable types of an - * input stack map frame. The value of such types is a local variable index. - */ - private static final int LOCAL = 0x2000000; + /** + * A flag used for LOCAL_KIND and STACK_KIND abstract types, indicating that if the resolved, + * concrete type is LONG or DOUBLE, TOP should be used instead (because the value has been + * partially overridden with an xSTORE instruction). + */ + private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 0x00100000 & FLAGS_MASK; - /** - * Kind of the the types that are relative to the stack of an input stack - * map frame. The value of such types is a position relatively to the top of - * this stack. - */ - private static final int STACK = 0x3000000; + // Useful predefined abstract types (all the possible CONSTANT_KIND types). - /** - * The TOP type. This is a BASE type. - */ - static final int TOP = BASE | 0; + private static final int TOP = CONSTANT_KIND | ITEM_TOP; + private static final int BOOLEAN = CONSTANT_KIND | ITEM_ASM_BOOLEAN; + private static final int BYTE = CONSTANT_KIND | ITEM_ASM_BYTE; + private static final int CHAR = CONSTANT_KIND | ITEM_ASM_CHAR; + private static final int SHORT = CONSTANT_KIND | ITEM_ASM_SHORT; + private static final int INTEGER = CONSTANT_KIND | ITEM_INTEGER; + private static final int FLOAT = CONSTANT_KIND | ITEM_FLOAT; + private static final int LONG = CONSTANT_KIND | ITEM_LONG; + private static final int DOUBLE = CONSTANT_KIND | ITEM_DOUBLE; + private static final int NULL = CONSTANT_KIND | ITEM_NULL; + private static final int UNINITIALIZED_THIS = CONSTANT_KIND | ITEM_UNINITIALIZED_THIS; - /** - * The BOOLEAN type. This is a BASE type mainly used for array types. - */ - static final int BOOLEAN = BASE | 9; + // ----------------------------------------------------------------------------------------------- + // Instance fields + // ----------------------------------------------------------------------------------------------- - /** - * The BYTE type. This is a BASE type mainly used for array types. - */ - static final int BYTE = BASE | 10; + /** The basic block to which these input and output stack map frames correspond. */ + Label owner; - /** - * The CHAR type. This is a BASE type mainly used for array types. - */ - static final int CHAR = BASE | 11; + /** The input stack map frame locals. This is an array of abstract types. */ + private int[] inputLocals; - /** - * The SHORT type. This is a BASE type mainly used for array types. - */ - static final int SHORT = BASE | 12; + /** The input stack map frame stack. This is an array of abstract types. */ + private int[] inputStack; - /** - * The INTEGER type. This is a BASE type. - */ - static final int INTEGER = BASE | 1; + /** The output stack map frame locals. This is an array of abstract types. */ + private int[] outputLocals; - /** - * The FLOAT type. This is a BASE type. - */ - static final int FLOAT = BASE | 2; + /** The output stack map frame stack. This is an array of abstract types. */ + private int[] outputStack; - /** - * The DOUBLE type. This is a BASE type. - */ - static final int DOUBLE = BASE | 3; + /** + * The start of the output stack, relatively to the input stack. This offset is always negative or + * null. A null offset means that the output stack must be appended to the input stack. A -n + * offset means that the first n output stack elements must replace the top n input stack + * elements, and that the other elements must be appended to the input stack. + */ + private short outputStackStart; - /** - * The LONG type. This is a BASE type. - */ - static final int LONG = BASE | 4; + /** The index of the top stack element in {@link #outputStack}. */ + private short outputStackTop; - /** - * The NULL type. This is a BASE type. - */ - static final int NULL = BASE | 5; + /** The number of types that are initialized in the basic block. See {@link #initializations}. */ + private int initializationCount; - /** - * The UNINITIALIZED_THIS type. This is a BASE type. - */ - static final int UNINITIALIZED_THIS = BASE | 6; + /** + * The abstract types that are initialized in the basic block. A constructor invocation on an + * UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every occurrence of this + * type in the local variables and in the operand stack. This cannot be done during the first step + * of the algorithm since, during this step, the local variables and the operand stack types are + * still abstract. It is therefore necessary to store the abstract types of the constructors which + * are invoked in the basic block, in order to do this replacement during the second step of the + * algorithm, where the frames are fully computed. Note that this array can contain abstract types + * that are relative to the input locals or to the input stack. + */ + private int[] initializations; - /** - * The stack size variation corresponding to each JVM instruction. This - * stack variation is equal to the size of the values produced by an - * instruction, minus the size of the values consumed by this instruction. - */ - static final int[] SIZE; + // ----------------------------------------------------------------------------------------------- + // Static methods to get abstract types from other type formats + // ----------------------------------------------------------------------------------------------- - /** - * Computes the stack size variation corresponding to each JVM instruction. - */ - static { - int i; - int[] b = new int[202]; - String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD" - + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD" - + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED" - + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE"; - for (i = 0; i < b.length; ++i) { - b[i] = s.charAt(i) - 'E'; - } - SIZE = b; + /** + * Returns the abstract type corresponding to the given public API frame element type. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param type a frame element type described using the same format as in {@link + * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or + * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating + * a NEW instruction (for uninitialized types). + * @return the abstract type corresponding to the given frame element type. + */ + static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Object type) { + if (type instanceof Integer) { + return CONSTANT_KIND | ((Integer) type).intValue(); + } else if (type instanceof String) { + String descriptor = Type.getObjectType((String) type).getDescriptor(); + return getAbstractTypeFromDescriptor(symbolTable, descriptor, 0); + } else { + return UNINITIALIZED_KIND + | symbolTable.addUninitializedType("", ((Label) type).bytecodeOffset); + } + } + + /** + * Returns the abstract type corresponding to the internal name of a class. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param internalName the internal name of a class. This must not be an array type + * descriptor. + * @return the abstract type value corresponding to the given internal name. + */ + static int getAbstractTypeFromInternalName( + final SymbolTable symbolTable, final String internalName) { + return REFERENCE_KIND | symbolTable.addType(internalName); + } - // code to generate the above string - // - // int NA = 0; // not applicable (unused opcode or variable size opcode) - // - // b = new int[] { - // 0, //NOP, // visitInsn - // 1, //ACONST_NULL, // - - // 1, //ICONST_M1, // - - // 1, //ICONST_0, // - - // 1, //ICONST_1, // - - // 1, //ICONST_2, // - - // 1, //ICONST_3, // - - // 1, //ICONST_4, // - - // 1, //ICONST_5, // - - // 2, //LCONST_0, // - - // 2, //LCONST_1, // - - // 1, //FCONST_0, // - - // 1, //FCONST_1, // - - // 1, //FCONST_2, // - - // 2, //DCONST_0, // - - // 2, //DCONST_1, // - - // 1, //BIPUSH, // visitIntInsn - // 1, //SIPUSH, // - - // 1, //LDC, // visitLdcInsn - // NA, //LDC_W, // - - // NA, //LDC2_W, // - - // 1, //ILOAD, // visitVarInsn - // 2, //LLOAD, // - - // 1, //FLOAD, // - - // 2, //DLOAD, // - - // 1, //ALOAD, // - - // NA, //ILOAD_0, // - - // NA, //ILOAD_1, // - - // NA, //ILOAD_2, // - - // NA, //ILOAD_3, // - - // NA, //LLOAD_0, // - - // NA, //LLOAD_1, // - - // NA, //LLOAD_2, // - - // NA, //LLOAD_3, // - - // NA, //FLOAD_0, // - - // NA, //FLOAD_1, // - - // NA, //FLOAD_2, // - - // NA, //FLOAD_3, // - - // NA, //DLOAD_0, // - - // NA, //DLOAD_1, // - - // NA, //DLOAD_2, // - - // NA, //DLOAD_3, // - - // NA, //ALOAD_0, // - - // NA, //ALOAD_1, // - - // NA, //ALOAD_2, // - - // NA, //ALOAD_3, // - - // -1, //IALOAD, // visitInsn - // 0, //LALOAD, // - - // -1, //FALOAD, // - - // 0, //DALOAD, // - - // -1, //AALOAD, // - - // -1, //BALOAD, // - - // -1, //CALOAD, // - - // -1, //SALOAD, // - - // -1, //ISTORE, // visitVarInsn - // -2, //LSTORE, // - - // -1, //FSTORE, // - - // -2, //DSTORE, // - - // -1, //ASTORE, // - - // NA, //ISTORE_0, // - - // NA, //ISTORE_1, // - - // NA, //ISTORE_2, // - - // NA, //ISTORE_3, // - - // NA, //LSTORE_0, // - - // NA, //LSTORE_1, // - - // NA, //LSTORE_2, // - - // NA, //LSTORE_3, // - - // NA, //FSTORE_0, // - - // NA, //FSTORE_1, // - - // NA, //FSTORE_2, // - - // NA, //FSTORE_3, // - - // NA, //DSTORE_0, // - - // NA, //DSTORE_1, // - - // NA, //DSTORE_2, // - - // NA, //DSTORE_3, // - - // NA, //ASTORE_0, // - - // NA, //ASTORE_1, // - - // NA, //ASTORE_2, // - - // NA, //ASTORE_3, // - - // -3, //IASTORE, // visitInsn - // -4, //LASTORE, // - - // -3, //FASTORE, // - - // -4, //DASTORE, // - - // -3, //AASTORE, // - - // -3, //BASTORE, // - - // -3, //CASTORE, // - - // -3, //SASTORE, // - - // -1, //POP, // - - // -2, //POP2, // - - // 1, //DUP, // - - // 1, //DUP_X1, // - - // 1, //DUP_X2, // - - // 2, //DUP2, // - - // 2, //DUP2_X1, // - - // 2, //DUP2_X2, // - - // 0, //SWAP, // - - // -1, //IADD, // - - // -2, //LADD, // - - // -1, //FADD, // - - // -2, //DADD, // - - // -1, //ISUB, // - - // -2, //LSUB, // - - // -1, //FSUB, // - - // -2, //DSUB, // - - // -1, //IMUL, // - - // -2, //LMUL, // - - // -1, //FMUL, // - - // -2, //DMUL, // - - // -1, //IDIV, // - - // -2, //LDIV, // - - // -1, //FDIV, // - - // -2, //DDIV, // - - // -1, //IREM, // - - // -2, //LREM, // - - // -1, //FREM, // - - // -2, //DREM, // - - // 0, //INEG, // - - // 0, //LNEG, // - - // 0, //FNEG, // - - // 0, //DNEG, // - - // -1, //ISHL, // - - // -1, //LSHL, // - - // -1, //ISHR, // - - // -1, //LSHR, // - - // -1, //IUSHR, // - - // -1, //LUSHR, // - - // -1, //IAND, // - - // -2, //LAND, // - - // -1, //IOR, // - - // -2, //LOR, // - - // -1, //IXOR, // - - // -2, //LXOR, // - - // 0, //IINC, // visitIincInsn - // 1, //I2L, // visitInsn - // 0, //I2F, // - - // 1, //I2D, // - - // -1, //L2I, // - - // -1, //L2F, // - - // 0, //L2D, // - - // 0, //F2I, // - - // 1, //F2L, // - - // 1, //F2D, // - - // -1, //D2I, // - - // 0, //D2L, // - - // -1, //D2F, // - - // 0, //I2B, // - - // 0, //I2C, // - - // 0, //I2S, // - - // -3, //LCMP, // - - // -1, //FCMPL, // - - // -1, //FCMPG, // - - // -3, //DCMPL, // - - // -3, //DCMPG, // - - // -1, //IFEQ, // visitJumpInsn - // -1, //IFNE, // - - // -1, //IFLT, // - - // -1, //IFGE, // - - // -1, //IFGT, // - - // -1, //IFLE, // - - // -2, //IF_ICMPEQ, // - - // -2, //IF_ICMPNE, // - - // -2, //IF_ICMPLT, // - - // -2, //IF_ICMPGE, // - - // -2, //IF_ICMPGT, // - - // -2, //IF_ICMPLE, // - - // -2, //IF_ACMPEQ, // - - // -2, //IF_ACMPNE, // - - // 0, //GOTO, // - - // 1, //JSR, // - - // 0, //RET, // visitVarInsn - // -1, //TABLESWITCH, // visiTableSwitchInsn - // -1, //LOOKUPSWITCH, // visitLookupSwitch - // -1, //IRETURN, // visitInsn - // -2, //LRETURN, // - - // -1, //FRETURN, // - - // -2, //DRETURN, // - - // -1, //ARETURN, // - - // 0, //RETURN, // - - // NA, //GETSTATIC, // visitFieldInsn - // NA, //PUTSTATIC, // - - // NA, //GETFIELD, // - - // NA, //PUTFIELD, // - - // NA, //INVOKEVIRTUAL, // visitMethodInsn - // NA, //INVOKESPECIAL, // - - // NA, //INVOKESTATIC, // - - // NA, //INVOKEINTERFACE, // - - // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn - // 1, //NEW, // visitTypeInsn - // 0, //NEWARRAY, // visitIntInsn - // 0, //ANEWARRAY, // visitTypeInsn - // 0, //ARRAYLENGTH, // visitInsn - // NA, //ATHROW, // - - // 0, //CHECKCAST, // visitTypeInsn - // 0, //INSTANCEOF, // - - // -1, //MONITORENTER, // visitInsn - // -1, //MONITOREXIT, // - - // NA, //WIDE, // NOT VISITED - // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn - // -1, //IFNULL, // visitJumpInsn - // -1, //IFNONNULL, // - - // NA, //GOTO_W, // - - // NA, //JSR_W, // - - // }; - // for (i = 0; i < b.length; ++i) { - // System.err.print((char)('E' + b[i])); - // } - // System.err.println(); + /** + * Returns the abstract type corresponding to the given type descriptor. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param buffer a string ending with a type descriptor. + * @param offset the start offset of the type descriptor in buffer. + * @return the abstract type corresponding to the given type descriptor. + */ + private static int getAbstractTypeFromDescriptor( + final SymbolTable symbolTable, final String buffer, final int offset) { + String internalName; + switch (buffer.charAt(offset)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + internalName = buffer.substring(offset + 1, buffer.length() - 1); + return REFERENCE_KIND | symbolTable.addType(internalName); + case '[': + int elementDescriptorOffset = offset + 1; + while (buffer.charAt(elementDescriptorOffset) == '[') { + ++elementDescriptorOffset; + } + int typeValue; + switch (buffer.charAt(elementDescriptorOffset)) { + case 'Z': + typeValue = BOOLEAN; + break; + case 'C': + typeValue = CHAR; + break; + case 'B': + typeValue = BYTE; + break; + case 'S': + typeValue = SHORT; + break; + case 'I': + typeValue = INTEGER; + break; + case 'F': + typeValue = FLOAT; + break; + case 'J': + typeValue = LONG; + break; + case 'D': + typeValue = DOUBLE; + break; + case 'L': + internalName = buffer.substring(elementDescriptorOffset + 1, buffer.length() - 1); + typeValue = REFERENCE_KIND | symbolTable.addType(internalName); + break; + default: + throw new IllegalArgumentException(); + } + return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue; + default: + throw new IllegalArgumentException(); } + } - /** - * The label (i.e. basic block) to which these input and output stack map - * frames correspond. - */ - Label owner; + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- - /** - * The input stack map frame locals. - */ - int[] inputLocals; + /** + * Constructs a new Frame. + * + * @param owner the basic block to which these input and output stack map frames correspond. + */ + Frame(final Label owner) { + this.owner = owner; + } - /** - * The input stack map frame stack. - */ - int[] inputStack; + /** + * Sets this frame to the value of the given frame. + * + *

WARNING: after this method is called the two frames share the same data structures. It is + * recommended to discard the given frame to avoid unexpected side effects. + * + * @param frame The new frame value. + */ + final void copyFrom(final Frame frame) { + inputLocals = frame.inputLocals; + inputStack = frame.inputStack; + outputStackStart = 0; + outputLocals = frame.outputLocals; + outputStack = frame.outputStack; + outputStackTop = frame.outputStackTop; + initializationCount = frame.initializationCount; + initializations = frame.initializations; + } - /** - * The output stack map frame locals. - */ - private int[] outputLocals; + // ----------------------------------------------------------------------------------------------- + // Methods related to the input frame + // ----------------------------------------------------------------------------------------------- - /** - * The output stack map frame stack. - */ - private int[] outputStack; + /** + * Sets the input frame from the given method description. This method is used to initialize the + * first frame of a method, which is implicit (i.e. not stored explicitly in the StackMapTable + * attribute). + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param access the method's access flags. + * @param descriptor the method descriptor. + * @param maxLocals the maximum number of local variables of the method. + */ + final void setInputFrameFromDescriptor( + final SymbolTable symbolTable, + final int access, + final String descriptor, + final int maxLocals) { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int inputLocalIndex = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & Constants.ACC_CONSTRUCTOR) == 0) { + inputLocals[inputLocalIndex++] = + REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName()); + } else { + inputLocals[inputLocalIndex++] = UNINITIALIZED_THIS; + } + } + for (Type argumentType : Type.getArgumentTypes(descriptor)) { + int abstractType = + getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0); + inputLocals[inputLocalIndex++] = abstractType; + if (abstractType == LONG || abstractType == DOUBLE) { + inputLocals[inputLocalIndex++] = TOP; + } + } + while (inputLocalIndex < maxLocals) { + inputLocals[inputLocalIndex++] = TOP; + } + } - /** - * Relative size of the output stack. The exact semantics of this field - * depends on the algorithm that is used. - * - * When only the maximum stack size is computed, this field is the size of - * the output stack relatively to the top of the input stack. - * - * When the stack map frames are completely computed, this field is the - * actual number of types in {@link #outputStack}. - */ - private int outputStackTop; + /** + * Sets the input frame from the given public API frame description. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param nLocal the number of local variables. + * @param local the local variable types, described using the same format as in {@link + * MethodVisitor#visitFrame}. + * @param nStack the number of operand stack elements. + * @param stack the operand stack types, described using the same format as in {@link + * MethodVisitor#visitFrame}. + */ + final void setInputFrameFromApiFormat( + final SymbolTable symbolTable, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) { + int inputLocalIndex = 0; + for (int i = 0; i < nLocal; ++i) { + inputLocals[inputLocalIndex++] = getAbstractTypeFromApiFormat(symbolTable, local[i]); + if (local[i] == Opcodes.LONG || local[i] == Opcodes.DOUBLE) { + inputLocals[inputLocalIndex++] = TOP; + } + } + while (inputLocalIndex < inputLocals.length) { + inputLocals[inputLocalIndex++] = TOP; + } + int nStackTop = 0; + for (int i = 0; i < nStack; ++i) { + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + ++nStackTop; + } + } + inputStack = new int[nStack + nStackTop]; + int inputStackIndex = 0; + for (int i = 0; i < nStack; ++i) { + inputStack[inputStackIndex++] = getAbstractTypeFromApiFormat(symbolTable, stack[i]); + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + inputStack[inputStackIndex++] = TOP; + } + } + outputStackTop = 0; + initializationCount = 0; + } - /** - * Number of types that are initialized in the basic block. - * - * @see #initializations - */ - private int initializationCount; + final int getInputStackSize() { + return inputStack.length; + } - /** - * The types that are initialized in the basic block. A constructor - * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace - * every occurence of this type in the local variables and in the - * operand stack. This cannot be done during the first phase of the - * algorithm since, during this phase, the local variables and the operand - * stack are not completely computed. It is therefore necessary to store the - * types on which constructors are invoked in the basic block, in order to - * do this replacement during the second phase of the algorithm, where the - * frames are fully computed. Note that this array can contain types that - * are relative to input locals or to the input stack (see below for the - * description of the algorithm). - */ - private int[] initializations; + // ----------------------------------------------------------------------------------------------- + // Methods related to the output frame + // ----------------------------------------------------------------------------------------------- - /** - * Returns the output frame local variable type at the given index. - * - * @param local - * the index of the local that must be returned. - * @return the output frame local variable type at the given index. - */ - private int get(final int local) { - if (outputLocals == null || local >= outputLocals.length) { - // this local has never been assigned in this basic block, - // so it is still equal to its value in the input frame - return LOCAL | local; - } else { - int type = outputLocals[local]; - if (type == 0) { - // this local has never been assigned in this basic block, - // so it is still equal to its value in the input frame - type = outputLocals[local] = LOCAL | local; - } - return type; - } + /** + * Returns the abstract type stored at the given local variable index in the output frame. + * + * @param localIndex the index of the local variable whose value must be returned. + * @return the abstract type stored at the given local variable index in the output frame. + */ + private int getLocal(final int localIndex) { + if (outputLocals == null || localIndex >= outputLocals.length) { + // If this local has never been assigned in this basic block, it is still equal to its value + // in the input frame. + return LOCAL_KIND | localIndex; + } else { + int abstractType = outputLocals[localIndex]; + if (abstractType == 0) { + // If this local has never been assigned in this basic block, so it is still equal to its + // value in the input frame. + abstractType = outputLocals[localIndex] = LOCAL_KIND | localIndex; + } + return abstractType; } + } - /** - * Sets the output frame local variable type at the given index. - * - * @param local - * the index of the local that must be set. - * @param type - * the value of the local that must be set. - */ - private void set(final int local, final int type) { - // creates and/or resizes the output local variables array if necessary - if (outputLocals == null) { - outputLocals = new int[10]; - } - int n = outputLocals.length; - if (local >= n) { - int[] t = new int[Math.max(local + 1, 2 * n)]; - System.arraycopy(outputLocals, 0, t, 0, n); - outputLocals = t; - } - // sets the local variable - outputLocals[local] = type; + /** + * Replaces the abstract type stored at the given local variable index in the output frame. + * + * @param localIndex the index of the output frame local variable that must be set. + * @param abstractType the value that must be set. + */ + private void setLocal(final int localIndex, final int abstractType) { + // Create and/or resize the output local variables array if necessary. + if (outputLocals == null) { + outputLocals = new int[10]; } - - /** - * Pushes a new type onto the output frame stack. - * - * @param type - * the type that must be pushed. - */ - private void push(final int type) { - // creates and/or resizes the output stack array if necessary - if (outputStack == null) { - outputStack = new int[10]; - } - int n = outputStack.length; - if (outputStackTop >= n) { - int[] t = new int[Math.max(outputStackTop + 1, 2 * n)]; - System.arraycopy(outputStack, 0, t, 0, n); - outputStack = t; - } - // pushes the type on the output stack - outputStack[outputStackTop++] = type; - // updates the maximun height reached by the output stack, if needed - int top = owner.inputStackTop + outputStackTop; - if (top > owner.outputStackMax) { - owner.outputStackMax = top; - } + int outputLocalsLength = outputLocals.length; + if (localIndex >= outputLocalsLength) { + int[] newOutputLocals = new int[Math.max(localIndex + 1, 2 * outputLocalsLength)]; + System.arraycopy(outputLocals, 0, newOutputLocals, 0, outputLocalsLength); + outputLocals = newOutputLocals; } + // Set the local variable. + outputLocals[localIndex] = abstractType; + } - /** - * Pushes a new type onto the output frame stack. - * - * @param cw - * the ClassWriter to which this label belongs. - * @param desc - * the descriptor of the type to be pushed. Can also be a method - * descriptor (in this case this method pushes its return type - * onto the output frame stack). - */ - private void push(final ClassWriter cw, final String desc) { - int type = type(cw, desc); - if (type != 0) { - push(type); - if (type == LONG || type == DOUBLE) { - push(TOP); - } - } + /** + * Pushes the given abstract type on the output frame stack. + * + * @param abstractType an abstract type. + */ + private void push(final int abstractType) { + // Create and/or resize the output stack array if necessary. + if (outputStack == null) { + outputStack = new int[10]; } - - /** - * Returns the int encoding of the given type. - * - * @param cw - * the ClassWriter to which this label belongs. - * @param desc - * a type descriptor. - * @return the int encoding of the given type. - */ - private static int type(final ClassWriter cw, final String desc) { - String t; - int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; - switch (desc.charAt(index)) { - case 'V': - return 0; - case 'Z': - case 'C': - case 'B': - case 'S': - case 'I': - return INTEGER; - case 'F': - return FLOAT; - case 'J': - return LONG; - case 'D': - return DOUBLE; - case 'L': - // stores the internal name, not the descriptor! - t = desc.substring(index + 1, desc.length() - 1); - return OBJECT | cw.addType(t); - // case '[': - default: - // extracts the dimensions and the element type - int data; - int dims = index + 1; - while (desc.charAt(dims) == '[') { - ++dims; - } - switch (desc.charAt(dims)) { - case 'Z': - data = BOOLEAN; - break; - case 'C': - data = CHAR; - break; - case 'B': - data = BYTE; - break; - case 'S': - data = SHORT; - break; - case 'I': - data = INTEGER; - break; - case 'F': - data = FLOAT; - break; - case 'J': - data = LONG; - break; - case 'D': - data = DOUBLE; - break; - // case 'L': - default: - // stores the internal name, not the descriptor - t = desc.substring(dims + 1, desc.length() - 1); - data = OBJECT | cw.addType(t); - } - return (dims - index) << 28 | data; - } + int outputStackLength = outputStack.length; + if (outputStackTop >= outputStackLength) { + int[] newOutputStack = new int[Math.max(outputStackTop + 1, 2 * outputStackLength)]; + System.arraycopy(outputStack, 0, newOutputStack, 0, outputStackLength); + outputStack = newOutputStack; } + // Pushes the abstract type on the output stack. + outputStack[outputStackTop++] = abstractType; + // Updates the maximum size reached by the output stack, if needed (note that this size is + // relative to the input stack size, which is not known yet). + short outputStackSize = (short) (outputStackStart + outputStackTop); + if (outputStackSize > owner.outputStackMax) { + owner.outputStackMax = outputStackSize; + } + } - /** - * Pops a type from the output frame stack and returns its value. - * - * @return the type that has been popped from the output frame stack. - */ - private int pop() { - if (outputStackTop > 0) { - return outputStack[--outputStackTop]; - } else { - // if the output frame stack is empty, pops from the input stack - return STACK | -(--owner.inputStackTop); - } + /** + * Pushes the abstract type corresponding to the given descriptor on the output frame stack. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param descriptor a type or method descriptor (in which case its return type is pushed). + */ + private void push(final SymbolTable symbolTable, final String descriptor) { + int typeDescriptorOffset = descriptor.charAt(0) == '(' ? descriptor.indexOf(')') + 1 : 0; + int abstractType = getAbstractTypeFromDescriptor(symbolTable, descriptor, typeDescriptorOffset); + if (abstractType != 0) { + push(abstractType); + if (abstractType == LONG || abstractType == DOUBLE) { + push(TOP); + } } + } - /** - * Pops the given number of types from the output frame stack. - * - * @param elements - * the number of types that must be popped. - */ - private void pop(final int elements) { - if (outputStackTop >= elements) { - outputStackTop -= elements; - } else { - // if the number of elements to be popped is greater than the number - // of elements in the output stack, clear it, and pops the remaining - // elements from the input stack. - owner.inputStackTop -= elements - outputStackTop; - outputStackTop = 0; - } + /** + * Pops an abstract type from the output frame stack and returns its value. + * + * @return the abstract type that has been popped from the output frame stack. + */ + private int pop() { + if (outputStackTop > 0) { + return outputStack[--outputStackTop]; + } else { + // If the output frame stack is empty, pop from the input stack. + return STACK_KIND | -(--outputStackStart); } + } - /** - * Pops a type from the output frame stack. - * - * @param desc - * the descriptor of the type to be popped. Can also be a method - * descriptor (in this case this method pops the types - * corresponding to the method arguments). - */ - private void pop(final String desc) { - char c = desc.charAt(0); - if (c == '(') { - pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1); - } else if (c == 'J' || c == 'D') { - pop(2); - } else { - pop(1); - } + /** + * Pops the given number of abstract types from the output frame stack. + * + * @param elements the number of abstract types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= elements; + } else { + // If the number of elements to be popped is greater than the number of elements in the output + // stack, clear it, and pop the remaining elements from the input stack. + outputStackStart -= elements - outputStackTop; + outputStackTop = 0; } + } - /** - * Adds a new type to the list of types on which a constructor is invoked in - * the basic block. - * - * @param var - * a type on a which a constructor is invoked. - */ - private void init(final int var) { - // creates and/or resizes the initializations array if necessary - if (initializations == null) { - initializations = new int[2]; - } - int n = initializations.length; - if (initializationCount >= n) { - int[] t = new int[Math.max(initializationCount + 1, 2 * n)]; - System.arraycopy(initializations, 0, t, 0, n); - initializations = t; - } - // stores the type to be initialized - initializations[initializationCount++] = var; + /** + * Pops as many abstract types from the output frame stack as described by the given descriptor. + * + * @param descriptor a type or method descriptor (in which case its argument types are popped). + */ + private void pop(final String descriptor) { + char firstDescriptorChar = descriptor.charAt(0); + if (firstDescriptorChar == '(') { + pop((Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1); + } else if (firstDescriptorChar == 'J' || firstDescriptorChar == 'D') { + pop(2); + } else { + pop(1); } + } - /** - * Replaces the given type with the appropriate type if it is one of the - * types on which a constructor is invoked in the basic block. - * - * @param cw - * the ClassWriter to which this label belongs. - * @param t - * a type - * @return t or, if t is one of the types on which a constructor is invoked - * in the basic block, the type corresponding to this constructor. - */ - private int init(final ClassWriter cw, final int t) { - int s; - if (t == UNINITIALIZED_THIS) { - s = OBJECT | cw.addType(cw.thisName); - } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) { - String type = cw.typeTable[t & BASE_VALUE].strVal1; - s = OBJECT | cw.addType(type); - } else { - return t; - } - for (int j = 0; j < initializationCount; ++j) { - int u = initializations[j]; - int dim = u & DIM; - int kind = u & KIND; - if (kind == LOCAL) { - u = dim + inputLocals[u & VALUE]; - } else if (kind == STACK) { - u = dim + inputStack[inputStack.length - (u & VALUE)]; - } - if (t == u) { - return s; - } - } - return t; + // ----------------------------------------------------------------------------------------------- + // Methods to handle uninitialized types + // ----------------------------------------------------------------------------------------------- + + /** + * Adds an abstract type to the list of types on which a constructor is invoked in the basic + * block. + * + * @param abstractType an abstract type on a which a constructor is invoked. + */ + private void addInitializedType(final int abstractType) { + // Create and/or resize the initializations array if necessary. + if (initializations == null) { + initializations = new int[2]; } + int initializationsLength = initializations.length; + if (initializationCount >= initializationsLength) { + int[] newInitializations = + new int[Math.max(initializationCount + 1, 2 * initializationsLength)]; + System.arraycopy(initializations, 0, newInitializations, 0, initializationsLength); + initializations = newInitializations; + } + // Store the abstract type. + initializations[initializationCount++] = abstractType; + } - /** - * Initializes the input frame of the first basic block from the method - * descriptor. - * - * @param cw - * the ClassWriter to which this label belongs. - * @param access - * the access flags of the method to which this label belongs. - * @param args - * the formal parameter types of this method. - * @param maxLocals - * the maximum number of local variables of this method. - */ - void initInputFrame(final ClassWriter cw, final int access, - final Type[] args, final int maxLocals) { - inputLocals = new int[maxLocals]; - inputStack = new int[0]; - int i = 0; - if ((access & Opcodes.ACC_STATIC) == 0) { - if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) { - inputLocals[i++] = OBJECT | cw.addType(cw.thisName); - } else { - inputLocals[i++] = UNINITIALIZED_THIS; - } + /** + * Returns the "initialized" abstract type corresponding to the given abstract type. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param abstractType an abstract type. + * @return the REFERENCE_KIND abstract type corresponding to abstractType if it is + * UNINITIALIZED_THIS or an UNINITIALIZED_KIND abstract type for one of the types on which a + * constructor is invoked in the basic block. Otherwise returns abstractType. + */ + private int getInitializedType(final SymbolTable symbolTable, final int abstractType) { + if (abstractType == UNINITIALIZED_THIS + || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND) { + for (int i = 0; i < initializationCount; ++i) { + int initializedType = initializations[i]; + int dim = initializedType & DIM_MASK; + int kind = initializedType & KIND_MASK; + int value = initializedType & VALUE_MASK; + if (kind == LOCAL_KIND) { + initializedType = dim + inputLocals[value]; + } else if (kind == STACK_KIND) { + initializedType = dim + inputStack[inputStack.length - value]; } - for (int j = 0; j < args.length; ++j) { - int t = type(cw, args[j].getDescriptor()); - inputLocals[i++] = t; - if (t == LONG || t == DOUBLE) { - inputLocals[i++] = TOP; - } - } - while (i < maxLocals) { - inputLocals[i++] = TOP; + if (abstractType == initializedType) { + if (abstractType == UNINITIALIZED_THIS) { + return REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName()); + } else { + return REFERENCE_KIND + | symbolTable.addType(symbolTable.getType(abstractType & VALUE_MASK).value); + } } + } } + return abstractType; + } - /** - * Simulates the action of the given instruction on the output stack frame. - * - * @param opcode - * the opcode of the instruction. - * @param arg - * the operand of the instruction, if any. - * @param cw - * the class writer to which this label belongs. - * @param item - * the operand of the instructions, if any. - */ - void execute(final int opcode, final int arg, final ClassWriter cw, - final Item item) { - int t1, t2, t3, t4; - switch (opcode) { - case Opcodes.NOP: - case Opcodes.INEG: - case Opcodes.LNEG: - case Opcodes.FNEG: - case Opcodes.DNEG: - case Opcodes.I2B: - case Opcodes.I2C: - case Opcodes.I2S: - case Opcodes.GOTO: - case Opcodes.RETURN: - break; - case Opcodes.ACONST_NULL: - push(NULL); - break; - case Opcodes.ICONST_M1: - case Opcodes.ICONST_0: - case Opcodes.ICONST_1: - case Opcodes.ICONST_2: - case Opcodes.ICONST_3: - case Opcodes.ICONST_4: - case Opcodes.ICONST_5: - case Opcodes.BIPUSH: - case Opcodes.SIPUSH: - case Opcodes.ILOAD: - push(INTEGER); - break; - case Opcodes.LCONST_0: - case Opcodes.LCONST_1: - case Opcodes.LLOAD: - push(LONG); - push(TOP); - break; - case Opcodes.FCONST_0: - case Opcodes.FCONST_1: - case Opcodes.FCONST_2: - case Opcodes.FLOAD: - push(FLOAT); - break; - case Opcodes.DCONST_0: - case Opcodes.DCONST_1: - case Opcodes.DLOAD: - push(DOUBLE); - push(TOP); - break; - case Opcodes.LDC: - switch (item.type) { - case ClassWriter.INT: - push(INTEGER); - break; - case ClassWriter.LONG: - push(LONG); - push(TOP); - break; - case ClassWriter.FLOAT: - push(FLOAT); - break; - case ClassWriter.DOUBLE: - push(DOUBLE); - push(TOP); - break; - case ClassWriter.CLASS: - push(OBJECT | cw.addType("java/lang/Class")); - break; - case ClassWriter.STR: - push(OBJECT | cw.addType("java/lang/String")); - break; - case ClassWriter.MTYPE: - push(OBJECT | cw.addType("java/lang/invoke/MethodType")); - break; - // case ClassWriter.HANDLE_BASE + [1..9]: - default: - push(OBJECT | cw.addType("java/lang/invoke/MethodHandle")); - } - break; - case Opcodes.ALOAD: - push(get(arg)); - break; - case Opcodes.IALOAD: - case Opcodes.BALOAD: - case Opcodes.CALOAD: - case Opcodes.SALOAD: - pop(2); - push(INTEGER); - break; - case Opcodes.LALOAD: - case Opcodes.D2L: - pop(2); - push(LONG); - push(TOP); - break; - case Opcodes.FALOAD: - pop(2); - push(FLOAT); - break; - case Opcodes.DALOAD: - case Opcodes.L2D: - pop(2); - push(DOUBLE); - push(TOP); - break; - case Opcodes.AALOAD: - pop(1); - t1 = pop(); - push(ELEMENT_OF + t1); - break; - case Opcodes.ISTORE: - case Opcodes.FSTORE: - case Opcodes.ASTORE: - t1 = pop(); - set(arg, t1); - if (arg > 0) { - t2 = get(arg - 1); - // if t2 is of kind STACK or LOCAL we cannot know its size! - if (t2 == LONG || t2 == DOUBLE) { - set(arg - 1, TOP); - } else if ((t2 & KIND) != BASE) { - set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); - } - } - break; - case Opcodes.LSTORE: - case Opcodes.DSTORE: - pop(1); - t1 = pop(); - set(arg, t1); - set(arg + 1, TOP); - if (arg > 0) { - t2 = get(arg - 1); - // if t2 is of kind STACK or LOCAL we cannot know its size! - if (t2 == LONG || t2 == DOUBLE) { - set(arg - 1, TOP); - } else if ((t2 & KIND) != BASE) { - set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); - } - } - break; - case Opcodes.IASTORE: - case Opcodes.BASTORE: - case Opcodes.CASTORE: - case Opcodes.SASTORE: - case Opcodes.FASTORE: - case Opcodes.AASTORE: - pop(3); - break; - case Opcodes.LASTORE: - case Opcodes.DASTORE: - pop(4); - break; - case Opcodes.POP: - case Opcodes.IFEQ: - case Opcodes.IFNE: - case Opcodes.IFLT: - case Opcodes.IFGE: - case Opcodes.IFGT: - case Opcodes.IFLE: - case Opcodes.IRETURN: - case Opcodes.FRETURN: - case Opcodes.ARETURN: - case Opcodes.TABLESWITCH: - case Opcodes.LOOKUPSWITCH: - case Opcodes.ATHROW: - case Opcodes.MONITORENTER: - case Opcodes.MONITOREXIT: - case Opcodes.IFNULL: - case Opcodes.IFNONNULL: - pop(1); - break; - case Opcodes.POP2: - case Opcodes.IF_ICMPEQ: - case Opcodes.IF_ICMPNE: - case Opcodes.IF_ICMPLT: - case Opcodes.IF_ICMPGE: - case Opcodes.IF_ICMPGT: - case Opcodes.IF_ICMPLE: - case Opcodes.IF_ACMPEQ: - case Opcodes.IF_ACMPNE: - case Opcodes.LRETURN: - case Opcodes.DRETURN: - pop(2); - break; - case Opcodes.DUP: - t1 = pop(); - push(t1); - push(t1); - break; - case Opcodes.DUP_X1: - t1 = pop(); - t2 = pop(); - push(t1); - push(t2); - push(t1); - break; - case Opcodes.DUP_X2: - t1 = pop(); - t2 = pop(); - t3 = pop(); - push(t1); - push(t3); - push(t2); - push(t1); - break; - case Opcodes.DUP2: - t1 = pop(); - t2 = pop(); - push(t2); - push(t1); - push(t2); - push(t1); - break; - case Opcodes.DUP2_X1: - t1 = pop(); - t2 = pop(); - t3 = pop(); - push(t2); - push(t1); - push(t3); - push(t2); - push(t1); - break; - case Opcodes.DUP2_X2: - t1 = pop(); - t2 = pop(); - t3 = pop(); - t4 = pop(); - push(t2); - push(t1); - push(t4); - push(t3); - push(t2); - push(t1); - break; - case Opcodes.SWAP: - t1 = pop(); - t2 = pop(); - push(t1); - push(t2); - break; - case Opcodes.IADD: - case Opcodes.ISUB: - case Opcodes.IMUL: - case Opcodes.IDIV: - case Opcodes.IREM: - case Opcodes.IAND: - case Opcodes.IOR: - case Opcodes.IXOR: - case Opcodes.ISHL: - case Opcodes.ISHR: - case Opcodes.IUSHR: - case Opcodes.L2I: - case Opcodes.D2I: - case Opcodes.FCMPL: - case Opcodes.FCMPG: - pop(2); + // ----------------------------------------------------------------------------------------------- + // Main method, to simulate the execution of each instruction on the output frame + // ----------------------------------------------------------------------------------------------- + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode the opcode of the instruction. + * @param arg the numeric operand of the instruction, if any. + * @param argSymbol the Symbol operand of the instruction, if any. + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + */ + void execute( + final int opcode, final int arg, final Symbol argSymbol, final SymbolTable symbolTable) { + // Abstract types popped from the stack or read from local variables. + int abstractType1; + int abstractType2; + int abstractType3; + int abstractType4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (argSymbol.tag) { + case Symbol.CONSTANT_INTEGER_TAG: push(INTEGER); break; - case Opcodes.LADD: - case Opcodes.LSUB: - case Opcodes.LMUL: - case Opcodes.LDIV: - case Opcodes.LREM: - case Opcodes.LAND: - case Opcodes.LOR: - case Opcodes.LXOR: - pop(4); + case Symbol.CONSTANT_LONG_TAG: push(LONG); push(TOP); break; - case Opcodes.FADD: - case Opcodes.FSUB: - case Opcodes.FMUL: - case Opcodes.FDIV: - case Opcodes.FREM: - case Opcodes.L2F: - case Opcodes.D2F: - pop(2); + case Symbol.CONSTANT_FLOAT_TAG: push(FLOAT); break; - case Opcodes.DADD: - case Opcodes.DSUB: - case Opcodes.DMUL: - case Opcodes.DDIV: - case Opcodes.DREM: - pop(4); + case Symbol.CONSTANT_DOUBLE_TAG: push(DOUBLE); push(TOP); break; - case Opcodes.LSHL: - case Opcodes.LSHR: - case Opcodes.LUSHR: - pop(3); - push(LONG); - push(TOP); + case Symbol.CONSTANT_CLASS_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/Class")); break; - case Opcodes.IINC: - set(arg, INTEGER); + case Symbol.CONSTANT_STRING_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/String")); break; - case Opcodes.I2L: - case Opcodes.F2L: - pop(1); - push(LONG); - push(TOP); + case Symbol.CONSTANT_METHOD_TYPE_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodType")); break; - case Opcodes.I2F: - pop(1); - push(FLOAT); - break; - case Opcodes.I2D: - case Opcodes.F2D: - pop(1); - push(DOUBLE); - push(TOP); + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodHandle")); break; - case Opcodes.F2I: - case Opcodes.ARRAYLENGTH: - case Opcodes.INSTANCEOF: - pop(1); - push(INTEGER); + case Symbol.CONSTANT_DYNAMIC_TAG: + push(symbolTable, argSymbol.value); break; - case Opcodes.LCMP: - case Opcodes.DCMPL: - case Opcodes.DCMPG: - pop(4); - push(INTEGER); - break; - case Opcodes.JSR: - case Opcodes.RET: - throw new RuntimeException( - "JSR/RET are not supported with computeFrames option"); - case Opcodes.GETSTATIC: - push(cw, item.strVal3); - break; - case Opcodes.PUTSTATIC: - pop(item.strVal3); - break; - case Opcodes.GETFIELD: - pop(1); - push(cw, item.strVal3); - break; - case Opcodes.PUTFIELD: - pop(item.strVal3); - pop(); + default: + throw new AssertionError(); + } + break; + case Opcodes.ALOAD: + push(getLocal(arg)); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + abstractType1 = pop(); + push(abstractType1 == NULL ? abstractType1 : ELEMENT_OF + abstractType1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + abstractType1 = pop(); + setLocal(arg, abstractType1); + if (arg > 0) { + int previousLocalType = getLocal(arg - 1); + if (previousLocalType == LONG || previousLocalType == DOUBLE) { + setLocal(arg - 1, TOP); + } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND + || (previousLocalType & KIND_MASK) == STACK_KIND) { + // The type of the previous local variable is not known yet, but if it later appears + // to be LONG or DOUBLE, we should then use TOP instead. + setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + abstractType1 = pop(); + setLocal(arg, abstractType1); + setLocal(arg + 1, TOP); + if (arg > 0) { + int previousLocalType = getLocal(arg - 1); + if (previousLocalType == LONG || previousLocalType == DOUBLE) { + setLocal(arg - 1, TOP); + } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND + || (previousLocalType & KIND_MASK) == STACK_KIND) { + // The type of the previous local variable is not known yet, but if it later appears + // to be LONG or DOUBLE, we should then use TOP instead. + setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + abstractType1 = pop(); + push(abstractType1); + push(abstractType1); + break; + case Opcodes.DUP_X1: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType1); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP_X2: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + push(abstractType1); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2_X1: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2_X2: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + abstractType4 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType4); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.SWAP: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType1); + push(abstractType2); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + setLocal(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new IllegalArgumentException("JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(symbolTable, argSymbol.value); + break; + case Opcodes.PUTSTATIC: + pop(argSymbol.value); + break; + case Opcodes.GETFIELD: + pop(1); + push(symbolTable, argSymbol.value); + break; + case Opcodes.PUTFIELD: + pop(argSymbol.value); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(argSymbol.value); + if (opcode != Opcodes.INVOKESTATIC) { + abstractType1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL && argSymbol.name.charAt(0) == '<') { + addInitializedType(abstractType1); + } + } + push(symbolTable, argSymbol.value); + break; + case Opcodes.INVOKEDYNAMIC: + pop(argSymbol.value); + push(symbolTable, argSymbol.value); + break; + case Opcodes.NEW: + push(UNINITIALIZED_KIND | symbolTable.addUninitializedType(argSymbol.value, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); break; - case Opcodes.INVOKEVIRTUAL: - case Opcodes.INVOKESPECIAL: - case Opcodes.INVOKESTATIC: - case Opcodes.INVOKEINTERFACE: - pop(item.strVal3); - if (opcode != Opcodes.INVOKESTATIC) { - t1 = pop(); - if (opcode == Opcodes.INVOKESPECIAL - && item.strVal2.charAt(0) == '<') { - init(t1); - } - } - push(cw, item.strVal3); + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); break; - case Opcodes.INVOKEDYNAMIC: - pop(item.strVal2); - push(cw, item.strVal2); + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); break; - case Opcodes.NEW: - push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg)); + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); break; - case Opcodes.NEWARRAY: - pop(); - switch (arg) { - case Opcodes.T_BOOLEAN: - push(ARRAY_OF | BOOLEAN); - break; - case Opcodes.T_CHAR: - push(ARRAY_OF | CHAR); - break; - case Opcodes.T_BYTE: - push(ARRAY_OF | BYTE); - break; - case Opcodes.T_SHORT: - push(ARRAY_OF | SHORT); - break; - case Opcodes.T_INT: - push(ARRAY_OF | INTEGER); - break; - case Opcodes.T_FLOAT: - push(ARRAY_OF | FLOAT); - break; - case Opcodes.T_DOUBLE: - push(ARRAY_OF | DOUBLE); - break; - // case Opcodes.T_LONG: - default: - push(ARRAY_OF | LONG); - break; - } + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); break; - case Opcodes.ANEWARRAY: - String s = item.strVal1; - pop(); - if (s.charAt(0) == '[') { - push(cw, '[' + s); - } else { - push(ARRAY_OF | OBJECT | cw.addType(s)); - } + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); break; - case Opcodes.CHECKCAST: - s = item.strVal1; - pop(); - if (s.charAt(0) == '[') { - push(cw, s); - } else { - push(OBJECT | cw.addType(s)); - } + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); break; - // case Opcodes.MULTIANEWARRAY: - default: - pop(arg); - push(cw, item.strVal1); + case Opcodes.T_LONG: + push(ARRAY_OF | LONG); break; + default: + throw new IllegalArgumentException(); + } + break; + case Opcodes.ANEWARRAY: + String arrayElementType = argSymbol.value; + pop(); + if (arrayElementType.charAt(0) == '[') { + push(symbolTable, '[' + arrayElementType); + } else { + push(ARRAY_OF | REFERENCE_KIND | symbolTable.addType(arrayElementType)); + } + break; + case Opcodes.CHECKCAST: + String castType = argSymbol.value; + pop(); + if (castType.charAt(0) == '[') { + push(symbolTable, castType); + } else { + push(REFERENCE_KIND | symbolTable.addType(castType)); } + break; + case Opcodes.MULTIANEWARRAY: + pop(arg); + push(symbolTable, argSymbol.value); + break; + default: + throw new IllegalArgumentException(); } + } - /** - * Merges the input frame of the given basic block with the input and output - * frames of this basic block. Returns true if the input frame of - * the given label has been changed by this operation. - * - * @param cw - * the ClassWriter to which this label belongs. - * @param frame - * the basic block whose input frame must be updated. - * @param edge - * the kind of the {@link Edge} between this label and 'label'. - * See {@link Edge#info}. - * @return true if the input frame of the given label has been - * changed by this operation. - */ - boolean merge(final ClassWriter cw, final Frame frame, final int edge) { - boolean changed = false; - int i, s, dim, kind, t; - - int nLocal = inputLocals.length; - int nStack = inputStack.length; - if (frame.inputLocals == null) { - frame.inputLocals = new int[nLocal]; - changed = true; - } + // ----------------------------------------------------------------------------------------------- + // Frame merging methods, used in the second step of the stack map frame computation algorithm + // ----------------------------------------------------------------------------------------------- - for (i = 0; i < nLocal; ++i) { - if (outputLocals != null && i < outputLocals.length) { - s = outputLocals[i]; - if (s == 0) { - t = inputLocals[i]; - } else { - dim = s & DIM; - kind = s & KIND; - if (kind == BASE) { - t = s; - } else { - if (kind == LOCAL) { - t = dim + inputLocals[s & VALUE]; - } else { - t = dim + inputStack[nStack - (s & VALUE)]; - } - if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 - && (t == LONG || t == DOUBLE)) { - t = TOP; - } - } - } - } else { - t = inputLocals[i]; - } - if (initializations != null) { - t = init(cw, t); - } - changed |= merge(cw, t, frame.inputLocals, i); - } + /** + * Merges the input frame of the given {@link Frame} with the input and output frames of this + * {@link Frame}. Returns true if the given frame has been changed by this operation (the + * input and output frames of this {@link Frame} are never changed). + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param dstFrame the {@link Frame} whose input frame must be updated. This should be the frame + * of a successor, in the control flow graph, of the basic block corresponding to this frame. + * @param catchTypeIndex if 'frame' corresponds to an exception handler basic block, the type + * table index of the caught exception type, otherwise 0. + * @return true if the input frame of 'frame' has been changed by this operation. + */ + final boolean merge( + final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) { + boolean frameChanged = false; - if (edge > 0) { - for (i = 0; i < nLocal; ++i) { - t = inputLocals[i]; - changed |= merge(cw, t, frame.inputLocals, i); + // Compute the concrete types of the local variables at the end of the basic block corresponding + // to this frame, by resolving its abstract output types, and merge these concrete types with + // those of the local variables in the input frame of dstFrame. + int nLocal = inputLocals.length; + int nStack = inputStack.length; + if (dstFrame.inputLocals == null) { + dstFrame.inputLocals = new int[nLocal]; + frameChanged = true; + } + for (int i = 0; i < nLocal; ++i) { + int concreteOutputType; + if (outputLocals != null && i < outputLocals.length) { + int abstractOutputType = outputLocals[i]; + if (abstractOutputType == 0) { + // If the local variable has never been assigned in this basic block, it is equal to its + // value at the beginning of the block. + concreteOutputType = inputLocals[i]; + } else { + int dim = abstractOutputType & DIM_MASK; + int kind = abstractOutputType & KIND_MASK; + if (kind == LOCAL_KIND) { + // By definition, a LOCAL_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; } - if (frame.inputStack == null) { - frame.inputStack = new int[1]; - changed = true; + } else if (kind == STACK_KIND) { + // By definition, a STACK_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + concreteOutputType = dim + inputStack[nStack - (abstractOutputType & VALUE_MASK)]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; } - changed |= merge(cw, edge, frame.inputStack, 0); - return changed; + } else { + concreteOutputType = abstractOutputType; + } } + } else { + // If the local variable has never been assigned in this basic block, it is equal to its + // value at the beginning of the block. + concreteOutputType = inputLocals[i]; + } + // concreteOutputType might be an uninitialized type from the input locals or from the input + // stack. However, if a constructor has been called for this class type in the basic block, + // then this type is no longer uninitialized at the end of basic block. + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputLocals, i); + } - int nInputStack = inputStack.length + owner.inputStackTop; - if (frame.inputStack == null) { - frame.inputStack = new int[nInputStack + outputStackTop]; - changed = true; - } + // If dstFrame is an exception handler block, it can be reached from any instruction of the + // basic block corresponding to this frame, in particular from the first one. Therefore, the + // input locals of dstFrame should be compatible (i.e. merged) with the input locals of this + // frame (and the input stack of dstFrame should be compatible, i.e. merged, with a one + // element stack containing the caught exception type). + if (catchTypeIndex > 0) { + for (int i = 0; i < nLocal; ++i) { + frameChanged |= merge(symbolTable, inputLocals[i], dstFrame.inputLocals, i); + } + if (dstFrame.inputStack == null) { + dstFrame.inputStack = new int[1]; + frameChanged = true; + } + frameChanged |= merge(symbolTable, catchTypeIndex, dstFrame.inputStack, 0); + return frameChanged; + } - for (i = 0; i < nInputStack; ++i) { - t = inputStack[i]; - if (initializations != null) { - t = init(cw, t); - } - changed |= merge(cw, t, frame.inputStack, i); + // Compute the concrete types of the stack operands at the end of the basic block corresponding + // to this frame, by resolving its abstract output types, and merge these concrete types with + // those of the stack operands in the input frame of dstFrame. + int nInputStack = inputStack.length + outputStackStart; + if (dstFrame.inputStack == null) { + dstFrame.inputStack = new int[nInputStack + outputStackTop]; + frameChanged = true; + } + // First, do this for the stack operands that have not been popped in the basic block + // corresponding to this frame, and which are therefore equal to their value in the input + // frame (except for uninitialized types, which may have been initialized). + for (int i = 0; i < nInputStack; ++i) { + int concreteOutputType = inputStack[i]; + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, i); + } + // Then, do this for the stack operands that have pushed in the basic block (this code is the + // same as the one above for local variables). + for (int i = 0; i < outputStackTop; ++i) { + int concreteOutputType; + int abstractOutputType = outputStack[i]; + int dim = abstractOutputType & DIM_MASK; + int kind = abstractOutputType & KIND_MASK; + if (kind == LOCAL_KIND) { + concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; } - for (i = 0; i < outputStackTop; ++i) { - s = outputStack[i]; - dim = s & DIM; - kind = s & KIND; - if (kind == BASE) { - t = s; - } else { - if (kind == LOCAL) { - t = dim + inputLocals[s & VALUE]; - } else { - t = dim + inputStack[nStack - (s & VALUE)]; - } - if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 - && (t == LONG || t == DOUBLE)) { - t = TOP; - } - } - if (initializations != null) { - t = init(cw, t); - } - changed |= merge(cw, t, frame.inputStack, nInputStack + i); + } else if (kind == STACK_KIND) { + concreteOutputType = dim + inputStack[nStack - (abstractOutputType & VALUE_MASK)]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; } - return changed; + } else { + concreteOutputType = abstractOutputType; + } + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, nInputStack + i); } + return frameChanged; + } - /** - * Merges the type at the given index in the given type array with the given - * type. Returns true if the type array has been modified by this - * operation. - * - * @param cw - * the ClassWriter to which this label belongs. - * @param t - * the type with which the type array element must be merged. - * @param types - * an array of types. - * @param index - * the index of the type that must be merged in 'types'. - * @return true if the type array has been modified by this - * operation. - */ - private static boolean merge(final ClassWriter cw, int t, - final int[] types, final int index) { - int u = types[index]; - if (u == t) { - // if the types are equal, merge(u,t)=u, so there is no change - return false; - } - if ((t & ~DIM) == NULL) { - if (u == NULL) { - return false; - } - t = NULL; + /** + * Merges the type at the given index in the given abstract type array with the given type. + * Returns true if the type array has been modified by this operation. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param sourceType the abstract type with which the abstract type array element must be merged. + * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND} or {@link + * #UNINITIALIZED_KIND} kind, with positive or null array dimensions. + * @param dstTypes an array of abstract types. These types should be of {@link #CONSTANT_KIND}, + * {@link #REFERENCE_KIND} or {@link #UNINITIALIZED_KIND} kind, with positive or null array + * dimensions. + * @param dstIndex the index of the type that must be merged in dstTypes. + * @return true if the type array has been modified by this operation. + */ + private static boolean merge( + final SymbolTable symbolTable, + final int sourceType, + final int[] dstTypes, + final int dstIndex) { + int dstType = dstTypes[dstIndex]; + if (dstType == sourceType) { + // If the types are equal, merge(sourceType, dstType) = dstType, so there is no change. + return false; + } + int srcType = sourceType; + if ((sourceType & ~DIM_MASK) == NULL) { + if (dstType == NULL) { + return false; + } + srcType = NULL; + } + if (dstType == 0) { + // If dstTypes[dstIndex] has never been assigned, merge(srcType, dstType) = srcType. + dstTypes[dstIndex] = srcType; + return true; + } + int mergedType; + if ((dstType & DIM_MASK) != 0 || (dstType & KIND_MASK) == REFERENCE_KIND) { + // If dstType is a reference type of any array dimension. + if (srcType == NULL) { + // If srcType is the NULL type, merge(srcType, dstType) = dstType, so there is no change. + return false; + } else if ((srcType & (DIM_MASK | KIND_MASK)) == (dstType & (DIM_MASK | KIND_MASK))) { + // If srcType has the same array dimension and the same kind as dstType. + if ((dstType & KIND_MASK) == REFERENCE_KIND) { + // If srcType and dstType are reference types with the same array dimension, + // merge(srcType, dstType) = dim(srcType) | common super class of srcType and dstType. + mergedType = + (srcType & DIM_MASK) + | REFERENCE_KIND + | symbolTable.addMergedType(srcType & VALUE_MASK, dstType & VALUE_MASK); + } else { + // If srcType and dstType are array types of equal dimension but different element types, + // merge(srcType, dstType) = dim(srcType) - 1 | java/lang/Object. + int mergedDim = ELEMENT_OF + (srcType & DIM_MASK); + mergedType = mergedDim | REFERENCE_KIND | symbolTable.addType("java/lang/Object"); } - if (u == 0) { - // if types[index] has never been assigned, merge(u,t)=t - types[index] = t; - return true; + } else if ((srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND) { + // If srcType is any other reference or array type, + // merge(srcType, dstType) = min(srcDdim, dstDim) | java/lang/Object + // where srcDim is the array dimension of srcType, minus 1 if srcType is an array type + // with a non reference element type (and similarly for dstDim). + int srcDim = srcType & DIM_MASK; + if (srcDim != 0 && (srcType & KIND_MASK) != REFERENCE_KIND) { + srcDim = ELEMENT_OF + srcDim; } - int v; - if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) { - // if u is a reference type of any dimension - if (t == NULL) { - // if t is the NULL type, merge(u,t)=u, so there is no change - return false; - } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) { - if ((u & BASE_KIND) == OBJECT) { - // if t is also a reference type, and if u and t have the - // same dimension merge(u,t) = dim(t) | common parent of the - // element types of u and t - v = (t & DIM) | OBJECT - | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE); - } else { - // if u and t are array types, but not with the same element - // type, merge(u,t)=java/lang/Object - v = OBJECT | cw.addType("java/lang/Object"); - } - } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) { - // if t is any other reference or array type, - // merge(u,t)=java/lang/Object - v = OBJECT | cw.addType("java/lang/Object"); - } else { - // if t is any other type, merge(u,t)=TOP - v = TOP; - } - } else if (u == NULL) { - // if u is the NULL type, merge(u,t)=t, - // or TOP if t is not a reference type - v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP; - } else { - // if u is any other type, merge(u,t)=TOP whatever t - v = TOP; + int dstDim = dstType & DIM_MASK; + if (dstDim != 0 && (dstType & KIND_MASK) != REFERENCE_KIND) { + dstDim = ELEMENT_OF + dstDim; } - if (u != v) { - types[index] = v; - return true; + mergedType = + Math.min(srcDim, dstDim) | REFERENCE_KIND | symbolTable.addType("java/lang/Object"); + } else { + // If srcType is any other type, merge(srcType, dstType) = TOP. + mergedType = TOP; + } + } else if (dstType == NULL) { + // If dstType is the NULL type, merge(srcType, dstType) = srcType, or TOP if srcType is not a + // an array type or a reference type. + mergedType = + (srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND ? srcType : TOP; + } else { + // If dstType is any other type, merge(srcType, dstType) = TOP whatever srcType. + mergedType = TOP; + } + if (mergedType != dstType) { + dstTypes[dstIndex] = mergedType; + return true; + } + return false; + } + + // ----------------------------------------------------------------------------------------------- + // Frame output methods, to generate StackMapFrame attributes + // ----------------------------------------------------------------------------------------------- + + /** + * Makes the given {@link MethodWriter} visit the input frame of this {@link Frame}. The visit is + * done with the {@link MethodWriter#visitFrameStart}, {@link MethodWriter#visitAbstractType} and + * {@link MethodWriter#visitFrameEnd} methods. + * + * @param methodWriter the {@link MethodWriter} that should visit the input frame of this {@link + * Frame}. + */ + final void accept(final MethodWriter methodWriter) { + // Compute the number of locals, ignoring TOP types that are just after a LONG or a DOUBLE, and + // all trailing TOP types. + int[] localTypes = inputLocals; + int nLocal = 0; + int nTrailingTop = 0; + int i = 0; + while (i < localTypes.length) { + int localType = localTypes[i]; + i += (localType == LONG || localType == DOUBLE) ? 2 : 1; + if (localType == TOP) { + nTrailingTop++; + } else { + nLocal += nTrailingTop + 1; + nTrailingTop = 0; + } + } + // Compute the stack size, ignoring TOP types that are just after a LONG or a DOUBLE. + int[] stackTypes = inputStack; + int nStack = 0; + i = 0; + while (i < stackTypes.length) { + int stackType = stackTypes[i]; + i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; + nStack++; + } + // Visit the frame and its content. + int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, nLocal, nStack); + i = 0; + while (nLocal-- > 0) { + int localType = localTypes[i]; + i += (localType == LONG || localType == DOUBLE) ? 2 : 1; + methodWriter.visitAbstractType(frameIndex++, localType); + } + i = 0; + while (nStack-- > 0) { + int stackType = stackTypes[i]; + i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; + methodWriter.visitAbstractType(frameIndex++, stackType); + } + methodWriter.visitFrameEnd(); + } + + /** + * Put the given abstract type in the given ByteVector, using the JVMS verification_type_info + * format used in StackMapTable attributes. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param abstractType an abstract type, restricted to {@link Frame#CONSTANT_KIND}, {@link + * Frame#REFERENCE_KIND} or {@link Frame#UNINITIALIZED_KIND} types. + * @param output where the abstract type must be put. + * @see JVMS + * 4.7.4 + */ + static void putAbstractType( + final SymbolTable symbolTable, final int abstractType, final ByteVector output) { + int arrayDimensions = (abstractType & Frame.DIM_MASK) >> DIM_SHIFT; + if (arrayDimensions == 0) { + int typeValue = abstractType & VALUE_MASK; + switch (abstractType & KIND_MASK) { + case CONSTANT_KIND: + output.putByte(typeValue); + break; + case REFERENCE_KIND: + output + .putByte(ITEM_OBJECT) + .putShort(symbolTable.addConstantClass(symbolTable.getType(typeValue).value).index); + break; + case UNINITIALIZED_KIND: + output.putByte(ITEM_UNINITIALIZED).putShort((int) symbolTable.getType(typeValue).data); + break; + default: + throw new AssertionError(); + } + } else { + // Case of an array type, we need to build its descriptor first. + StringBuilder typeDescriptor = new StringBuilder(); + while (arrayDimensions-- > 0) { + typeDescriptor.append('['); + } + if ((abstractType & KIND_MASK) == REFERENCE_KIND) { + typeDescriptor + .append('L') + .append(symbolTable.getType(abstractType & VALUE_MASK).value) + .append(';'); + } else { + switch (abstractType & VALUE_MASK) { + case Frame.ITEM_ASM_BOOLEAN: + typeDescriptor.append('Z'); + break; + case Frame.ITEM_ASM_BYTE: + typeDescriptor.append('B'); + break; + case Frame.ITEM_ASM_CHAR: + typeDescriptor.append('C'); + break; + case Frame.ITEM_ASM_SHORT: + typeDescriptor.append('S'); + break; + case Frame.ITEM_INTEGER: + typeDescriptor.append('I'); + break; + case Frame.ITEM_FLOAT: + typeDescriptor.append('F'); + break; + case Frame.ITEM_LONG: + typeDescriptor.append('J'); + break; + case Frame.ITEM_DOUBLE: + typeDescriptor.append('D'); + break; + default: + throw new AssertionError(); } - return false; + } + output + .putByte(ITEM_OBJECT) + .putShort(symbolTable.addConstantClass(typeDescriptor.toString()).index); } + } } diff --git a/src/jvm/clojure/asm/Handle.java b/src/jvm/clojure/asm/Handle.java index a147cf1af8..9121744932 100644 --- a/src/jvm/clojure/asm/Handle.java +++ b/src/jvm/clojure/asm/Handle.java @@ -1,32 +1,30 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; @@ -38,130 +36,154 @@ */ public final class Handle { - /** - * The kind of field or method designated by this Handle. Should be - * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, - * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, - * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, - * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or - * {@link Opcodes#H_INVOKEINTERFACE}. - */ - final int tag; - - /** - * The internal name of the field or method designed by this handle. - */ - final String owner; - - /** - * The name of the field or method designated by this handle. - */ - final String name; - - /** - * The descriptor of the field or method designated by this handle. - */ - final String desc; - - /** - * Constructs a new field or method handle. - * - * @param tag - * the kind of field or method designated by this Handle. Must be - * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, - * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, - * {@link Opcodes#H_INVOKEVIRTUAL}, - * {@link Opcodes#H_INVOKESTATIC}, - * {@link Opcodes#H_INVOKESPECIAL}, - * {@link Opcodes#H_NEWINVOKESPECIAL} or - * {@link Opcodes#H_INVOKEINTERFACE}. - * @param owner - * the internal name of the field or method designed by this - * handle. - * @param name - * the name of the field or method designated by this handle. - * @param desc - * the descriptor of the field or method designated by this - * handle. - */ - public Handle(int tag, String owner, String name, String desc) { - this.tag = tag; - this.owner = owner; - this.name = name; - this.desc = desc; - } + /** + * The kind of field or method designated by this Handle. Should be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + */ + private final int tag; - /** - * Returns the kind of field or method designated by this handle. - * - * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, - * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, - * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, - * {@link Opcodes#H_INVOKESPECIAL}, - * {@link Opcodes#H_NEWINVOKESPECIAL} or - * {@link Opcodes#H_INVOKEINTERFACE}. - */ - public int getTag() { - return tag; - } + /** The internal name of the class that owns the field or method designated by this handle. */ + private final String owner; - /** - * Returns the internal name of the field or method designed by this handle. - * - * @return the internal name of the field or method designed by this handle. - */ - public String getOwner() { - return owner; - } + /** The name of the field or method designated by this handle. */ + private final String name; - /** - * Returns the name of the field or method designated by this handle. - * - * @return the name of the field or method designated by this handle. - */ - public String getName() { - return name; - } + /** The descriptor of the field or method designated by this handle. */ + private final String descriptor; - /** - * Returns the descriptor of the field or method designated by this handle. - * - * @return the descriptor of the field or method designated by this handle. - */ - public String getDesc() { - return desc; - } + /** Whether the owner is an interface or not. */ + private final boolean isInterface; - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Handle)) { - return false; - } - Handle h = (Handle) obj; - return tag == h.tag && owner.equals(h.owner) && name.equals(h.name) - && desc.equals(h.desc); - } + /** + * Constructs a new field or method handle. + * + * @param tag the kind of field or method designated by this Handle. Must be {@link + * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link + * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link + * Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the class that owns the field or method designated by this + * handle. + * @param name the name of the field or method designated by this handle. + * @param descriptor the descriptor of the field or method designated by this handle. + * @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String, + * boolean)}. + */ + @Deprecated + public Handle(final int tag, final String owner, final String name, final String descriptor) { + this(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE); + } - @Override - public int hashCode() { - return tag + owner.hashCode() * name.hashCode() * desc.hashCode(); - } + /** + * Constructs a new field or method handle. + * + * @param tag the kind of field or method designated by this Handle. Must be {@link + * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link + * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link + * Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the class that owns the field or method designated by this + * handle. + * @param name the name of the field or method designated by this handle. + * @param descriptor the descriptor of the field or method designated by this handle. + * @param isInterface whether the owner is an interface or not. + */ + public Handle( + final int tag, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + this.tag = tag; + this.owner = owner; + this.name = name; + this.descriptor = descriptor; + this.isInterface = isInterface; + } + + /** + * Returns the kind of field or method designated by this handle. + * + * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + */ + public int getTag() { + return tag; + } + + /** + * Returns the internal name of the class that owns the field or method designated by this handle. + * + * @return the internal name of the class that owns the field or method designated by this handle. + */ + public String getOwner() { + return owner; + } + + /** + * Returns the name of the field or method designated by this handle. + * + * @return the name of the field or method designated by this handle. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the field or method designated by this handle. + * + * @return the descriptor of the field or method designated by this handle. + */ + public String getDesc() { + return descriptor; + } - /** - * Returns the textual representation of this handle. The textual - * representation is: - * - *

-     * owner '.' name desc ' ' '(' tag ')'
-     * 
- * - * . As this format is unambiguous, it can be parsed if necessary. - */ - @Override - public String toString() { - return owner + '.' + name + desc + " (" + tag + ')'; + /** + * Returns true if the owner of the field or method designated by this handle is an interface. + * + * @return true if the owner of the field or method designated by this handle is an interface. + */ + public boolean isInterface() { + return isInterface; + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Handle)) { + return false; } + Handle handle = (Handle) object; + return tag == handle.tag + && isInterface == handle.isInterface + && owner.equals(handle.owner) + && name.equals(handle.name) + && descriptor.equals(handle.descriptor); + } + + @Override + public int hashCode() { + return tag + + (isInterface ? 64 : 0) + + owner.hashCode() * name.hashCode() * descriptor.hashCode(); + } + + /** + * Returns the textual representation of this handle. The textual representation is: + * + *
    + *
  • for a reference to a class: owner "." name descriptor " (" tag ")", + *
  • for a reference to an interface: owner "." name descriptor " (" tag " itf)". + *
+ */ + @Override + public String toString() { + return owner + '.' + name + descriptor + " (" + tag + (isInterface ? " itf" : "") + ')'; + } } diff --git a/src/jvm/clojure/asm/Handler.java b/src/jvm/clojure/asm/Handler.java index bc0579e01b..41002b62be 100644 --- a/src/jvm/clojure/asm/Handler.java +++ b/src/jvm/clojure/asm/Handler.java @@ -1,121 +1,198 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * Information about an exception handler block. + * Information about an exception handler. Corresponds to an element of the exception_table array of + * a Code attribute, as defined in the Java Virtual Machine Specification (JVMS). Handler instances + * can be chained together, with their {@link #nextHandler} field, to describe a full JVMS + * exception_table array. * + * @see JVMS + * 4.7.3 * @author Eric Bruneton */ -class Handler { +final class Handler { + + /** + * The start_pc field of this JVMS exception_table entry. Corresponds to the beginning of the + * exception handler's scope (inclusive). + */ + final Label startPc; + + /** + * The end_pc field of this JVMS exception_table entry. Corresponds to the end of the exception + * handler's scope (exclusive). + */ + final Label endPc; + + /** + * The handler_pc field of this JVMS exception_table entry. Corresponding to the beginning of the + * exception handler's code. + */ + final Label handlerPc; - /** - * Beginning of the exception handler's scope (inclusive). - */ - Label start; + /** + * The catch_type field of this JVMS exception_table entry. This is the constant pool index of the + * internal name of the type of exceptions handled by this handler, or 0 to catch any exceptions. + */ + final int catchType; - /** - * End of the exception handler's scope (exclusive). - */ - Label end; + /** + * The internal name of the type of exceptions handled by this handler, or null to catch + * any exceptions. + */ + final String catchTypeDescriptor; - /** - * Beginning of the exception handler's code. - */ - Label handler; + /** The next exception handler. */ + Handler nextHandler; - /** - * Internal name of the type of exceptions handled by this handler, or - * null to catch any exceptions. - */ - String desc; + /** + * Constructs a new Handler. + * + * @param startPc the start_pc field of this JVMS exception_table entry. + * @param endPc the end_pc field of this JVMS exception_table entry. + * @param handlerPc the handler_pc field of this JVMS exception_table entry. + * @param catchType The catch_type field of this JVMS exception_table entry. + * @param catchTypeDescriptor The internal name of the type of exceptions handled by this handler, + * or null to catch any exceptions. + */ + Handler( + final Label startPc, + final Label endPc, + final Label handlerPc, + final int catchType, + final String catchTypeDescriptor) { + this.startPc = startPc; + this.endPc = endPc; + this.handlerPc = handlerPc; + this.catchType = catchType; + this.catchTypeDescriptor = catchTypeDescriptor; + } - /** - * Constant pool index of the internal name of the type of exceptions - * handled by this handler, or 0 to catch any exceptions. - */ - int type; + /** + * Constructs a new Handler from the given one, with a different scope. + * + * @param handler an existing Handler. + * @param startPc the start_pc field of this JVMS exception_table entry. + * @param endPc the end_pc field of this JVMS exception_table entry. + */ + Handler(final Handler handler, final Label startPc, final Label endPc) { + this(startPc, endPc, handler.handlerPc, handler.catchType, handler.catchTypeDescriptor); + this.nextHandler = handler.nextHandler; + } + + /** + * Removes the range between start and end from the Handler list that begins with the given + * element. + * + * @param firstHandler the beginning of a Handler list. May be null. + * @param start the start of the range to be removed. + * @param end the end of the range to be removed. Maybe null. + * @return the exception handler list with the start-end range removed. + */ + static Handler removeRange(final Handler firstHandler, final Label start, final Label end) { + if (firstHandler == null) { + return null; + } else { + firstHandler.nextHandler = removeRange(firstHandler.nextHandler, start, end); + } + int handlerStart = firstHandler.startPc.bytecodeOffset; + int handlerEnd = firstHandler.endPc.bytecodeOffset; + int rangeStart = start.bytecodeOffset; + int rangeEnd = end == null ? Integer.MAX_VALUE : end.bytecodeOffset; + // Return early if [handlerStart,handlerEnd[ and [rangeStart,rangeEnd[ don't intersect. + if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) { + return firstHandler; + } + if (rangeStart <= handlerStart) { + if (rangeEnd >= handlerEnd) { + // If [handlerStart,handlerEnd[ is included in [rangeStart,rangeEnd[, remove firstHandler. + return firstHandler.nextHandler; + } else { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [rangeEnd,handlerEnd[ + return new Handler(firstHandler, end, firstHandler.endPc); + } + } else if (rangeEnd >= handlerEnd) { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [handlerStart,rangeStart[ + return new Handler(firstHandler, firstHandler.startPc, start); + } else { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = + // [handlerStart,rangeStart[ + [rangeEnd,handerEnd[ + firstHandler.nextHandler = new Handler(firstHandler, end, firstHandler.endPc); + return new Handler(firstHandler, firstHandler.startPc, start); + } + } + + /** + * Returns the number of elements of the Handler list that begins with the given element. + * + * @param firstHandler the beginning of a Handler list. May be null. + * @return the number of elements of the Handler list that begins with 'handler'. + */ + static int getExceptionTableLength(final Handler firstHandler) { + int length = 0; + Handler handler = firstHandler; + while (handler != null) { + length++; + handler = handler.nextHandler; + } + return length; + } - /** - * Next exception handler block info. - */ - Handler next; + /** + * Returns the size in bytes of the JVMS exception_table corresponding to the Handler list that + * begins with the given element. This includes the exception_table_length field. + * + * @param firstHandler the beginning of a Handler list. May be null. + * @return the size in bytes of the exception_table_length and exception_table structures. + */ + static int getExceptionTableSize(final Handler firstHandler) { + return 2 + 8 * getExceptionTableLength(firstHandler); + } - /** - * Removes the range between start and end from the given exception - * handlers. - * - * @param h - * an exception handler list. - * @param start - * the start of the range to be removed. - * @param end - * the end of the range to be removed. Maybe null. - * @return the exception handler list with the start-end range removed. - */ - static Handler remove(Handler h, Label start, Label end) { - if (h == null) { - return null; - } else { - h.next = remove(h.next, start, end); - } - int hstart = h.start.position; - int hend = h.end.position; - int s = start.position; - int e = end == null ? Integer.MAX_VALUE : end.position; - // if [hstart,hend[ and [s,e[ intervals intersect... - if (s < hend && e > hstart) { - if (s <= hstart) { - if (e >= hend) { - // [hstart,hend[ fully included in [s,e[, h removed - h = h.next; - } else { - // [hstart,hend[ minus [s,e[ = [e,hend[ - h.start = end; - } - } else if (e >= hend) { - // [hstart,hend[ minus [s,e[ = [hstart,s[ - h.end = start; - } else { - // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[ - Handler g = new Handler(); - g.start = end; - g.end = h.end; - g.handler = h.handler; - g.desc = h.desc; - g.type = h.type; - g.next = h.next; - h.end = start; - h.next = g; - } - } - return h; + /** + * Puts the JVMS exception_table corresponding to the Handler list that begins with the given + * element. This includes the exception_table_length field. + * + * @param firstHandler the beginning of a Handler list. May be null. + * @param output where the exception_table_length and exception_table structures must be put. + */ + static void putExceptionTable(final Handler firstHandler, final ByteVector output) { + output.putShort(getExceptionTableLength(firstHandler)); + Handler handler = firstHandler; + while (handler != null) { + output + .putShort(handler.startPc.bytecodeOffset) + .putShort(handler.endPc.bytecodeOffset) + .putShort(handler.handlerPc.bytecodeOffset) + .putShort(handler.catchType); + handler = handler.nextHandler; } + } } diff --git a/src/jvm/clojure/asm/Item.java b/src/jvm/clojure/asm/Item.java deleted file mode 100644 index 274a548319..0000000000 --- a/src/jvm/clojure/asm/Item.java +++ /dev/null @@ -1,311 +0,0 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ -package clojure.asm; - -/** - * A constant pool item. Constant pool items can be created with the 'newXXX' - * methods in the {@link ClassWriter} class. - * - * @author Eric Bruneton - */ -final class Item { - - /** - * Index of this item in the constant pool. - */ - int index; - - /** - * Type of this constant pool item. A single class is used to represent all - * constant pool item types, in order to minimize the bytecode size of this - * package. The value of this field is one of {@link ClassWriter#INT}, - * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT}, - * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8}, - * {@link ClassWriter#STR}, {@link ClassWriter#CLASS}, - * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD}, - * {@link ClassWriter#METH}, {@link ClassWriter#IMETH}, - * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}. - * - * MethodHandle constant 9 variations are stored using a range of 9 values - * from {@link ClassWriter#HANDLE_BASE} + 1 to - * {@link ClassWriter#HANDLE_BASE} + 9. - * - * Special Item types are used for Items that are stored in the ClassWriter - * {@link ClassWriter#typeTable}, instead of the constant pool, in order to - * avoid clashes with normal constant pool items in the ClassWriter constant - * pool's hash table. These special item types are - * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and - * {@link ClassWriter#TYPE_MERGED}. - */ - int type; - - /** - * Value of this item, for an integer item. - */ - int intVal; - - /** - * Value of this item, for a long item. - */ - long longVal; - - /** - * First part of the value of this item, for items that do not hold a - * primitive value. - */ - String strVal1; - - /** - * Second part of the value of this item, for items that do not hold a - * primitive value. - */ - String strVal2; - - /** - * Third part of the value of this item, for items that do not hold a - * primitive value. - */ - String strVal3; - - /** - * The hash code value of this constant pool item. - */ - int hashCode; - - /** - * Link to another constant pool item, used for collision lists in the - * constant pool's hash table. - */ - Item next; - - /** - * Constructs an uninitialized {@link Item}. - */ - Item() { - } - - /** - * Constructs an uninitialized {@link Item} for constant pool element at - * given position. - * - * @param index - * index of the item to be constructed. - */ - Item(final int index) { - this.index = index; - } - - /** - * Constructs a copy of the given item. - * - * @param index - * index of the item to be constructed. - * @param i - * the item that must be copied into the item to be constructed. - */ - Item(final int index, final Item i) { - this.index = index; - type = i.type; - intVal = i.intVal; - longVal = i.longVal; - strVal1 = i.strVal1; - strVal2 = i.strVal2; - strVal3 = i.strVal3; - hashCode = i.hashCode; - } - - /** - * Sets this item to an integer item. - * - * @param intVal - * the value of this item. - */ - void set(final int intVal) { - this.type = ClassWriter.INT; - this.intVal = intVal; - this.hashCode = 0x7FFFFFFF & (type + intVal); - } - - /** - * Sets this item to a long item. - * - * @param longVal - * the value of this item. - */ - void set(final long longVal) { - this.type = ClassWriter.LONG; - this.longVal = longVal; - this.hashCode = 0x7FFFFFFF & (type + (int) longVal); - } - - /** - * Sets this item to a float item. - * - * @param floatVal - * the value of this item. - */ - void set(final float floatVal) { - this.type = ClassWriter.FLOAT; - this.intVal = Float.floatToRawIntBits(floatVal); - this.hashCode = 0x7FFFFFFF & (type + (int) floatVal); - } - - /** - * Sets this item to a double item. - * - * @param doubleVal - * the value of this item. - */ - void set(final double doubleVal) { - this.type = ClassWriter.DOUBLE; - this.longVal = Double.doubleToRawLongBits(doubleVal); - this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal); - } - - /** - * Sets this item to an item that do not hold a primitive value. - * - * @param type - * the type of this item. - * @param strVal1 - * first part of the value of this item. - * @param strVal2 - * second part of the value of this item. - * @param strVal3 - * third part of the value of this item. - */ - void set(final int type, final String strVal1, final String strVal2, - final String strVal3) { - this.type = type; - this.strVal1 = strVal1; - this.strVal2 = strVal2; - this.strVal3 = strVal3; - switch (type) { - case ClassWriter.UTF8: - case ClassWriter.STR: - case ClassWriter.CLASS: - case ClassWriter.MTYPE: - case ClassWriter.TYPE_NORMAL: - hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); - return; - case ClassWriter.NAME_TYPE: { - hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() - * strVal2.hashCode()); - return; - } - // ClassWriter.FIELD: - // ClassWriter.METH: - // ClassWriter.IMETH: - // ClassWriter.HANDLE_BASE + 1..9 - default: - hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() - * strVal2.hashCode() * strVal3.hashCode()); - } - } - - /** - * Sets the item to an InvokeDynamic item. - * - * @param name - * invokedynamic's name. - * @param desc - * invokedynamic's desc. - * @param bsmIndex - * zero based index into the class attribute BootrapMethods. - */ - void set(String name, String desc, int bsmIndex) { - this.type = ClassWriter.INDY; - this.longVal = bsmIndex; - this.strVal1 = name; - this.strVal2 = desc; - this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex - * strVal1.hashCode() * strVal2.hashCode()); - } - - /** - * Sets the item to a BootstrapMethod item. - * - * @param position - * position in byte in the class attribute BootrapMethods. - * @param hashCode - * hashcode of the item. This hashcode is processed from the - * hashcode of the bootstrap method and the hashcode of all - * bootstrap arguments. - */ - void set(int position, int hashCode) { - this.type = ClassWriter.BSM; - this.intVal = position; - this.hashCode = hashCode; - } - - /** - * Indicates if the given item is equal to this one. This method assumes - * that the two items have the same {@link #type}. - * - * @param i - * the item to be compared to this one. Both items must have the - * same {@link #type}. - * @return true if the given item if equal to this one, - * false otherwise. - */ - boolean isEqualTo(final Item i) { - switch (type) { - case ClassWriter.UTF8: - case ClassWriter.STR: - case ClassWriter.CLASS: - case ClassWriter.MTYPE: - case ClassWriter.TYPE_NORMAL: - return i.strVal1.equals(strVal1); - case ClassWriter.TYPE_MERGED: - case ClassWriter.LONG: - case ClassWriter.DOUBLE: - return i.longVal == longVal; - case ClassWriter.INT: - case ClassWriter.FLOAT: - return i.intVal == intVal; - case ClassWriter.TYPE_UNINIT: - return i.intVal == intVal && i.strVal1.equals(strVal1); - case ClassWriter.NAME_TYPE: - return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2); - case ClassWriter.INDY: { - return i.longVal == longVal && i.strVal1.equals(strVal1) - && i.strVal2.equals(strVal2); - } - // case ClassWriter.FIELD: - // case ClassWriter.METH: - // case ClassWriter.IMETH: - // case ClassWriter.HANDLE_BASE + 1..9 - default: - return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2) - && i.strVal3.equals(strVal3); - } - } - -} diff --git a/src/jvm/clojure/asm/Label.java b/src/jvm/clojure/asm/Label.java index 735e1b91ec..86afe087de 100644 --- a/src/jvm/clojure/asm/Label.java +++ b/src/jvm/clojure/asm/Label.java @@ -1,560 +1,621 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A label represents a position in the bytecode of a method. Labels are used - * for jump, goto, and switch instructions, and for try catch blocks. A label - * designates the instruction that is just after. Note however that there - * can be other elements between a label and the instruction it designates (such + * A position in the bytecode of a method. Labels are used for jump, goto, and switch instructions, + * and for try catch blocks. A label designates the instruction that is just after. Note + * however that there can be other elements between a label and the instruction it designates (such * as other labels, stack map frames, line numbers, etc.). * * @author Eric Bruneton */ public class Label { - /** - * Indicates if this label is only used for debug attributes. Such a label - * is not the start of a basic block, the target of a jump instruction, or - * an exception handler. It can be safely ignored in control flow graph - * analysis algorithms (for optimization purposes). - */ - static final int DEBUG = 1; - - /** - * Indicates if the position of this label is known. - */ - static final int RESOLVED = 2; - - /** - * Indicates if this label has been updated, after instruction resizing. - */ - static final int RESIZED = 4; - - /** - * Indicates if this basic block has been pushed in the basic block stack. - * See {@link MethodWriter#visitMaxs visitMaxs}. - */ - static final int PUSHED = 8; - - /** - * Indicates if this label is the target of a jump instruction, or the start - * of an exception handler. - */ - static final int TARGET = 16; - - /** - * Indicates if a stack map frame must be stored for this label. - */ - static final int STORE = 32; - - /** - * Indicates if this label corresponds to a reachable basic block. - */ - static final int REACHABLE = 64; - - /** - * Indicates if this basic block ends with a JSR instruction. - */ - static final int JSR = 128; - - /** - * Indicates if this basic block ends with a RET instruction. - */ - static final int RET = 256; - - /** - * Indicates if this basic block is the start of a subroutine. - */ - static final int SUBROUTINE = 512; - - /** - * Indicates if this subroutine basic block has been visited by a - * visitSubroutine(null, ...) call. - */ - static final int VISITED = 1024; - - /** - * Indicates if this subroutine basic block has been visited by a - * visitSubroutine(!null, ...) call. - */ - static final int VISITED2 = 2048; - - /** - * Field used to associate user information to a label. Warning: this field - * is used by the ASM tree package. In order to use it with the ASM tree - * package you must override the - * {@link clojure.asm.tree.MethodNode#getLabelNode} method. - */ - public Object info; - - /** - * Flags that indicate the status of this label. - * - * @see #DEBUG - * @see #RESOLVED - * @see #RESIZED - * @see #PUSHED - * @see #TARGET - * @see #STORE - * @see #REACHABLE - * @see #JSR - * @see #RET - */ - int status; - - /** - * The line number corresponding to this label, if known. - */ - int line; - - /** - * The position of this label in the code, if known. - */ - int position; - - /** - * Number of forward references to this label, times two. - */ - private int referenceCount; - - /** - * Informations about forward references. Each forward reference is - * described by two consecutive integers in this array: the first one is the - * position of the first byte of the bytecode instruction that contains the - * forward reference, while the second is the position of the first byte of - * the forward reference itself. In fact the sign of the first integer - * indicates if this reference uses 2 or 4 bytes, and its absolute value - * gives the position of the bytecode instruction. This array is also used - * as a bitset to store the subroutines to which a basic block belongs. This - * information is needed in {@linked MethodWriter#visitMaxs}, after all - * forward references have been resolved. Hence the same array can be used - * for both purposes without problems. - */ - private int[] srcAndRefPositions; - - // ------------------------------------------------------------------------ - - /* - * Fields for the control flow and data flow graph analysis algorithms (used - * to compute the maximum stack size or the stack map frames). A control - * flow graph contains one node per "basic block", and one edge per "jump" - * from one basic block to another. Each node (i.e., each basic block) is - * represented by the Label object that corresponds to the first instruction - * of this basic block. Each node also stores the list of its successors in - * the graph, as a linked list of Edge objects. - * - * The control flow analysis algorithms used to compute the maximum stack - * size or the stack map frames are similar and use two steps. The first - * step, during the visit of each instruction, builds information about the - * state of the local variables and the operand stack at the end of each - * basic block, called the "output frame", relatively to the frame - * state at the beginning of the basic block, which is called the "input - * frame", and which is unknown during this step. The second step, in - * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes - * information about the input frame of each basic block, from the input - * state of the first basic block (known from the method signature), and by - * the using the previously computed relative output frames. - * - * The algorithm used to compute the maximum stack size only computes the - * relative output and absolute input stack heights, while the algorithm - * used to compute stack map frames computes relative output frames and - * absolute input frames. - */ - - /** - * Start of the output stack relatively to the input stack. The exact - * semantics of this field depends on the algorithm that is used. - * - * When only the maximum stack size is computed, this field is the number of - * elements in the input stack. - * - * When the stack map frames are completely computed, this field is the - * offset of the first output stack element relatively to the top of the - * input stack. This offset is always negative or null. A null offset means - * that the output stack must be appended to the input stack. A -n offset - * means that the first n output stack elements must replace the top n input - * stack elements, and that the other elements must be appended to the input - * stack. - */ - int inputStackTop; - - /** - * Maximum height reached by the output stack, relatively to the top of the - * input stack. This maximum is always positive or null. - */ - int outputStackMax; - - /** - * Information about the input and output stack map frames of this basic - * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES} - * option is used. - */ - Frame frame; - - /** - * The successor of this label, in the order they are visited. This linked - * list does not include labels used for debug info only. If - * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it - * does not contain successive labels that denote the same bytecode position - * (in this case only the first label appears in this list). - */ - Label successor; - - /** - * The successors of this node in the control flow graph. These successors - * are stored in a linked list of {@link Edge Edge} objects, linked to each - * other by their {@link Edge#next} field. - */ - Edge successors; - - /** - * The next basic block in the basic block stack. This stack is used in the - * main loop of the fix point algorithm used in the second step of the - * control flow analysis algorithms. It is also used in - * {@link #visitSubroutine} to avoid using a recursive method. - * - * @see MethodWriter#visitMaxs - */ - Label next; - - // ------------------------------------------------------------------------ - // Constructor - // ------------------------------------------------------------------------ - - /** - * Constructs a new label. - */ - public Label() { + /** + * A flag indicating that a label is only used for debug attributes. Such a label is not the start + * of a basic block, the target of a jump instruction, or an exception handler. It can be safely + * ignored in control flow graph analysis algorithms (for optimization purposes). + */ + static final int FLAG_DEBUG_ONLY = 1; + + /** + * A flag indicating that a label is the target of a jump instruction, or the start of an + * exception handler. + */ + static final int FLAG_JUMP_TARGET = 2; + + /** A flag indicating that the bytecode offset of a label is known. */ + static final int FLAG_RESOLVED = 4; + + /** A flag indicating that a label corresponds to a reachable basic block. */ + static final int FLAG_REACHABLE = 8; + + /** + * A flag indicating that the basic block corresponding to a label ends with a subroutine call. By + * construction in {@link MethodWriter#visitJumpInsn}, labels with this flag set have at least two + * outgoing edges: + * + *
    + *
  • the first one corresponds to the instruction that follows the jsr instruction in the + * bytecode, i.e. where execution continues when it returns from the jsr call. This is a + * virtual control flow edge, since execution never goes directly from the jsr to the next + * instruction. Instead, it goes to the subroutine and eventually returns to the instruction + * following the jsr. This virtual edge is used to compute the real outgoing edges of the + * basic blocks ending with a ret instruction, in {@link #addSubroutineRetSuccessors}. + *
  • the second one corresponds to the target of the jsr instruction, + *
+ */ + static final int FLAG_SUBROUTINE_CALLER = 16; + + /** + * A flag indicating that the basic block corresponding to a label is the start of a subroutine. + */ + static final int FLAG_SUBROUTINE_START = 32; + + /** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */ + static final int FLAG_SUBROUTINE_END = 64; + + /** + * The number of elements to add to the {@link #otherLineNumbers} array when it needs to be + * resized to store a new source line number. + */ + static final int LINE_NUMBERS_CAPACITY_INCREMENT = 4; + + /** + * The number of elements to add to the {@link #forwardReferences} array when it needs to be + * resized to store a new forward reference. + */ + static final int FORWARD_REFERENCES_CAPACITY_INCREMENT = 6; + + /** + * The bit mask to extract the type of a forward reference to this label. The extracted type is + * either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link #FORWARD_REFERENCE_TYPE_WIDE}. + * + * @see #forwardReferences + */ + static final int FORWARD_REFERENCE_TYPE_MASK = 0xF0000000; + + /** + * The type of forward references stored with two bytes in the bytecode. This is the case, for + * instance, of a forward reference from an ifnull instruction. + */ + static final int FORWARD_REFERENCE_TYPE_SHORT = 0x10000000; + + /** + * The type of forward references stored in four bytes in the bytecode. This is the case, for + * instance, of a forward reference from a lookupswitch instruction. + */ + static final int FORWARD_REFERENCE_TYPE_WIDE = 0x20000000; + + /** + * The bit mask to extract the 'handle' of a forward reference to this label. The extracted handle + * is the bytecode offset where the forward reference value is stored (using either 2 or 4 bytes, + * as indicated by the {@link #FORWARD_REFERENCE_TYPE_MASK}). + * + * @see #forwardReferences + */ + static final int FORWARD_REFERENCE_HANDLE_MASK = 0x0FFFFFFF; + + /** + * A sentinel element used to indicate the end of a list of labels. + * + * @see #nextListElement + */ + static final Label EMPTY_LIST = new Label(); + + /** + * A user managed state associated with this label. Warning: this field is used by the ASM tree + * package. In order to use it with the ASM tree package you must override the getLabelNode method + * in MethodNode. + */ + public Object info; + + /** + * The type and status of this label or its corresponding basic block. Must be zero or more of + * {@link #FLAG_DEBUG_ONLY}, {@link #FLAG_JUMP_TARGET}, {@link #FLAG_RESOLVED}, {@link + * #FLAG_REACHABLE}, {@link #FLAG_SUBROUTINE_CALLER}, {@link #FLAG_SUBROUTINE_START}, {@link + * #FLAG_SUBROUTINE_END}. + */ + short flags; + + /** + * The source line number corresponding to this label, or 0. If there are several source line + * numbers corresponding to this label, the first one is stored in this field, and the remaining + * ones are stored in {@link #otherLineNumbers}. + */ + private short lineNumber; + + /** + * The source line numbers corresponding to this label, in addition to {@link #lineNumber}, or + * null. The first element of this array is the number n of source line numbers it contains, which + * are stored between indices 1 and n (inclusive). + */ + private int[] otherLineNumbers; + + /** + * The offset of this label in the bytecode of its method, in bytes. This value is set if and only + * if the {@link #FLAG_RESOLVED} flag is set. + */ + int bytecodeOffset; + + /** + * The forward references to this label. The first element is the number of forward references, + * times 2 (this corresponds to the index of the last element actually used in this array). Then, + * each forward reference is described with two consecutive integers noted + * 'sourceInsnBytecodeOffset' and 'reference': + * + *
    + *
  • 'sourceInsnBytecodeOffset' is the bytecode offset of the instruction that contains the + * forward reference, + *
  • 'reference' contains the type and the offset in the bytecode where the forward reference + * value must be stored, which can be extracted with {@link #FORWARD_REFERENCE_TYPE_MASK} + * and {@link #FORWARD_REFERENCE_HANDLE_MASK}. + *
+ * + * For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is + * equal to x, and 'reference' is of type {@link #FORWARD_REFERENCE_TYPE_SHORT} with value x + 1 + * (because the ifnull instruction uses a 2 bytes bytecode offset operand stored one byte after + * the start of the instruction itself). For the default case of a lookupswitch instruction at + * bytecode offset x, 'sourceInsnBytecodeOffset' is equal to x, and 'reference' is of type {@link + * #FORWARD_REFERENCE_TYPE_WIDE} with value between x + 1 and x + 4 (because the lookupswitch + * instruction uses a 4 bytes bytecode offset operand stored one to four bytes after the start of + * the instruction itself). + */ + private int[] forwardReferences; + + // ----------------------------------------------------------------------------------------------- + + // Fields for the control flow and data flow graph analysis algorithms (used to compute the + // maximum stack size or the stack map frames). A control flow graph contains one node per "basic + // block", and one edge per "jump" from one basic block to another. Each node (i.e., each basic + // block) is represented with the Label object that corresponds to the first instruction of this + // basic block. Each node also stores the list of its successors in the graph, as a linked list of + // Edge objects. + // + // The control flow analysis algorithms used to compute the maximum stack size or the stack map + // frames are similar and use two steps. The first step, during the visit of each instruction, + // builds information about the state of the local variables and the operand stack at the end of + // each basic block, called the "output frame", relatively to the frame state at the + // beginning of the basic block, which is called the "input frame", and which is unknown + // during this step. The second step, in {@link MethodWriter#computeAllFrames} and {@link + // MethodWriter#computeMaxStackAndLocal}, is a fix point algorithm + // that computes information about the input frame of each basic block, from the input state of + // the first basic block (known from the method signature), and by the using the previously + // computed relative output frames. + // + // The algorithm used to compute the maximum stack size only computes the relative output and + // absolute input stack heights, while the algorithm used to compute stack map frames computes + // relative output frames and absolute input frames. + + /** + * The number of elements in the input stack of the basic block corresponding to this label. This + * field is computed in {@link MethodWriter#computeMaxStackAndLocal}. + */ + short inputStackSize; + + /** + * The number of elements in the output stack, at the end of the basic block corresponding to this + * label. This field is only computed for basic blocks that end with a RET instruction. + */ + short outputStackSize; + + /** + * The maximum height reached by the output stack, relatively to the top of the input stack, in + * the basic block corresponding to this label. This maximum is always positive or null. + */ + short outputStackMax; + + /** + * The id of the subroutine to which this basic block belongs, or 0. If the basic block belongs to + * several subroutines, this is the id of the "oldest" subroutine that contains it (with the + * convention that a subroutine calling another one is "older" than the callee). This field is + * computed in {@link MethodWriter#computeMaxStackAndLocal}, if the method contains JSR + * instructions. + */ + short subroutineId; + + /** + * The input and output stack map frames of the basic block corresponding to this label. This + * field is only used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} or {@link + * MethodWriter#COMPUTE_INSERTED_FRAMES} option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited in {@link MethodVisitor#visitLabel}. + * This linked list does not include labels used for debug info only. If the {@link + * MethodWriter#COMPUTE_ALL_FRAMES} or {@link MethodWriter#COMPUTE_INSERTED_FRAMES} option is used + * then it does not contain either successive labels that denote the same bytecode offset (in this + * case only the first label appears in this list). + */ + Label nextBasicBlock; + + /** + * The outgoing edges of the basic block corresponding to this label, in the control flow graph of + * its method. These edges are stored in a linked list of {@link Edge} objects, linked to each + * other by their {@link Edge#nextEdge} field. + */ + Edge outgoingEdges; + + /** + * The next element in the list of labels to which this label belongs, or null if it does not + * belong to any list. All lists of labels must end with the {@link #EMPTY_LIST} sentinel, in + * order to ensure that this field is null if and only if this label does not belong to a list of + * labels. Note that there can be several lists of labels at the same time, but that a label can + * belong to at most one list at a time (unless some lists share a common tail, but this is not + * used in practice). + * + *

List of labels are used in {@link MethodWriter#computeAllFrames} and {@link + * MethodWriter#computeMaxStackAndLocal} to compute stack map frames and the maximum stack size, + * respectively, as well as in {@link #markSubroutine} and {@link #addSubroutineRetSuccessors} to + * compute the basic blocks belonging to subroutines and their outgoing edges. Outside of these + * methods, this field should be null (this property is a precondition and a postcondition of + * these methods). + */ + Label nextListElement; + + // ----------------------------------------------------------------------------------------------- + // Constructor and accessors + // ----------------------------------------------------------------------------------------------- + + /** Constructs a new label. */ + public Label() { + // Nothing to do. + } + + /** + * Returns the bytecode offset corresponding to this label. This offset is computed from the start + * of the method's bytecode. This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @return the bytecode offset corresponding to this label. + * @throws IllegalStateException if this label is not resolved yet. + */ + public int getOffset() { + if ((flags & FLAG_RESOLVED) == 0) { + throw new IllegalStateException("Label offset position has not been resolved yet"); } - - // ------------------------------------------------------------------------ - // Methods to compute offsets and to manage forward references - // ------------------------------------------------------------------------ - - /** - * Returns the offset corresponding to this label. This offset is computed - * from the start of the method's bytecode. This method is intended for - * {@link Attribute} sub classes, and is normally not needed by class - * generators or adapters. - * - * @return the offset corresponding to this label. - * @throws IllegalStateException - * if this label is not resolved yet. - */ - public int getOffset() { - if ((status & RESOLVED) == 0) { - throw new IllegalStateException( - "Label offset position has not been resolved yet"); - } - return position; + return bytecodeOffset; + } + + /** + * Returns the "canonical" {@link Label} instance corresponding to this label's bytecode offset, + * if known, otherwise the label itself. The canonical instance is the first label (in the order + * of their visit by {@link MethodVisitor#visitLabel}) corresponding to this bytecode offset. It + * cannot be known for labels which have not been visited yet. + * + *

This method should only be used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} option + * is used. + * + * @return the label itself if {@link #frame} is null, otherwise the Label's frame owner. This + * corresponds to the "canonical" label instance described above thanks to the way the label + * frame is set in {@link MethodWriter#visitLabel}. + */ + final Label getCanonicalInstance() { + return frame == null ? this : frame.owner; + } + + // ----------------------------------------------------------------------------------------------- + // Methods to manage line numbers + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a source line number corresponding to this label. + * + * @param lineNumber a source line number (which should be strictly positive). + */ + final void addLineNumber(final int lineNumber) { + if (this.lineNumber == 0) { + this.lineNumber = (short) lineNumber; + } else { + if (otherLineNumbers == null) { + otherLineNumbers = new int[LINE_NUMBERS_CAPACITY_INCREMENT]; + } + int otherLineNumberIndex = ++otherLineNumbers[0]; + if (otherLineNumberIndex >= otherLineNumbers.length) { + int[] newLineNumbers = new int[otherLineNumbers.length + LINE_NUMBERS_CAPACITY_INCREMENT]; + System.arraycopy(otherLineNumbers, 0, newLineNumbers, 0, otherLineNumbers.length); + otherLineNumbers = newLineNumbers; + } + otherLineNumbers[otherLineNumberIndex] = lineNumber; } - - /** - * Puts a reference to this label in the bytecode of a method. If the - * position of the label is known, the offset is computed and written - * directly. Otherwise, a null offset is written and a new forward reference - * is declared for this label. - * - * @param owner - * the code writer that calls this method. - * @param out - * the bytecode of the method. - * @param source - * the position of first byte of the bytecode instruction that - * contains this label. - * @param wideOffset - * true if the reference must be stored in 4 bytes, or - * false if it must be stored with 2 bytes. - * @throws IllegalArgumentException - * if this label has not been created by the given code writer. - */ - void put(final MethodWriter owner, final ByteVector out, final int source, - final boolean wideOffset) { - if ((status & RESOLVED) == 0) { - if (wideOffset) { - addReference(-1 - source, out.length); - out.putInt(-1); - } else { - addReference(source, out.length); - out.putShort(-1); - } - } else { - if (wideOffset) { - out.putInt(position - source); - } else { - out.putShort(position - source); - } + } + + /** + * Makes the given visitor visit this label and its source line numbers, if applicable. + * + * @param methodVisitor a method visitor. + * @param visitLineNumbers whether to visit of the label's source line numbers, if any. + */ + final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) { + methodVisitor.visitLabel(this); + if (visitLineNumbers && lineNumber != 0) { + methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this); + if (otherLineNumbers != null) { + for (int i = 1; i <= otherLineNumbers[0]; ++i) { + methodVisitor.visitLineNumber(otherLineNumbers[i], this); } + } } - - /** - * Adds a forward reference to this label. This method must be called only - * for a true forward reference, i.e. only if this label is not resolved - * yet. For backward references, the offset of the reference can be, and - * must be, computed and stored directly. - * - * @param sourcePosition - * the position of the referencing instruction. This position - * will be used to compute the offset of this forward reference. - * @param referencePosition - * the position where the offset for this forward reference must - * be stored. - */ - private void addReference(final int sourcePosition, - final int referencePosition) { - if (srcAndRefPositions == null) { - srcAndRefPositions = new int[6]; - } - if (referenceCount >= srcAndRefPositions.length) { - int[] a = new int[srcAndRefPositions.length + 6]; - System.arraycopy(srcAndRefPositions, 0, a, 0, - srcAndRefPositions.length); - srcAndRefPositions = a; - } - srcAndRefPositions[referenceCount++] = sourcePosition; - srcAndRefPositions[referenceCount++] = referencePosition; + } + + // ----------------------------------------------------------------------------------------------- + // Methods to compute offsets and to manage forward references + // ----------------------------------------------------------------------------------------------- + + /** + * Puts a reference to this label in the bytecode of a method. If the bytecode offset of the label + * is known, the relative bytecode offset between the label and the instruction referencing it is + * computed and written directly. Otherwise, a null relative offset is written and a new forward + * reference is declared for this label. + * + * @param code the bytecode of the method. This is where the reference is appended. + * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the + * reference to be appended. + * @param wideReference whether the reference must be stored in 4 bytes (instead of 2 bytes). + */ + final void put( + final ByteVector code, final int sourceInsnBytecodeOffset, final boolean wideReference) { + if ((flags & FLAG_RESOLVED) == 0) { + if (wideReference) { + addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_WIDE, code.length); + code.putInt(-1); + } else { + addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_SHORT, code.length); + code.putShort(-1); + } + } else { + if (wideReference) { + code.putInt(bytecodeOffset - sourceInsnBytecodeOffset); + } else { + code.putShort(bytecodeOffset - sourceInsnBytecodeOffset); + } } - - /** - * Resolves all forward references to this label. This method must be called - * when this label is added to the bytecode of the method, i.e. when its - * position becomes known. This method fills in the blanks that where left - * in the bytecode by each forward reference previously added to this label. - * - * @param owner - * the code writer that calls this method. - * @param position - * the position of this label in the bytecode. - * @param data - * the bytecode of the method. - * @return true if a blank that was left for this label was to - * small to store the offset. In such a case the corresponding jump - * instruction is replaced with a pseudo instruction (using unused - * opcodes) using an unsigned two bytes offset. These pseudo - * instructions will need to be replaced with true instructions with - * wider offsets (4 bytes instead of 2). This is done in - * {@link MethodWriter#resizeInstructions}. - * @throws IllegalArgumentException - * if this label has already been resolved, or if it has not - * been created by the given code writer. - */ - boolean resolve(final MethodWriter owner, final int position, - final byte[] data) { - boolean needUpdate = false; - this.status |= RESOLVED; - this.position = position; - int i = 0; - while (i < referenceCount) { - int source = srcAndRefPositions[i++]; - int reference = srcAndRefPositions[i++]; - int offset; - if (source >= 0) { - offset = position - source; - if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { - /* - * changes the opcode of the jump instruction, in order to - * be able to find it later (see resizeInstructions in - * MethodWriter). These temporary opcodes are similar to - * jump instruction opcodes, except that the 2 bytes offset - * is unsigned (and can therefore represent values from 0 to - * 65535, which is sufficient since the size of a method is - * limited to 65535 bytes). - */ - int opcode = data[reference - 1] & 0xFF; - if (opcode <= Opcodes.JSR) { - // changes IFEQ ... JSR to opcodes 202 to 217 - data[reference - 1] = (byte) (opcode + 49); - } else { - // changes IFNULL and IFNONNULL to opcodes 218 and 219 - data[reference - 1] = (byte) (opcode + 20); - } - needUpdate = true; - } - data[reference++] = (byte) (offset >>> 8); - data[reference] = (byte) offset; - } else { - offset = position + source + 1; - data[reference++] = (byte) (offset >>> 24); - data[reference++] = (byte) (offset >>> 16); - data[reference++] = (byte) (offset >>> 8); - data[reference] = (byte) offset; - } - } - return needUpdate; + } + + /** + * Adds a forward reference to this label. This method must be called only for a true forward + * reference, i.e. only if this label is not resolved yet. For backward references, the relative + * bytecode offset of the reference can be, and must be, computed and stored directly. + * + * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the + * reference stored at referenceHandle. + * @param referenceType either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link + * #FORWARD_REFERENCE_TYPE_WIDE}. + * @param referenceHandle the offset in the bytecode where the forward reference value must be + * stored. + */ + private void addForwardReference( + final int sourceInsnBytecodeOffset, final int referenceType, final int referenceHandle) { + if (forwardReferences == null) { + forwardReferences = new int[FORWARD_REFERENCES_CAPACITY_INCREMENT]; } - - /** - * Returns the first label of the series to which this label belongs. For an - * isolated label or for the first label in a series of successive labels, - * this method returns the label itself. For other labels it returns the - * first label of the series. - * - * @return the first label of the series to which this label belongs. - */ - Label getFirst() { - return !ClassReader.FRAMES || frame == null ? this : frame.owner; + int lastElementIndex = forwardReferences[0]; + if (lastElementIndex + 2 >= forwardReferences.length) { + int[] newValues = new int[forwardReferences.length + FORWARD_REFERENCES_CAPACITY_INCREMENT]; + System.arraycopy(forwardReferences, 0, newValues, 0, forwardReferences.length); + forwardReferences = newValues; } - - // ------------------------------------------------------------------------ - // Methods related to subroutines - // ------------------------------------------------------------------------ - - /** - * Returns true is this basic block belongs to the given subroutine. - * - * @param id - * a subroutine id. - * @return true is this basic block belongs to the given subroutine. - */ - boolean inSubroutine(final long id) { - if ((status & Label.VISITED) != 0) { - return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0; - } - return false; + forwardReferences[++lastElementIndex] = sourceInsnBytecodeOffset; + forwardReferences[++lastElementIndex] = referenceType | referenceHandle; + forwardReferences[0] = lastElementIndex; + } + + /** + * Sets the bytecode offset of this label to the given value and resolves the forward references + * to this label, if any. This method must be called when this label is added to the bytecode of + * the method, i.e. when its bytecode offset becomes known. This method fills in the blanks that + * where left in the bytecode by each forward reference previously added to this label. + * + * @param code the bytecode of the method. + * @param bytecodeOffset the bytecode offset of this label. + * @return true if a blank that was left for this label was too small to store the + * offset. In such a case the corresponding jump instruction is replaced with an equivalent + * ASM specific instruction using an unsigned two bytes offset. These ASM specific + * instructions are later replaced with standard bytecode instructions with wider offsets (4 + * bytes instead of 2), in ClassReader. + */ + final boolean resolve(final byte[] code, final int bytecodeOffset) { + this.flags |= FLAG_RESOLVED; + this.bytecodeOffset = bytecodeOffset; + if (forwardReferences == null) { + return false; } - - /** - * Returns true if this basic block and the given one belong to a common - * subroutine. - * - * @param block - * another basic block. - * @return true if this basic block and the given one belong to a common - * subroutine. - */ - boolean inSameSubroutine(final Label block) { - if ((status & VISITED) == 0 || (block.status & VISITED) == 0) { - return false; - } - for (int i = 0; i < srcAndRefPositions.length; ++i) { - if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) { - return true; - } + boolean hasAsmInstructions = false; + for (int i = forwardReferences[0]; i > 0; i -= 2) { + final int sourceInsnBytecodeOffset = forwardReferences[i - 1]; + final int reference = forwardReferences[i]; + final int relativeOffset = bytecodeOffset - sourceInsnBytecodeOffset; + int handle = reference & FORWARD_REFERENCE_HANDLE_MASK; + if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_SHORT) { + if (relativeOffset < Short.MIN_VALUE || relativeOffset > Short.MAX_VALUE) { + // Change the opcode of the jump instruction, in order to be able to find it later in + // ClassReader. These ASM specific opcodes are similar to jump instruction opcodes, except + // that the 2 bytes offset is unsigned (and can therefore represent values from 0 to + // 65535, which is sufficient since the size of a method is limited to 65535 bytes). + int opcode = code[sourceInsnBytecodeOffset] & 0xFF; + if (opcode < Opcodes.IFNULL) { + // Change IFEQ ... JSR to ASM_IFEQ ... ASM_JSR. + code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_OPCODE_DELTA); + } else { + // Change IFNULL and IFNONNULL to ASM_IFNULL and ASM_IFNONNULL. + code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_IFNULL_OPCODE_DELTA); + } + hasAsmInstructions = true; } - return false; + code[handle++] = (byte) (relativeOffset >>> 8); + code[handle] = (byte) relativeOffset; + } else { + code[handle++] = (byte) (relativeOffset >>> 24); + code[handle++] = (byte) (relativeOffset >>> 16); + code[handle++] = (byte) (relativeOffset >>> 8); + code[handle] = (byte) relativeOffset; + } } - - /** - * Marks this basic block as belonging to the given subroutine. - * - * @param id - * a subroutine id. - * @param nbSubroutines - * the total number of subroutines in the method. - */ - void addToSubroutine(final long id, final int nbSubroutines) { - if ((status & VISITED) == 0) { - status |= VISITED; - srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1]; - } - srcAndRefPositions[(int) (id >>> 32)] |= (int) id; + return hasAsmInstructions; + } + + // ----------------------------------------------------------------------------------------------- + // Methods related to subroutines + // ----------------------------------------------------------------------------------------------- + + /** + * Finds the basic blocks that belong to the subroutine starting with the basic block + * corresponding to this label, and marks these blocks as belonging to this subroutine. This + * method follows the control flow graph to find all the blocks that are reachable from the + * current basic block WITHOUT following any jsr target. + * + *

Note: a precondition and postcondition of this method is that all labels must have a null + * {@link #nextListElement}. + * + * @param subroutineId the id of the subroutine starting with the basic block corresponding to + * this label. + */ + final void markSubroutine(final short subroutineId) { + // Data flow algorithm: put this basic block in a list of blocks to process (which are blocks + // belonging to subroutine subroutineId) and, while there are blocks to process, remove one from + // the list, mark it as belonging to the subroutine, and add its successor basic blocks in the + // control flow graph to the list of blocks to process (if not already done). + Label listOfBlocksToProcess = this; + listOfBlocksToProcess.nextListElement = EMPTY_LIST; + while (listOfBlocksToProcess != EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + basicBlock.nextListElement = null; + + // If it is not already marked as belonging to a subroutine, mark it as belonging to + // subroutineId and add its successors to the list of blocks to process (unless already done). + if (basicBlock.subroutineId == 0) { + basicBlock.subroutineId = subroutineId; + listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess); + } } - - /** - * Finds the basic blocks that belong to a given subroutine, and marks these - * blocks as belonging to this subroutine. This method follows the control - * flow graph to find all the blocks that are reachable from the current - * block WITHOUT following any JSR target. - * - * @param JSR - * a JSR block that jumps to this subroutine. If this JSR is not - * null it is added to the successor of the RET blocks found in - * the subroutine. - * @param id - * the id of this subroutine. - * @param nbSubroutines - * the total number of subroutines in the method. - */ - void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) { - // user managed stack of labels, to avoid using a recursive method - // (recursivity can lead to stack overflow with very large methods) - Label stack = this; - while (stack != null) { - // removes a label l from the stack - Label l = stack; - stack = l.next; - l.next = null; - - if (JSR != null) { - if ((l.status & VISITED2) != 0) { - continue; - } - l.status |= VISITED2; - // adds JSR to the successors of l, if it is a RET block - if ((l.status & RET) != 0) { - if (!l.inSameSubroutine(JSR)) { - Edge e = new Edge(); - e.info = l.inputStackTop; - e.successor = JSR.successors.successor; - e.next = l.successors; - l.successors = e; - } - } - } else { - // if the l block already belongs to subroutine 'id', continue - if (l.inSubroutine(id)) { - continue; - } - // marks the l block as belonging to subroutine 'id' - l.addToSubroutine(id, nbSubroutines); - } - // pushes each successor of l on the stack, except JSR targets - Edge e = l.successors; - while (e != null) { - // if the l block is a JSR block, then 'l.successors.next' leads - // to the JSR target (see {@link #visitJumpInsn}) and must - // therefore not be followed - if ((l.status & Label.JSR) == 0 || e != l.successors.next) { - // pushes e.successor on the stack if it not already added - if (e.successor.next == null) { - e.successor.next = stack; - stack = e.successor; - } - } - e = e.next; - } - } + } + + /** + * Finds the basic blocks that end a subroutine starting with the basic block corresponding to + * this label and, for each one of them, adds an outgoing edge to the basic block following the + * given subroutine call. In other words, completes the control flow graph by adding the edges + * corresponding to the return from this subroutine, when called from the given caller basic + * block. + * + *

Note: a precondition and postcondition of this method is that all labels must have a null + * {@link #nextListElement}. + * + * @param subroutineCaller a basic block that ends with a jsr to the basic block corresponding to + * this label. This label is supposed to correspond to the start of a subroutine. + */ + final void addSubroutineRetSuccessors(final Label subroutineCaller) { + // Data flow algorithm: put this basic block in a list blocks to process (which are blocks + // belonging to a subroutine starting with this label) and, while there are blocks to process, + // remove one from the list, put it in a list of blocks that have been processed, add a return + // edge to the successor of subroutineCaller if applicable, and add its successor basic blocks + // in the control flow graph to the list of blocks to process (if not already done). + Label listOfProcessedBlocks = EMPTY_LIST; + Label listOfBlocksToProcess = this; + listOfBlocksToProcess.nextListElement = EMPTY_LIST; + while (listOfBlocksToProcess != EMPTY_LIST) { + // Move a basic block from the list of blocks to process to the list of processed blocks. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = basicBlock.nextListElement; + basicBlock.nextListElement = listOfProcessedBlocks; + listOfProcessedBlocks = basicBlock; + + // Add an edge from this block to the successor of the caller basic block, if this block is + // the end of a subroutine and if this block and subroutineCaller do not belong to the same + // subroutine. + if ((basicBlock.flags & FLAG_SUBROUTINE_END) != 0 + && basicBlock.subroutineId != subroutineCaller.subroutineId) { + basicBlock.outgoingEdges = + new Edge( + basicBlock.outputStackSize, + // By construction, the first outgoing edge of a basic block that ends with a jsr + // instruction leads to the jsr continuation block, i.e. where execution continues + // when ret is called (see {@link #FLAG_SUBROUTINE_CALLER}). + subroutineCaller.outgoingEdges.successor, + basicBlock.outgoingEdges); + } + // Add its successors to the list of blocks to process. Note that {@link #pushSuccessors} does + // not push basic blocks which are already in a list. Here this means either in the list of + // blocks to process, or in the list of already processed blocks. This second list is + // important to make sure we don't reprocess an already processed block. + listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess); } - - // ------------------------------------------------------------------------ - // Overriden Object methods - // ------------------------------------------------------------------------ - - /** - * Returns a string representation of this label. - * - * @return a string representation of this label. - */ - @Override - public String toString() { - return "L" + System.identityHashCode(this); + // Reset the {@link #nextListElement} of all the basic blocks that have been processed to null, + // so that this method can be called again with a different subroutine or subroutine caller. + while (listOfProcessedBlocks != EMPTY_LIST) { + Label newListOfProcessedBlocks = listOfProcessedBlocks.nextListElement; + listOfProcessedBlocks.nextListElement = null; + listOfProcessedBlocks = newListOfProcessedBlocks; + } + } + + /** + * Adds the successors of this label in the method's control flow graph (except those + * corresponding to a jsr target, and those already in a list of labels) to the given list of + * blocks to process, and returns the new list. + * + * @param listOfLabelsToProcess a list of basic blocks to process, linked together with their + * {@link #nextListElement} field. + * @return the new list of blocks to process. + */ + private Label pushSuccessors(final Label listOfLabelsToProcess) { + Label newListOfLabelsToProcess = listOfLabelsToProcess; + Edge outgoingEdge = outgoingEdges; + while (outgoingEdge != null) { + // By construction, the second outgoing edge of a basic block that ends with a jsr instruction + // leads to the jsr target (see {@link #FLAG_SUBROUTINE_CALLER}). + boolean isJsrTarget = + (flags & Label.FLAG_SUBROUTINE_CALLER) != 0 && outgoingEdge == outgoingEdges.nextEdge; + if (!isJsrTarget && outgoingEdge.successor.nextListElement == null) { + // Add this successor to the list of blocks to process, if it does not already belong to a + // list of labels. + outgoingEdge.successor.nextListElement = newListOfLabelsToProcess; + newListOfLabelsToProcess = outgoingEdge.successor; + } + outgoingEdge = outgoingEdge.nextEdge; } + return newListOfLabelsToProcess; + } + + // ----------------------------------------------------------------------------------------------- + // Overridden Object methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } } diff --git a/src/jvm/clojure/asm/MethodVisitor.java b/src/jvm/clojure/asm/MethodVisitor.java index 347455e284..4489612134 100644 --- a/src/jvm/clojure/asm/MethodVisitor.java +++ b/src/jvm/clojure/asm/MethodVisitor.java @@ -1,662 +1,786 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A visitor to visit a Java method. The methods of this class must be called in - * the following order: [ visitAnnotationDefault ] ( - * visitAnnotation | visitParameterAnnotation | - * visitAttribute )* [ visitCode ( visitFrame | - * visitXInsn | visitLabel | - * visitTryCatchBlock | visitLocalVariable | - * visitLineNumber )* visitMaxs ] visitEnd. In - * addition, the visitXInsn and visitLabel methods - * must be called in the sequential order of the bytecode instructions of the - * visited code, visitTryCatchBlock must be called before the - * labels passed as arguments have been visited, and the - * visitLocalVariable and visitLineNumber methods must be - * called after the labels passed as arguments have been visited. + * A visitor to visit a Java method. The methods of this class must be called in the following + * order: ( visitParameter )* [ visitAnnotationDefault ] ( + * visitAnnotation | visitAnnotableParameterCount | + * visitParameterAnnotation visitTypeAnnotation | visitAttribute )* [ + * visitCode ( visitFrame | visitXInsn | visitLabel | + * visitInsnAnnotation | visitTryCatchBlock | visitTryCatchAnnotation | + * visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )* + * visitMaxs ] visitEnd. In addition, the visitXInsn and + * visitLabel methods must be called in the sequential order of the bytecode instructions + * of the visited code, visitInsnAnnotation must be called after the annotated + * instruction, visitTryCatchBlock must be called before the labels passed as + * arguments have been visited, visitTryCatchBlockAnnotation must be called after + * the corresponding try catch block has been visited, and the visitLocalVariable, + * visitLocalVariableAnnotation and visitLineNumber methods must be called + * after the labels passed as arguments have been visited. * * @author Eric Bruneton */ public abstract class MethodVisitor { - /** - * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. - */ - protected final int api; - - /** - * The method visitor to which this visitor must delegate method calls. May - * be null. - */ - protected MethodVisitor mv; - - /** - * Constructs a new {@link MethodVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - */ - public MethodVisitor(final int api) { - this(api, null); - } - - /** - * Constructs a new {@link MethodVisitor}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - * @param mv - * the method visitor to which this visitor must delegate method - * calls. May be null. - */ - public MethodVisitor(final int api, final MethodVisitor mv) { - if (api != Opcodes.ASM4) { - throw new IllegalArgumentException(); - } - this.api = api; - this.mv = mv; - } - - // ------------------------------------------------------------------------- - // Annotations and non standard attributes - // ------------------------------------------------------------------------- - - /** - * Visits the default value of this annotation interface method. - * - * @return a visitor to the visit the actual default value of this - * annotation interface method, or null if this visitor is - * not interested in visiting this default value. The 'name' - * parameters passed to the methods of this annotation visitor are - * ignored. Moreover, exacly one visit method must be called on this - * annotation visitor, followed by visitEnd. - */ - public AnnotationVisitor visitAnnotationDefault() { - if (mv != null) { - return mv.visitAnnotationDefault(); - } - return null; - } - - /** - * Visits an annotation of this method. - * - * @param desc - * the class descriptor of the annotation class. - * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if - * this visitor is not interested in visiting this annotation. - */ - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - if (mv != null) { - return mv.visitAnnotation(desc, visible); - } - return null; - } - - /** - * Visits an annotation of a parameter this method. - * - * @param parameter - * the parameter index. - * @param desc - * the class descriptor of the annotation class. - * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if - * this visitor is not interested in visiting this annotation. - */ - public AnnotationVisitor visitParameterAnnotation(int parameter, - String desc, boolean visible) { - if (mv != null) { - return mv.visitParameterAnnotation(parameter, desc, visible); - } - return null; - } - - /** - * Visits a non standard attribute of this method. - * - * @param attr - * an attribute. - */ - public void visitAttribute(Attribute attr) { - if (mv != null) { - mv.visitAttribute(attr); - } - } - - /** - * Starts the visit of the method's code, if any (i.e. non abstract method). - */ - public void visitCode() { - if (mv != null) { - mv.visitCode(); - } - } - - /** - * Visits the current state of the local variables and operand stack - * elements. This method must(*) be called just before any - * instruction i that follows an unconditional branch instruction - * such as GOTO or THROW, that is the target of a jump instruction, or that - * starts an exception handler block. The visited types must describe the - * values of the local variables and of the operand stack elements just - * before i is executed.
- *
- * (*) this is mandatory only for classes whose version is greater than or - * equal to {@link Opcodes#V1_6 V1_6}.
- *
- * The frames of a method must be given either in expanded form, or in - * compressed form (all frames must use the same format, i.e. you must not - * mix expanded and compressed frames within a single method): - *

    - *
  • In expanded form, all frames must have the F_NEW type.
  • - *
  • In compressed form, frames are basically "deltas" from the state of - * the previous frame: - *
      - *
    • {@link Opcodes#F_SAME} representing frame with exactly the same - * locals as the previous frame and with the empty stack.
    • - *
    • {@link Opcodes#F_SAME1} representing frame with exactly the same - * locals as the previous frame and with single value on the stack ( - * nStack is 1 and stack[0] contains value for the - * type of the stack item).
    • - *
    • {@link Opcodes#F_APPEND} representing frame with current locals are - * the same as the locals in the previous frame, except that additional - * locals are defined (nLocal is 1, 2 or 3 and - * local elements contains values representing added types).
    • - *
    • {@link Opcodes#F_CHOP} representing frame with current locals are the - * same as the locals in the previous frame, except that the last 1-3 locals - * are absent and with the empty stack (nLocals is 1, 2 or 3).
    • - *
    • {@link Opcodes#F_FULL} representing complete frame data.
    • - *
    - *

- * In both cases the first frame, corresponding to the method's parameters - * and access flags, is implicit and must not be visited. Also, it is - * illegal to visit two or more frames for the same code location (i.e., at - * least one instruction must be visited between two calls to visitFrame). - * - * @param type - * the type of this stack map frame. Must be - * {@link Opcodes#F_NEW} for expanded frames, or - * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, - * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or - * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for - * compressed frames. - * @param nLocal - * the number of local variables in the visited frame. - * @param local - * the local variable types in this frame. This array must not be - * modified. Primitive types are represented by - * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, - * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, - * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or - * {@link Opcodes#UNINITIALIZED_THIS} (long and double are - * represented by a single element). Reference types are - * represented by String objects (representing internal names), - * and uninitialized types by Label objects (this label - * designates the NEW instruction that created this uninitialized - * value). - * @param nStack - * the number of operand stack elements in the visited frame. - * @param stack - * the operand stack types in this frame. This array must not be - * modified. Its content has the same format as the "local" - * array. - * @throws IllegalStateException - * if a frame is visited just after another one, without any - * instruction between the two (unless this frame is a - * Opcodes#F_SAME frame, in which case it is silently ignored). - */ - public void visitFrame(int type, int nLocal, Object[] local, int nStack, - Object[] stack) { - if (mv != null) { - mv.visitFrame(type, nLocal, local, nStack, stack); - } - } - - // ------------------------------------------------------------------------- - // Normal instructions - // ------------------------------------------------------------------------- - - /** - * Visits a zero operand instruction. - * - * @param opcode - * the opcode of the instruction to be visited. This opcode is - * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, - * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, - * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, - * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, - * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, - * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, - * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, - * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, - * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, - * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, - * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, - * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, - * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, - * or MONITOREXIT. - */ - public void visitInsn(int opcode) { - if (mv != null) { - mv.visitInsn(opcode); - } - } - - /** - * Visits an instruction with a single int operand. - * - * @param opcode - * the opcode of the instruction to be visited. This opcode is - * either BIPUSH, SIPUSH or NEWARRAY. - * @param operand - * the operand of the instruction to be visited.
- * When opcode is BIPUSH, operand value should be between - * Byte.MIN_VALUE and Byte.MAX_VALUE.
- * When opcode is SIPUSH, operand value should be between - * Short.MIN_VALUE and Short.MAX_VALUE.
- * When opcode is NEWARRAY, operand value should be one of - * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR}, - * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, - * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT}, - * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. - */ - public void visitIntInsn(int opcode, int operand) { - if (mv != null) { - mv.visitIntInsn(opcode, operand); - } - } - - /** - * Visits a local variable instruction. A local variable instruction is an - * instruction that loads or stores the value of a local variable. - * - * @param opcode - * the opcode of the local variable instruction to be visited. - * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, - * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. - * @param var - * the operand of the instruction to be visited. This operand is - * the index of a local variable. - */ - public void visitVarInsn(int opcode, int var) { - if (mv != null) { - mv.visitVarInsn(opcode, var); - } - } - - /** - * Visits a type instruction. A type instruction is an instruction that - * takes the internal name of a class as parameter. - * - * @param opcode - * the opcode of the type instruction to be visited. This opcode - * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. - * @param type - * the operand of the instruction to be visited. This operand - * must be the internal name of an object or array class (see - * {@link Type#getInternalName() getInternalName}). - */ - public void visitTypeInsn(int opcode, String type) { - if (mv != null) { - mv.visitTypeInsn(opcode, type); - } - } - - /** - * Visits a field instruction. A field instruction is an instruction that - * loads or stores the value of a field of an object. - * - * @param opcode - * the opcode of the type instruction to be visited. This opcode - * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. - * @param owner - * the internal name of the field's owner class (see - * {@link Type#getInternalName() getInternalName}). - * @param name - * the field's name. - * @param desc - * the field's descriptor (see {@link Type Type}). - */ - public void visitFieldInsn(int opcode, String owner, String name, - String desc) { - if (mv != null) { - mv.visitFieldInsn(opcode, owner, name, desc); - } - } - - /** - * Visits a method instruction. A method instruction is an instruction that - * invokes a method. - * - * @param opcode - * the opcode of the type instruction to be visited. This opcode - * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or - * INVOKEINTERFACE. - * @param owner - * the internal name of the method's owner class (see - * {@link Type#getInternalName() getInternalName}). - * @param name - * the method's name. - * @param desc - * the method's descriptor (see {@link Type Type}). - */ - public void visitMethodInsn(int opcode, String owner, String name, - String desc) { - if (mv != null) { - mv.visitMethodInsn(opcode, owner, name, desc); - } - } - - /** - * Visits an invokedynamic instruction. - * - * @param name - * the method's name. - * @param desc - * the method's descriptor (see {@link Type Type}). - * @param bsm - * the bootstrap method. - * @param bsmArgs - * the bootstrap method constant arguments. Each argument must be - * an {@link Integer}, {@link Float}, {@link Long}, - * {@link Double}, {@link String}, {@link Type} or {@link Handle} - * value. This method is allowed to modify the content of the - * array so a caller should expect that this array may change. - */ - public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, - Object... bsmArgs) { - if (mv != null) { - mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); - } - } - - /** - * Visits a jump instruction. A jump instruction is an instruction that may - * jump to another instruction. - * - * @param opcode - * the opcode of the type instruction to be visited. This opcode - * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, - * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, - * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. - * @param label - * the operand of the instruction to be visited. This operand is - * a label that designates the instruction to which the jump - * instruction may jump. - */ - public void visitJumpInsn(int opcode, Label label) { - if (mv != null) { - mv.visitJumpInsn(opcode, label); - } - } - - /** - * Visits a label. A label designates the instruction that will be visited - * just after it. - * - * @param label - * a {@link Label Label} object. - */ - public void visitLabel(Label label) { - if (mv != null) { - mv.visitLabel(label); - } - } - - // ------------------------------------------------------------------------- - // Special instructions - // ------------------------------------------------------------------------- - - /** - * Visits a LDC instruction. Note that new constant types may be added in - * future versions of the Java Virtual Machine. To easily detect new - * constant types, implementations of this method should check for - * unexpected constant types, like this: - * - *
-     * if (cst instanceof Integer) {
-     *     // ...
-     * } else if (cst instanceof Float) {
-     *     // ...
-     * } else if (cst instanceof Long) {
-     *     // ...
-     * } else if (cst instanceof Double) {
-     *     // ...
-     * } else if (cst instanceof String) {
-     *     // ...
-     * } else if (cst instanceof Type) {
-     *     int sort = ((Type) cst).getSort();
-     *     if (sort == Type.OBJECT) {
-     *         // ...
-     *     } else if (sort == Type.ARRAY) {
-     *         // ...
-     *     } else if (sort == Type.METHOD) {
-     *         // ...
-     *     } else {
-     *         // throw an exception
-     *     }
-     * } else if (cst instanceof Handle) {
-     *     // ...
-     * } else {
-     *     // throw an exception
-     * }
-     * 
- * - * @param cst - * the constant to be loaded on the stack. This parameter must be - * a non null {@link Integer}, a {@link Float}, a {@link Long}, a - * {@link Double}, a {@link String}, a {@link Type} of OBJECT or - * ARRAY sort for .class constants, for classes whose - * version is 49.0, a {@link Type} of METHOD sort or a - * {@link Handle} for MethodType and MethodHandle constants, for - * classes whose version is 51.0. - */ - public void visitLdcInsn(Object cst) { - if (mv != null) { - mv.visitLdcInsn(cst); - } - } - - /** - * Visits an IINC instruction. - * - * @param var - * index of the local variable to be incremented. - * @param increment - * amount to increment the local variable by. - */ - public void visitIincInsn(int var, int increment) { - if (mv != null) { - mv.visitIincInsn(var, increment); - } - } - - /** - * Visits a TABLESWITCH instruction. - * - * @param min - * the minimum key value. - * @param max - * the maximum key value. - * @param dflt - * beginning of the default handler block. - * @param labels - * beginnings of the handler blocks. labels[i] is the - * beginning of the handler block for the min + i key. - */ - public void visitTableSwitchInsn(int min, int max, Label dflt, - Label... labels) { - if (mv != null) { - mv.visitTableSwitchInsn(min, max, dflt, labels); - } - } - - /** - * Visits a LOOKUPSWITCH instruction. - * - * @param dflt - * beginning of the default handler block. - * @param keys - * the values of the keys. - * @param labels - * beginnings of the handler blocks. labels[i] is the - * beginning of the handler block for the keys[i] key. - */ - public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { - if (mv != null) { - mv.visitLookupSwitchInsn(dflt, keys, labels); - } - } - - /** - * Visits a MULTIANEWARRAY instruction. - * - * @param desc - * an array type descriptor (see {@link Type Type}). - * @param dims - * number of dimensions of the array to allocate. - */ - public void visitMultiANewArrayInsn(String desc, int dims) { - if (mv != null) { - mv.visitMultiANewArrayInsn(desc, dims); - } - } - - // ------------------------------------------------------------------------- - // Exceptions table entries, debug information, max stack and max locals - // ------------------------------------------------------------------------- - - /** - * Visits a try catch block. - * - * @param start - * beginning of the exception handler's scope (inclusive). - * @param end - * end of the exception handler's scope (exclusive). - * @param handler - * beginning of the exception handler's code. - * @param type - * internal name of the type of exceptions handled by the - * handler, or null to catch any exceptions (for - * "finally" blocks). - * @throws IllegalArgumentException - * if one of the labels has already been visited by this visitor - * (by the {@link #visitLabel visitLabel} method). - */ - public void visitTryCatchBlock(Label start, Label end, Label handler, - String type) { - if (mv != null) { - mv.visitTryCatchBlock(start, end, handler, type); - } - } - - /** - * Visits a local variable declaration. - * - * @param name - * the name of a local variable. - * @param desc - * the type descriptor of this local variable. - * @param signature - * the type signature of this local variable. May be - * null if the local variable type does not use generic - * types. - * @param start - * the first instruction corresponding to the scope of this local - * variable (inclusive). - * @param end - * the last instruction corresponding to the scope of this local - * variable (exclusive). - * @param index - * the local variable's index. - * @throws IllegalArgumentException - * if one of the labels has not already been visited by this - * visitor (by the {@link #visitLabel visitLabel} method). - */ - public void visitLocalVariable(String name, String desc, String signature, - Label start, Label end, int index) { - if (mv != null) { - mv.visitLocalVariable(name, desc, signature, start, end, index); - } - } - - /** - * Visits a line number declaration. - * - * @param line - * a line number. This number refers to the source file from - * which the class was compiled. - * @param start - * the first instruction corresponding to this line number. - * @throws IllegalArgumentException - * if start has not already been visited by this - * visitor (by the {@link #visitLabel visitLabel} method). - */ - public void visitLineNumber(int line, Label start) { - if (mv != null) { - mv.visitLineNumber(line, start); - } - } - - /** - * Visits the maximum stack size and the maximum number of local variables - * of the method. - * - * @param maxStack - * maximum stack size of the method. - * @param maxLocals - * maximum number of local variables for the method. - */ - public void visitMaxs(int maxStack, int maxLocals) { - if (mv != null) { - mv.visitMaxs(maxStack, maxLocals); - } - } - - /** - * Visits the end of the method. This method, which is the last one to be - * called, is used to inform the visitor that all the annotations and - * attributes of the method have been visited. - */ - public void visitEnd() { - if (mv != null) { - mv.visitEnd(); - } + private static final String REQUIRES_ASM5 = "This feature requires ASM5"; + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + */ + protected final int api; + + /** The method visitor to which this visitor must delegate method calls. May be null. */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + */ + public MethodVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link + * Opcodes#ASM7_EXPERIMENTAL}. + * @param methodVisitor the method visitor to which this visitor must delegate method calls. May + * be null. + */ + public MethodVisitor(final int api, final MethodVisitor methodVisitor) { + if (api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM7_EXPERIMENTAL) { + throw new IllegalArgumentException(); + } + this.api = api; + this.mv = methodVisitor; + } + + // ----------------------------------------------------------------------------------------------- + // Parameters, annotations and non standard attributes + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a parameter of this method. + * + * @param name parameter name or null if none is provided. + * @param access the parameter's access flags, only ACC_FINAL, ACC_SYNTHETIC + * or/and ACC_MANDATED are allowed (see {@link Opcodes}). + */ + public void visitParameter(final String name, final int access) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + mv.visitParameter(name, access); + } + } + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this annotation interface method, or + * null if this visitor is not interested in visiting this default value. The 'name' + * parameters passed to the methods of this annotation visitor are ignored. Moreover, exacly + * one visit method must be called on this annotation visitor, followed by visitEnd. + */ + public AnnotationVisitor visitAnnotationDefault() { + if (mv != null) { + return mv.visitAnnotationDefault(); + } + return null; + } + + /** + * Visits an annotation of this method. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (mv != null) { + return mv.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the method signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#METHOD_TYPE_PARAMETER}, {@link + * TypeReference#METHOD_TYPE_PARAMETER_BOUND}, {@link TypeReference#METHOD_RETURN}, {@link + * TypeReference#METHOD_RECEIVER}, {@link TypeReference#METHOD_FORMAL_PARAMETER} or {@link + * TypeReference#THROWS}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be null if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits the number of method parameters that can have annotations. By default (i.e. when this + * method is not called), all the method parameters defined by the method descriptor can have + * annotations. + * + * @param parameterCount the number of method parameters than can have annotations. This number + * must be less or equal than the number of parameter types in the method descriptor. It can + * be strictly less when a method has synthetic parameters and when these parameters are + * ignored when computing parameter indices for the purpose of parameter annotations (see + * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). + * @param visible true to define the number of method parameters that can have + * annotations visible at runtime, false to define the number of method parameters + * that can have annotations invisible at runtime. + */ + public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) { + if (mv != null) { + mv.visitAnnotableParameterCount(parameterCount, visible); + } + } + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter the parameter index. This index must be strictly smaller than the number of + * parameters in the method descriptor, and strictly smaller than the parameter count + * specified in {@link #visitAnnotableParameterCount}. Important note: a parameter index i + * is not required to correspond to the i'th parameter descriptor in the method + * descriptor, in particular in case of synthetic parameters (see + * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitParameterAnnotation( + final int parameter, final String descriptor, final boolean visible) { + if (mv != null) { + return mv.visitParameterAnnotation(parameter, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of this method. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (mv != null) { + mv.visitAttribute(attribute); + } + } + + /** Starts the visit of the method's code, if any (i.e. non abstract method). */ + public void visitCode() { + if (mv != null) { + mv.visitCode(); + } + } + + /** + * Visits the current state of the local variables and operand stack elements. This method must(*) + * be called just before any instruction i that follows an unconditional branch + * instruction such as GOTO or THROW, that is the target of a jump instruction, or that starts an + * exception handler block. The visited types must describe the values of the local variables and + * of the operand stack elements just before i is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or equal to {@link + * Opcodes#V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in compressed form (all frames + * must use the same format, i.e. you must not mix expanded and compressed frames within a single + * method): + * + *
    + *
  • In expanded form, all frames must have the F_NEW type. + *
  • In compressed form, frames are basically "deltas" from the state of the previous frame: + *
      + *
    • {@link Opcodes#F_SAME} representing frame with exactly the same locals as the + * previous frame and with the empty stack. + *
    • {@link Opcodes#F_SAME1} representing frame with exactly the same locals as the + * previous frame and with single value on the stack ( nStack is 1 and + * stack[0] contains value for the type of the stack item). + *
    • {@link Opcodes#F_APPEND} representing frame with current locals are the same as the + * locals in the previous frame, except that additional locals are defined ( + * nLocal is 1, 2 or 3 and local elements contains values + * representing added types). + *
    • {@link Opcodes#F_CHOP} representing frame with current locals are the same as the + * locals in the previous frame, except that the last 1-3 locals are absent and with + * the empty stack (nLocals is 1, 2 or 3). + *
    • {@link Opcodes#F_FULL} representing complete frame data. + *
    + *
+ * + *
+ * In both cases the first frame, corresponding to the method's parameters and access flags, is + * implicit and must not be visited. Also, it is illegal to visit two or more frames for the same + * code location (i.e., at least one instruction must be visited between two calls to visitFrame). + * + * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded + * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link + * Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames. + * @param nLocal the number of local variables in the visited frame. + * @param local the local variable types in this frame. This array must not be modified. Primitive + * types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element). + * Reference types are represented by String objects (representing internal names), and + * uninitialized types by Label objects (this label designates the NEW instruction that + * created this uninitialized value). + * @param nStack the number of operand stack elements in the visited frame. + * @param stack the operand stack types in this frame. This array must not be modified. Its + * content has the same format as the "local" array. + * @throws IllegalStateException if a frame is visited just after another one, without any + * instruction between the two (unless this frame is a Opcodes#F_SAME frame, in which case it + * is silently ignored). + */ + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) { + if (mv != null) { + mv.visitFrame(type, nLocal, local, nStack, stack); + } + } + + // ----------------------------------------------------------------------------------------------- + // Normal instructions + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode the opcode of the instruction to be visited. This opcode is either NOP, + * ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, + * LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, + * FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, + * AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, + * SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, + * FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, + * LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, + * D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, + * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT. + */ + public void visitInsn(final int opcode) { + if (mv != null) { + mv.visitInsn(opcode); + } + } + + /** + * Visits an instruction with a single int operand. + * + * @param opcode the opcode of the instruction to be visited. This opcode is either BIPUSH, SIPUSH + * or NEWARRAY. + * @param operand the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between Byte.MIN_VALUE and Byte.MAX_VALUE. + *
+ * When opcode is SIPUSH, operand value should be between Short.MIN_VALUE and Short.MAX_VALUE. + *
+ * When opcode is NEWARRAY, operand value should be one of {@link Opcodes#T_BOOLEAN}, {@link + * Opcodes#T_CHAR}, {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, {@link Opcodes#T_BYTE}, + * {@link Opcodes#T_SHORT}, {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + public void visitIntInsn(final int opcode, final int operand) { + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + /** + * Visits a local variable instruction. A local variable instruction is an instruction that loads + * or stores the value of a local variable. + * + * @param opcode the opcode of the local variable instruction to be visited. This opcode is either + * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var the operand of the instruction to be visited. This operand is the index of a local + * variable. + */ + public void visitVarInsn(final int opcode, final int var) { + if (mv != null) { + mv.visitVarInsn(opcode, var); + } + } + + /** + * Visits a type instruction. A type instruction is an instruction that takes the internal name of + * a class as parameter. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW, + * ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type the operand of the instruction to be visited. This operand must be the internal + * name of an object or array class (see {@link Type#getInternalName()}). + */ + public void visitTypeInsn(final int opcode, final String type) { + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + /** + * Visits a field instruction. A field instruction is an instruction that loads or stores the + * value of a field of an object. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}). + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + */ + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, descriptor); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @deprecated + */ + @Deprecated + public void visitMethodInsn( + final int opcode, final String owner, final String name, final String descriptor) { + if (api >= Opcodes.ASM5) { + boolean isInterface = opcode == Opcodes.INVOKEINTERFACE; + visitMethodInsn(opcode, owner, name, descriptor, isInterface); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, descriptor); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param isInterface if the method's owner class is an interface. + */ + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + if (api < Opcodes.ASM5) { + if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new IllegalArgumentException("INVOKESPECIAL/STATIC on interfaces requires ASM5"); + } + visitMethodInsn(opcode, owner, name, descriptor); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + + /** + * Visits an invokedynamic instruction. + * + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, {@link + * Type}, {@link Handle} or {@link ConstantDynamic} value. This method is allowed to modify + * the content of the array so a caller should expect that this array may change. + */ + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + } + + /** + * Visits a jump instruction. A jump instruction is an instruction that may jump to another + * instruction. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either IFEQ, + * IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, + * IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label the operand of the instruction to be visited. This operand is a label that + * designates the instruction to which the jump instruction may jump. + */ + public void visitJumpInsn(final int opcode, final Label label) { + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + /** + * Visits a label. A label designates the instruction that will be visited just after it. + * + * @param label a {@link Label} object. + */ + public void visitLabel(final Label label) { + if (mv != null) { + mv.visitLabel(label); + } + } + + // ----------------------------------------------------------------------------------------------- + // Special instructions + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. Note that new constant types may be added in future versions of the + * Java Virtual Machine. To easily detect new constant types, implementations of this method + * should check for unexpected constant types, like this: + * + *
+   * if (cst instanceof Integer) {
+   *     // ...
+   * } else if (cst instanceof Float) {
+   *     // ...
+   * } else if (cst instanceof Long) {
+   *     // ...
+   * } else if (cst instanceof Double) {
+   *     // ...
+   * } else if (cst instanceof String) {
+   *     // ...
+   * } else if (cst instanceof Type) {
+   *     int sort = ((Type) cst).getSort();
+   *     if (sort == Type.OBJECT) {
+   *         // ...
+   *     } else if (sort == Type.ARRAY) {
+   *         // ...
+   *     } else if (sort == Type.METHOD) {
+   *         // ...
+   *     } else {
+   *         // throw an exception
+   *     }
+   * } else if (cst instanceof Handle) {
+   *     // ...
+   * } else if (cst instanceof Condy) {
+   *     // ...
+   * } else {
+   *     // throw an exception
+   * }
+   * 
+ * + * @param value the constant to be loaded on the stack. This parameter must be a non null {@link + * Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link + * Type} of OBJECT or ARRAY sort for .class constants, for classes whose version is + * 49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle + * constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant + * dynamic for classes whose version is 55. + */ + public void visitLdcInsn(final Object value) { + if (api < Opcodes.ASM5 + && (value instanceof Handle + || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (api != Opcodes.ASM7_EXPERIMENTAL && value instanceof ConstantDynamic) { + throw new UnsupportedOperationException("This feature requires ASM7"); + } + if (mv != null) { + mv.visitLdcInsn(value); + } + } + + /** + * Visits an IINC instruction. + * + * @param var index of the local variable to be incremented. + * @param increment amount to increment the local variable by. + */ + public void visitIincInsn(final int var, final int increment) { + if (mv != null) { + mv.visitIincInsn(var, increment); + } + } + + /** + * Visits a TABLESWITCH instruction. + * + * @param min the minimum key value. + * @param max the maximum key value. + * @param dflt beginning of the default handler block. + * @param labels beginnings of the handler blocks. labels[i] is the beginning of the + * handler block for the min + i key. + */ + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt beginning of the default handler block. + * @param keys the values of the keys. + * @param labels beginnings of the handler blocks. labels[i] is the beginning of the + * handler block for the keys[i] key. + */ + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param descriptor an array type descriptor (see {@link Type}). + * @param numDimensions the number of dimensions of the array to allocate. + */ + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + if (mv != null) { + mv.visitMultiANewArrayInsn(descriptor, numDimensions); + } + } + + /** + * Visits an annotation on an instruction. This method must be called just after the + * annotated instruction. It can be called several times for the same instruction. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#INSTANCEOF}, {@link TypeReference#NEW}, {@link + * TypeReference#CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE}, {@link + * TypeReference#CAST}, {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link + * TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link + * TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be null if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitInsnAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitInsnAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + // ----------------------------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start the beginning of the exception handler's scope (inclusive). + * @param end the end of the exception handler's scope (exclusive). + * @param handler the beginning of the exception handler's code. + * @param type the internal name of the type of exceptions handled by the handler, or + * null to catch any exceptions (for "finally" blocks). + * @throws IllegalArgumentException if one of the labels has already been visited by this visitor + * (by the {@link #visitLabel} method). + */ + public void visitTryCatchBlock( + final Label start, final Label end, final Label handler, final String type) { + if (mv != null) { + mv.visitTryCatchBlock(start, end, handler, type); + } + } + + /** + * Visits an annotation on an exception handler type. This method must be called after the + * {@link #visitTryCatchBlock} for the annotated exception handler. It can be called several times + * for the same exception handler. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#EXCEPTION_PARAMETER}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be null if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTryCatchAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a local variable declaration. + * + * @param name the name of a local variable. + * @param descriptor the type descriptor of this local variable. + * @param signature the type signature of this local variable. May be null if the local + * variable type does not use generic types. + * @param start the first instruction corresponding to the scope of this local variable + * (inclusive). + * @param end the last instruction corresponding to the scope of this local variable (exclusive). + * @param index the local variable's index. + * @throws IllegalArgumentException if one of the labels has not already been visited by this + * visitor (by the {@link #visitLabel} method). + */ + public void visitLocalVariable( + final String name, + final String descriptor, + final String signature, + final Label start, + final Label end, + final int index) { + if (mv != null) { + mv.visitLocalVariable(name, descriptor, signature, start, end, index); + } + } + + /** + * Visits an annotation on a local variable type. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE}. See {@link + * TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be null if the annotation targets + * 'typeRef' as a whole. + * @param start the fist instructions corresponding to the continuous ranges that make the scope + * of this local variable (inclusive). + * @param end the last instructions corresponding to the continuous ranges that make the scope of + * this local variable (exclusive). This array must have the same size as the 'start' array. + * @param index the local variable's index in each range. This array must have the same size as + * the 'start' array. + * @param descriptor the class descriptor of the annotation class. + * @param visible true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitLocalVariableAnnotation( + final int typeRef, + final TypePath typePath, + final Label[] start, + final Label[] end, + final int[] index, + final String descriptor, + final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitLocalVariableAnnotation( + typeRef, typePath, start, end, index, descriptor, visible); + } + return null; + } + + /** + * Visits a line number declaration. + * + * @param line a line number. This number refers to the source file from which the class was + * compiled. + * @param start the first instruction corresponding to this line number. + * @throws IllegalArgumentException if start has not already been visited by this visitor + * (by the {@link #visitLabel} method). + */ + public void visitLineNumber(final int line, final Label start) { + if (mv != null) { + mv.visitLineNumber(line, start); + } + } + + /** + * Visits the maximum stack size and the maximum number of local variables of the method. + * + * @param maxStack maximum stack size of the method. + * @param maxLocals maximum number of local variables for the method. + */ + public void visitMaxs(final int maxStack, final int maxLocals) { + if (mv != null) { + mv.visitMaxs(maxStack, maxLocals); + } + } + + /** + * Visits the end of the method. This method, which is the last one to be called, is used to + * inform the visitor that all the annotations and attributes of the method have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); } + } } diff --git a/src/jvm/clojure/asm/MethodWriter.java b/src/jvm/clojure/asm/MethodWriter.java index 91973188c8..2ced84a191 100644 --- a/src/jvm/clojure/asm/MethodWriter.java +++ b/src/jvm/clojure/asm/MethodWriter.java @@ -1,2685 +1,2413 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * A {@link MethodVisitor} that generates methods in bytecode form. Each visit - * method of this class appends the bytecode corresponding to the visited - * instruction to a byte vector, in the order these methods are called. + * A {@link MethodVisitor} that generates a corresponding 'method_info' structure, as defined in the + * Java Virtual Machine Specification (JVMS). * + * @see JVMS + * 4.6 * @author Eric Bruneton * @author Eugene Kuleshov */ -class MethodWriter extends MethodVisitor { - - /** - * Pseudo access flag used to denote constructors. - */ - static final int ACC_CONSTRUCTOR = 0x80000; - - /** - * Frame has exactly the same locals as the previous stack map frame and - * number of stack items is zero. - */ - static final int SAME_FRAME = 0; // to 63 (0-3f) - - /** - * Frame has exactly the same locals as the previous stack map frame and - * number of stack items is 1 - */ - static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f) - - /** - * Reserved for future use - */ - static final int RESERVED = 128; - - /** - * Frame has exactly the same locals as the previous stack map frame and - * number of stack items is 1. Offset is bigger then 63; - */ - static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7 - - /** - * Frame where current locals are the same as the locals in the previous - * frame, except that the k last locals are absent. The value of k is given - * by the formula 251-frame_type. - */ - static final int CHOP_FRAME = 248; // to 250 (f8-fA) - - /** - * Frame has exactly the same locals as the previous stack map frame and - * number of stack items is zero. Offset is bigger then 63; - */ - static final int SAME_FRAME_EXTENDED = 251; // fb - - /** - * Frame where current locals are the same as the locals in the previous - * frame, except that k additional locals are defined. The value of k is - * given by the formula frame_type-251. - */ - static final int APPEND_FRAME = 252; // to 254 // fc-fe - - /** - * Full frame - */ - static final int FULL_FRAME = 255; // ff - - /** - * Indicates that the stack map frames must be recomputed from scratch. In - * this case the maximum stack size and number of local variables is also - * recomputed from scratch. - * - * @see #compute - */ - private static final int FRAMES = 0; - - /** - * Indicates that the maximum stack size and number of local variables must - * be automatically computed. - * - * @see #compute - */ - private static final int MAXS = 1; - - /** - * Indicates that nothing must be automatically computed. - * - * @see #compute - */ - private static final int NOTHING = 2; - - /** - * The class writer to which this method must be added. - */ - final ClassWriter cw; - - /** - * Access flags of this method. - */ - private int access; - - /** - * The index of the constant pool item that contains the name of this - * method. - */ - private final int name; - - /** - * The index of the constant pool item that contains the descriptor of this - * method. - */ - private final int desc; - - /** - * The descriptor of this method. - */ - private final String descriptor; - - /** - * The signature of this method. - */ - String signature; - - /** - * If not zero, indicates that the code of this method must be copied from - * the ClassReader associated to this writer in cw.cr. More - * precisely, this field gives the index of the first byte to copied from - * cw.cr.b. - */ - int classReaderOffset; - - /** - * If not zero, indicates that the code of this method must be copied from - * the ClassReader associated to this writer in cw.cr. More - * precisely, this field gives the number of bytes to copied from - * cw.cr.b. - */ - int classReaderLength; - - /** - * Number of exceptions that can be thrown by this method. - */ - int exceptionCount; - - /** - * The exceptions that can be thrown by this method. More precisely, this - * array contains the indexes of the constant pool items that contain the - * internal names of these exception classes. - */ - int[] exceptions; - - /** - * The annotation default attribute of this method. May be null. - */ - private ByteVector annd; - - /** - * The runtime visible annotations of this method. May be null. - */ - private AnnotationWriter anns; - - /** - * The runtime invisible annotations of this method. May be null. - */ - private AnnotationWriter ianns; - - /** - * The runtime visible parameter annotations of this method. May be - * null. - */ - private AnnotationWriter[] panns; - - /** - * The runtime invisible parameter annotations of this method. May be - * null. - */ - private AnnotationWriter[] ipanns; - - /** - * The number of synthetic parameters of this method. - */ - private int synthetics; - - /** - * The non standard attributes of the method. - */ - private Attribute attrs; - - /** - * The bytecode of this method. - */ - private ByteVector code = new ByteVector(); - - /** - * Maximum stack size of this method. - */ - private int maxStack; - - /** - * Maximum number of local variables for this method. - */ - private int maxLocals; - - /** - * Number of local variables in the current stack map frame. - */ - private int currentLocals; - - /** - * Number of stack map frames in the StackMapTable attribute. - */ - private int frameCount; - - /** - * The StackMapTable attribute. - */ - private ByteVector stackMap; - - /** - * The offset of the last frame that was written in the StackMapTable - * attribute. - */ - private int previousFrameOffset; - - /** - * The last frame that was written in the StackMapTable attribute. - * - * @see #frame - */ - private int[] previousFrame; - - /** - * The current stack map frame. The first element contains the offset of the - * instruction to which the frame corresponds, the second element is the - * number of locals and the third one is the number of stack elements. The - * local variables start at index 3 and are followed by the operand stack - * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = - * nStack, frame[3] = nLocal. All types are encoded as integers, with the - * same format as the one used in {@link Label}, but limited to BASE types. - */ - private int[] frame; - - /** - * Number of elements in the exception handler list. - */ - private int handlerCount; - - /** - * The first element in the exception handler list. - */ - private Handler firstHandler; - - /** - * The last element in the exception handler list. - */ - private Handler lastHandler; - - /** - * Number of entries in the LocalVariableTable attribute. - */ - private int localVarCount; - - /** - * The LocalVariableTable attribute. - */ - private ByteVector localVar; - - /** - * Number of entries in the LocalVariableTypeTable attribute. - */ - private int localVarTypeCount; - - /** - * The LocalVariableTypeTable attribute. - */ - private ByteVector localVarType; - - /** - * Number of entries in the LineNumberTable attribute. - */ - private int lineNumberCount; - - /** - * The LineNumberTable attribute. - */ - private ByteVector lineNumber; - - /** - * The non standard attributes of the method's code. - */ - private Attribute cattrs; - - /** - * Indicates if some jump instructions are too small and need to be resized. - */ - private boolean resize; - - /** - * The number of subroutines in this method. - */ - private int subroutines; - - // ------------------------------------------------------------------------ - - /* - * Fields for the control flow graph analysis algorithm (used to compute the - * maximum stack size). A control flow graph contains one node per "basic - * block", and one edge per "jump" from one basic block to another. Each - * node (i.e., each basic block) is represented by the Label object that - * corresponds to the first instruction of this basic block. Each node also - * stores the list of its successors in the graph, as a linked list of Edge - * objects. - */ - - /** - * Indicates what must be automatically computed. - * - * @see #FRAMES - * @see #MAXS - * @see #NOTHING - */ - private final int compute; - - /** - * A list of labels. This list is the list of basic blocks in the method, - * i.e. a list of Label objects linked to each other by their - * {@link Label#successor} field, in the order they are visited by - * {@link MethodVisitor#visitLabel}, and starting with the first basic - * block. - */ - private Label labels; - - /** - * The previous basic block. - */ - private Label previousBlock; - - /** - * The current basic block. - */ - private Label currentBlock; - - /** - * The (relative) stack size after the last visited instruction. This size - * is relative to the beginning of the current basic block, i.e., the true - * stack size after the last visited instruction is equal to the - * {@link Label#inputStackTop beginStackSize} of the current basic block - * plus stackSize. - */ - private int stackSize; - - /** - * The (relative) maximum stack size after the last visited instruction. - * This size is relative to the beginning of the current basic block, i.e., - * the true maximum stack size after the last visited instruction is equal - * to the {@link Label#inputStackTop beginStackSize} of the current basic - * block plus stackSize. - */ - private int maxStackSize; - - // ------------------------------------------------------------------------ - // Constructor - // ------------------------------------------------------------------------ - - /** - * Constructs a new {@link MethodWriter}. - * - * @param cw - * the class writer in which the method must be added. - * @param access - * the method's access flags (see {@link Opcodes}). - * @param name - * the method's name. - * @param desc - * the method's descriptor (see {@link Type}). - * @param signature - * the method's signature. May be null. - * @param exceptions - * the internal names of the method's exceptions. May be - * null. - * @param computeMaxs - * true if the maximum stack size and number of local - * variables must be automatically computed. - * @param computeFrames - * true if the stack map tables must be recomputed from - * scratch. - */ - MethodWriter(final ClassWriter cw, final int access, final String name, - final String desc, final String signature, - final String[] exceptions, final boolean computeMaxs, - final boolean computeFrames) { - super(Opcodes.ASM4); - if (cw.firstMethod == null) { - cw.firstMethod = this; - } else { - cw.lastMethod.mv = this; - } - cw.lastMethod = this; - this.cw = cw; - this.access = access; - if ("".equals(name)) { - this.access |= ACC_CONSTRUCTOR; - } - this.name = cw.newUTF8(name); - this.desc = cw.newUTF8(desc); - this.descriptor = desc; - if (ClassReader.SIGNATURES) { - this.signature = signature; - } - if (exceptions != null && exceptions.length > 0) { - exceptionCount = exceptions.length; - this.exceptions = new int[exceptionCount]; - for (int i = 0; i < exceptionCount; ++i) { - this.exceptions[i] = cw.newClass(exceptions[i]); - } - } - this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); - if (computeMaxs || computeFrames) { - // updates maxLocals - int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; - if ((access & Opcodes.ACC_STATIC) != 0) { - --size; - } - maxLocals = size; - currentLocals = size; - // creates and visits the label for the first basic block - labels = new Label(); - labels.status |= Label.PUSHED; - visitLabel(labels); - } +final class MethodWriter extends MethodVisitor { + + /** Indicates that nothing must be computed. */ + static final int COMPUTE_NOTHING = 0; + + /** + * Indicates that the maximum stack size and the maximum number of local variables must be + * computed, from scratch. + */ + static final int COMPUTE_MAX_STACK_AND_LOCAL = 1; + + /** + * Indicates that the maximum stack size and the maximum number of local variables must be + * computed, from the existing stack map frames. This can be done more efficiently than with the + * control flow graph algorithm used for {@link #COMPUTE_MAX_STACK_AND_LOCAL}, by using a linear + * scan of the bytecode instructions. + */ + static final int COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES = 2; + + /** + * Indicates that the stack map frames of type F_INSERT must be computed. The other frames are not + * computed. They should all be of type F_NEW and should be sufficient to compute the content of + * the F_INSERT frames, together with the bytecode instructions between a F_NEW and a F_INSERT + * frame - and without any knowledge of the type hierarchy (by definition of F_INSERT). + */ + static final int COMPUTE_INSERTED_FRAMES = 3; + + /** + * Indicates that all the stack map frames must be computed. In this case the maximum stack size + * and the maximum number of local variables is also computed. + */ + static final int COMPUTE_ALL_FRAMES = 4; + + /** Indicates that {@link #STACK_SIZE_DELTA} is not applicable (not constant or never used). */ + private static final int NA = 0; + + /** + * The stack size variation corresponding to each JVM opcode. The stack size variation for opcode + * 'o' is given by the array element at index 'o'. + * + * @see JVMS 6 + */ + private static final int[] STACK_SIZE_DELTA = { + 0, // nop = 0 (0x0) + 1, // aconst_null = 1 (0x1) + 1, // iconst_m1 = 2 (0x2) + 1, // iconst_0 = 3 (0x3) + 1, // iconst_1 = 4 (0x4) + 1, // iconst_2 = 5 (0x5) + 1, // iconst_3 = 6 (0x6) + 1, // iconst_4 = 7 (0x7) + 1, // iconst_5 = 8 (0x8) + 2, // lconst_0 = 9 (0x9) + 2, // lconst_1 = 10 (0xa) + 1, // fconst_0 = 11 (0xb) + 1, // fconst_1 = 12 (0xc) + 1, // fconst_2 = 13 (0xd) + 2, // dconst_0 = 14 (0xe) + 2, // dconst_1 = 15 (0xf) + 1, // bipush = 16 (0x10) + 1, // sipush = 17 (0x11) + 1, // ldc = 18 (0x12) + NA, // ldc_w = 19 (0x13) + NA, // ldc2_w = 20 (0x14) + 1, // iload = 21 (0x15) + 2, // lload = 22 (0x16) + 1, // fload = 23 (0x17) + 2, // dload = 24 (0x18) + 1, // aload = 25 (0x19) + NA, // iload_0 = 26 (0x1a) + NA, // iload_1 = 27 (0x1b) + NA, // iload_2 = 28 (0x1c) + NA, // iload_3 = 29 (0x1d) + NA, // lload_0 = 30 (0x1e) + NA, // lload_1 = 31 (0x1f) + NA, // lload_2 = 32 (0x20) + NA, // lload_3 = 33 (0x21) + NA, // fload_0 = 34 (0x22) + NA, // fload_1 = 35 (0x23) + NA, // fload_2 = 36 (0x24) + NA, // fload_3 = 37 (0x25) + NA, // dload_0 = 38 (0x26) + NA, // dload_1 = 39 (0x27) + NA, // dload_2 = 40 (0x28) + NA, // dload_3 = 41 (0x29) + NA, // aload_0 = 42 (0x2a) + NA, // aload_1 = 43 (0x2b) + NA, // aload_2 = 44 (0x2c) + NA, // aload_3 = 45 (0x2d) + -1, // iaload = 46 (0x2e) + 0, // laload = 47 (0x2f) + -1, // faload = 48 (0x30) + 0, // daload = 49 (0x31) + -1, // aaload = 50 (0x32) + -1, // baload = 51 (0x33) + -1, // caload = 52 (0x34) + -1, // saload = 53 (0x35) + -1, // istore = 54 (0x36) + -2, // lstore = 55 (0x37) + -1, // fstore = 56 (0x38) + -2, // dstore = 57 (0x39) + -1, // astore = 58 (0x3a) + NA, // istore_0 = 59 (0x3b) + NA, // istore_1 = 60 (0x3c) + NA, // istore_2 = 61 (0x3d) + NA, // istore_3 = 62 (0x3e) + NA, // lstore_0 = 63 (0x3f) + NA, // lstore_1 = 64 (0x40) + NA, // lstore_2 = 65 (0x41) + NA, // lstore_3 = 66 (0x42) + NA, // fstore_0 = 67 (0x43) + NA, // fstore_1 = 68 (0x44) + NA, // fstore_2 = 69 (0x45) + NA, // fstore_3 = 70 (0x46) + NA, // dstore_0 = 71 (0x47) + NA, // dstore_1 = 72 (0x48) + NA, // dstore_2 = 73 (0x49) + NA, // dstore_3 = 74 (0x4a) + NA, // astore_0 = 75 (0x4b) + NA, // astore_1 = 76 (0x4c) + NA, // astore_2 = 77 (0x4d) + NA, // astore_3 = 78 (0x4e) + -3, // iastore = 79 (0x4f) + -4, // lastore = 80 (0x50) + -3, // fastore = 81 (0x51) + -4, // dastore = 82 (0x52) + -3, // aastore = 83 (0x53) + -3, // bastore = 84 (0x54) + -3, // castore = 85 (0x55) + -3, // sastore = 86 (0x56) + -1, // pop = 87 (0x57) + -2, // pop2 = 88 (0x58) + 1, // dup = 89 (0x59) + 1, // dup_x1 = 90 (0x5a) + 1, // dup_x2 = 91 (0x5b) + 2, // dup2 = 92 (0x5c) + 2, // dup2_x1 = 93 (0x5d) + 2, // dup2_x2 = 94 (0x5e) + 0, // swap = 95 (0x5f) + -1, // iadd = 96 (0x60) + -2, // ladd = 97 (0x61) + -1, // fadd = 98 (0x62) + -2, // dadd = 99 (0x63) + -1, // isub = 100 (0x64) + -2, // lsub = 101 (0x65) + -1, // fsub = 102 (0x66) + -2, // dsub = 103 (0x67) + -1, // imul = 104 (0x68) + -2, // lmul = 105 (0x69) + -1, // fmul = 106 (0x6a) + -2, // dmul = 107 (0x6b) + -1, // idiv = 108 (0x6c) + -2, // ldiv = 109 (0x6d) + -1, // fdiv = 110 (0x6e) + -2, // ddiv = 111 (0x6f) + -1, // irem = 112 (0x70) + -2, // lrem = 113 (0x71) + -1, // frem = 114 (0x72) + -2, // drem = 115 (0x73) + 0, // ineg = 116 (0x74) + 0, // lneg = 117 (0x75) + 0, // fneg = 118 (0x76) + 0, // dneg = 119 (0x77) + -1, // ishl = 120 (0x78) + -1, // lshl = 121 (0x79) + -1, // ishr = 122 (0x7a) + -1, // lshr = 123 (0x7b) + -1, // iushr = 124 (0x7c) + -1, // lushr = 125 (0x7d) + -1, // iand = 126 (0x7e) + -2, // land = 127 (0x7f) + -1, // ior = 128 (0x80) + -2, // lor = 129 (0x81) + -1, // ixor = 130 (0x82) + -2, // lxor = 131 (0x83) + 0, // iinc = 132 (0x84) + 1, // i2l = 133 (0x85) + 0, // i2f = 134 (0x86) + 1, // i2d = 135 (0x87) + -1, // l2i = 136 (0x88) + -1, // l2f = 137 (0x89) + 0, // l2d = 138 (0x8a) + 0, // f2i = 139 (0x8b) + 1, // f2l = 140 (0x8c) + 1, // f2d = 141 (0x8d) + -1, // d2i = 142 (0x8e) + 0, // d2l = 143 (0x8f) + -1, // d2f = 144 (0x90) + 0, // i2b = 145 (0x91) + 0, // i2c = 146 (0x92) + 0, // i2s = 147 (0x93) + -3, // lcmp = 148 (0x94) + -1, // fcmpl = 149 (0x95) + -1, // fcmpg = 150 (0x96) + -3, // dcmpl = 151 (0x97) + -3, // dcmpg = 152 (0x98) + -1, // ifeq = 153 (0x99) + -1, // ifne = 154 (0x9a) + -1, // iflt = 155 (0x9b) + -1, // ifge = 156 (0x9c) + -1, // ifgt = 157 (0x9d) + -1, // ifle = 158 (0x9e) + -2, // if_icmpeq = 159 (0x9f) + -2, // if_icmpne = 160 (0xa0) + -2, // if_icmplt = 161 (0xa1) + -2, // if_icmpge = 162 (0xa2) + -2, // if_icmpgt = 163 (0xa3) + -2, // if_icmple = 164 (0xa4) + -2, // if_acmpeq = 165 (0xa5) + -2, // if_acmpne = 166 (0xa6) + 0, // goto = 167 (0xa7) + 1, // jsr = 168 (0xa8) + 0, // ret = 169 (0xa9) + -1, // tableswitch = 170 (0xaa) + -1, // lookupswitch = 171 (0xab) + -1, // ireturn = 172 (0xac) + -2, // lreturn = 173 (0xad) + -1, // freturn = 174 (0xae) + -2, // dreturn = 175 (0xaf) + -1, // areturn = 176 (0xb0) + 0, // return = 177 (0xb1) + NA, // getstatic = 178 (0xb2) + NA, // putstatic = 179 (0xb3) + NA, // getfield = 180 (0xb4) + NA, // putfield = 181 (0xb5) + NA, // invokevirtual = 182 (0xb6) + NA, // invokespecial = 183 (0xb7) + NA, // invokestatic = 184 (0xb8) + NA, // invokeinterface = 185 (0xb9) + NA, // invokedynamic = 186 (0xba) + 1, // new = 187 (0xbb) + 0, // newarray = 188 (0xbc) + 0, // anewarray = 189 (0xbd) + 0, // arraylength = 190 (0xbe) + NA, // athrow = 191 (0xbf) + 0, // checkcast = 192 (0xc0) + 0, // instanceof = 193 (0xc1) + -1, // monitorenter = 194 (0xc2) + -1, // monitorexit = 195 (0xc3) + NA, // wide = 196 (0xc4) + NA, // multianewarray = 197 (0xc5) + -1, // ifnull = 198 (0xc6) + -1, // ifnonnull = 199 (0xc7) + NA, // goto_w = 200 (0xc8) + NA // jsr_w = 201 (0xc9) + }; + + /** Where the constants used in this MethodWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the method_info structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The access_flags field of the method_info JVMS structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the + * ClassFile structure. + */ + private final int accessFlags; + + /** The name_index field of the method_info JVMS structure. */ + private final int nameIndex; + + /** The descriptor_index field of the method_info JVMS structure. */ + private final int descriptorIndex; + + /** The descriptor of this method. */ + private final String descriptor; + + // Code attribute fields and sub attributes: + + /** The max_stack field of the Code attribute. */ + private int maxStack; + + /** The max_locals field of the Code attribute. */ + private int maxLocals; + + /** The 'code' field of the Code attribute. */ + private final ByteVector code = new ByteVector(); + + /** + * The first element in the exception handler list (used to generate the exception_table of the + * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May + * be null. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list (used to generate the exception_table of the + * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May + * be null. + */ + private Handler lastHandler; + + /** The line_number_table_length field of the LineNumberTable code attribute. */ + private int lineNumberTableLength; + + /** The line_number_table array of the LineNumberTable code attribute, or null. */ + private ByteVector lineNumberTable; + + /** The local_variable_table_length field of the LocalVariableTable code attribute. */ + private int localVariableTableLength; + + /** The local_variable_table array of the LocalVariableTable code attribute, or null. */ + private ByteVector localVariableTable; + + /** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */ + private int localVariableTypeTableLength; + + /** + * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or + * null. + */ + private ByteVector localVariableTypeTable; + + /** The number_of_entries field of the StackMapTable code attribute. */ + private int stackMapTableNumberOfEntries; + + /** The 'entries' array of the StackMapTable code attribute. */ + private ByteVector stackMapTableEntries; + + /** + * The last runtime visible type annotation of the Code attribute. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of the Code attribute. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of the Code attribute. The next ones can be accessed with the + * {@link Attribute#nextAttribute} field. May be null. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstCodeAttribute; + + // Other method_info attributes: + + /** The number_of_exceptions field of the Exceptions attribute. */ + private final int numberOfExceptions; + + /** The exception_index_table array of the Exceptions attribute, or null. */ + private final int[] exceptionIndexTable; + + /** The signature_index field of the Signature attribute. */ + private final int signatureIndex; + + /** + * The last runtime visible annotation of this method. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this method. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** The number of method parameters that can have runtime visible annotations, or 0. */ + private int visibleAnnotableParameterCount; + + /** + * The runtime visible parameter annotations of this method. Each array element contains the last + * annotation of a parameter (which can be null - the previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field). May be null. + */ + private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations; + + /** The number of method parameters that can have runtime visible annotations, or 0. */ + private int invisibleAnnotableParameterCount; + + /** + * The runtime invisible parameter annotations of this method. Each array element contains the + * last annotation of a parameter (which can be null - the previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field). May be null. + */ + private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations; + + /** + * The last runtime visible type annotation of this method. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this method. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be null. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** The default_value field of the AnnotationDefault attribute, or null. */ + private ByteVector defaultValue; + + /** The parameters_count field of the MethodParameters attribute. */ + private int parametersCount; + + /** The 'parameters' array of the MethodParameters attribute, or null. */ + private ByteVector parameters; + + /** + * The first non standard attribute of this method. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be null. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + // ----------------------------------------------------------------------------------------------- + // Fields used to compute the maximum stack size and number of locals, and the stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link + * #COMPUTE_INSERTED_FRAMES}, {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}. + */ + private final int compute; + + /** + * The first basic block of the method. The next ones (in bytecode offset order) can be accessed + * with the {@link Label#nextBasicBlock} field. + */ + private Label firstBasicBlock; + + /** + * The last basic block of the method (in bytecode offset order). This field is updated each time + * a basic block is encountered, and is used to append it at the end of the basic block list. + */ + private Label lastBasicBlock; + + /** + * The current basic block, i.e. the basic block of the last visited instruction. When {@link + * #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this + * field is null for unreachable code. When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays + * unchanged throughout the whole method (i.e. the whole code is seen as a single basic block; + * indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame - + * and the maximum stack size as well - without using any control flow graph). + */ + private Label currentBasicBlock; + + /** + * The relative stack size after the last visited instruction. This size is relative to the + * beginning of {@link #currentBasicBlock}, i.e. the true stack size after the last visited + * instruction is equal to the {@link Label#inputStackSize} of the current basic block plus {@link + * #relativeStackSize}. When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of + * the method, so this relative size is also equal to the absolute stack size after the last + * visited instruction. + */ + private int relativeStackSize; + + /** + * The maximum relative stack size after the last visited instruction. This size is relative to + * the beginning of {@link #currentBasicBlock}, i.e. the true maximum stack size after the last + * visited instruction is equal to the {@link Label#inputStackSize} of the current basic block + * plus {@link #maxRelativeStackSize}.When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of + * the method, so this relative size is also equal to the absolute maximum stack size after the + * last visited instruction. + */ + private int maxRelativeStackSize; + + /** The number of local variables in the last visited stack map frame. */ + private int currentLocals; + + /** The bytecode offset of the last frame that was written in {@link #stackMapTableEntries}. */ + private int previousFrameOffset; + + /** + * The last frame that was written in {@link #stackMapTableEntries}. This field has the same + * format as {@link #currentFrame}. + */ + private int[] previousFrame; + + /** + * The current stack map frame. The first element contains the bytecode offset of the instruction + * to which the frame corresponds, the second element is the number of locals and the third one is + * the number of stack elements. The local variables start at index 3 and are followed by the + * operand stack elements. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = nStack, + * frame[3] = nLocal. Local variables and operand stack entries contain abstract types, as defined + * in {@link Frame}, but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND} + * or {@link Frame#UNINITIALIZED_KIND} abstract types. Long and double types use only one array + * entry. + */ + private int[] currentFrame; + + /** Whether this method contains subroutines. */ + private boolean hasSubroutines; + + // ----------------------------------------------------------------------------------------------- + // Other miscellaneous status fields + // ----------------------------------------------------------------------------------------------- + + /** Whether the bytecode of this method contains ASM specific instructions. */ + private boolean hasAsmInstructions; + + /** + * The start offset of the last visited instruction. Used to set the offset field of type + * annotations of type 'offset_target' (see JVMS + * 4.7.20.1). + */ + private int lastBytecodeOffset; + + /** + * The offset in bytes in {@link SymbolTable#getSource} from which the method_info for this method + * (excluding its first 6 bytes) must be copied, or 0. + */ + private int sourceOffset; + + /** + * The length in bytes in {@link SymbolTable#getSource} which must be copied to get the + * method_info for this method (excluding its first 6 bytes for access_flags, name_index and + * descriptor_index). + */ + private int sourceLength; + + // ----------------------------------------------------------------------------------------------- + // Constructor and accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link MethodWriter}. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param access the method's access flags (see {@link Opcodes}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be null. + * @param exceptions the internal names of the method's exceptions. May be null. + * @param compute indicates what must be computed (see #compute). + */ + MethodWriter( + final SymbolTable symbolTable, + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions, + final int compute) { + super(Opcodes.ASM6); + this.symbolTable = symbolTable; + this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + this.descriptor = descriptor; + this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature); + if (exceptions != null && exceptions.length > 0) { + numberOfExceptions = exceptions.length; + this.exceptionIndexTable = new int[numberOfExceptions]; + for (int i = 0; i < numberOfExceptions; ++i) { + this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index; + } + } else { + numberOfExceptions = 0; + this.exceptionIndexTable = null; } + this.compute = compute; + if (compute != COMPUTE_NOTHING) { + // Update maxLocals and currentLocals. + int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --argumentsSize; + } + maxLocals = argumentsSize; + currentLocals = argumentsSize; + // Create and visit the label for the first basic block. + firstBasicBlock = new Label(); + visitLabel(firstBasicBlock); + } + } - // ------------------------------------------------------------------------ - // Implementation of the MethodVisitor abstract class - // ------------------------------------------------------------------------ + boolean hasFrames() { + return stackMapTableNumberOfEntries > 0; + } - @Override - public AnnotationVisitor visitAnnotationDefault() { - if (!ClassReader.ANNOTATIONS) { - return null; - } - annd = new ByteVector(); - return new AnnotationWriter(cw, false, annd, null, 0); - } + boolean hasAsmInstructions() { + return hasAsmInstructions; + } - @Override - public AnnotationVisitor visitAnnotation(final String desc, - final boolean visible) { - if (!ClassReader.ANNOTATIONS) { - return null; - } - ByteVector bv = new ByteVector(); - // write type, and reserve space for values count - bv.putShort(cw.newUTF8(desc)).putShort(0); - AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); - if (visible) { - aw.next = anns; - anns = aw; - } else { - aw.next = ianns; - ianns = aw; - } - return aw; - } + // ----------------------------------------------------------------------------------------------- + // Implementation of the MethodVisitor abstract class + // ----------------------------------------------------------------------------------------------- - @Override - public AnnotationVisitor visitParameterAnnotation(final int parameter, - final String desc, final boolean visible) { - if (!ClassReader.ANNOTATIONS) { - return null; - } - ByteVector bv = new ByteVector(); - if ("Ljava/lang/Synthetic;".equals(desc)) { - // workaround for a bug in javac with synthetic parameters - // see ClassReader.readParameterAnnotations - synthetics = Math.max(synthetics, parameter + 1); - return new AnnotationWriter(cw, false, bv, null, 0); - } - // write type, and reserve space for values count - bv.putShort(cw.newUTF8(desc)).putShort(0); - AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); - if (visible) { - if (panns == null) { - panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; - } - aw.next = panns[parameter]; - panns[parameter] = aw; - } else { - if (ipanns == null) { - ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; - } - aw.next = ipanns[parameter]; - ipanns[parameter] = aw; - } - return aw; + @Override + public void visitParameter(final String name, final int access) { + if (parameters == null) { + parameters = new ByteVector(); } - - @Override - public void visitAttribute(final Attribute attr) { - if (attr.isCodeAttribute()) { - attr.next = cattrs; - cattrs = attr; - } else { - attr.next = attrs; - attrs = attr; - } + ++parametersCount; + parameters.putShort((name == null) ? 0 : symbolTable.addConstantUtf8(name)).putShort(access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + defaultValue = new ByteVector(); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null); + } + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + // Create a ByteVector to hold an 'annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. + ByteVector annotation = new ByteVector(); + // Write type_index and reserve space for num_element_value_pairs. + annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastRuntimeVisibleAnnotation = + new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation); } - - @Override - public void visitCode() { + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget(typeRef, typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) { + if (visible) { + visibleAnnotableParameterCount = parameterCount; + } else { + invisibleAnnotableParameterCount = parameterCount; + } + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, final String annotationDescriptor, final boolean visible) { + // Create a ByteVector to hold an 'annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. + ByteVector annotation = new ByteVector(); + // Write type_index and reserve space for num_element_value_pairs. + annotation.putShort(symbolTable.addConstantUtf8(annotationDescriptor)).putShort(0); + if (visible) { + if (lastRuntimeVisibleParameterAnnotations == null) { + lastRuntimeVisibleParameterAnnotations = + new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + return lastRuntimeVisibleParameterAnnotations[parameter] = + new AnnotationWriter( + symbolTable, annotation, lastRuntimeVisibleParameterAnnotations[parameter]); + } else { + if (lastRuntimeInvisibleParameterAnnotations == null) { + lastRuntimeInvisibleParameterAnnotations = + new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + return lastRuntimeInvisibleParameterAnnotations[parameter] = + new AnnotationWriter( + symbolTable, annotation, lastRuntimeInvisibleParameterAnnotations[parameter]); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + if (attribute.isCodeAttribute()) { + attribute.nextAttribute = firstCodeAttribute; + firstCodeAttribute = attribute; + } else { + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + } + + @Override + public void visitCode() { + // Nothing to do. + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) { + if (compute == COMPUTE_ALL_FRAMES) { + return; } - @Override - public void visitFrame(final int type, final int nLocal, - final Object[] local, final int nStack, final Object[] stack) { - if (!ClassReader.FRAMES || compute == FRAMES) { - return; - } - + if (compute == COMPUTE_INSERTED_FRAMES) { + if (currentBasicBlock.frame == null) { + // This should happen only once, for the implicit first frame (which is explicitly visited + // in ClassReader if the EXPAND_ASM_INSNS option is used - and COMPUTE_INSERTED_FRAMES + // can't be set if EXPAND_ASM_INSNS is not used). + currentBasicBlock.frame = new CurrentFrame(currentBasicBlock); + currentBasicBlock.frame.setInputFrameFromDescriptor( + symbolTable, accessFlags, descriptor, nLocal); + currentBasicBlock.frame.accept(this); + } else { if (type == Opcodes.F_NEW) { - if (previousFrame == null) { - visitImplicitFirstFrame(); - } - currentLocals = nLocal; - int frameIndex = startFrame(code.length, nLocal, nStack); - for (int i = 0; i < nLocal; ++i) { - if (local[i] instanceof String) { - frame[frameIndex++] = Frame.OBJECT - | cw.addType((String) local[i]); - } else if (local[i] instanceof Integer) { - frame[frameIndex++] = ((Integer) local[i]).intValue(); - } else { - frame[frameIndex++] = Frame.UNINITIALIZED - | cw.addUninitializedType("", - ((Label) local[i]).position); - } - } - for (int i = 0; i < nStack; ++i) { - if (stack[i] instanceof String) { - frame[frameIndex++] = Frame.OBJECT - | cw.addType((String) stack[i]); - } else if (stack[i] instanceof Integer) { - frame[frameIndex++] = ((Integer) stack[i]).intValue(); - } else { - frame[frameIndex++] = Frame.UNINITIALIZED - | cw.addUninitializedType("", - ((Label) stack[i]).position); - } - } - endFrame(); + currentBasicBlock.frame.setInputFrameFromApiFormat( + symbolTable, nLocal, local, nStack, stack); } else { - int delta; - if (stackMap == null) { - stackMap = new ByteVector(); - delta = code.length; - } else { - delta = code.length - previousFrameOffset - 1; - if (delta < 0) { - if (type == Opcodes.F_SAME) { - return; - } else { - throw new IllegalStateException(); - } - } - } - - switch (type) { - case Opcodes.F_FULL: - currentLocals = nLocal; - stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal); - for (int i = 0; i < nLocal; ++i) { - writeFrameType(local[i]); - } - stackMap.putShort(nStack); - for (int i = 0; i < nStack; ++i) { - writeFrameType(stack[i]); - } - break; - case Opcodes.F_APPEND: - currentLocals += nLocal; - stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta); - for (int i = 0; i < nLocal; ++i) { - writeFrameType(local[i]); - } - break; - case Opcodes.F_CHOP: - currentLocals -= nLocal; - stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta); - break; - case Opcodes.F_SAME: - if (delta < 64) { - stackMap.putByte(delta); - } else { - stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); - } - break; - case Opcodes.F_SAME1: - if (delta < 64) { - stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); - } else { - stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) - .putShort(delta); - } - writeFrameType(stack[0]); - break; - } - - previousFrameOffset = code.length; - ++frameCount; - } + // In this case type is equal to F_INSERT by hypothesis, and currentBlock.frame contains + // the stack map frame at the current instruction, computed from the last F_NEW frame + // and the bytecode instructions in between (via calls to CurrentFrame#execute). + } + currentBasicBlock.frame.accept(this); + } + } else if (type == Opcodes.F_NEW) { + if (previousFrame == null) { + int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + Frame implicitFirstFrame = new Frame(new Label()); + implicitFirstFrame.setInputFrameFromDescriptor( + symbolTable, accessFlags, descriptor, argumentsSize); + implicitFirstFrame.accept(this); + } + currentLocals = nLocal; + int frameIndex = visitFrameStart(code.length, nLocal, nStack); + for (int i = 0; i < nLocal; ++i) { + currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]); + } + for (int i = 0; i < nStack; ++i) { + currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]); + } + visitFrameEnd(); + } else { + int offsetDelta; + if (stackMapTableEntries == null) { + stackMapTableEntries = new ByteVector(); + offsetDelta = code.length; + } else { + offsetDelta = code.length - previousFrameOffset - 1; + if (offsetDelta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + currentLocals = nLocal; + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal); + for (int i = 0; i < nLocal; ++i) { + putFrameType(local[i]); + } + stackMapTableEntries.putShort(nStack); + for (int i = 0; i < nStack; ++i) { + putFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + currentLocals += nLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocal).putShort(offsetDelta); + for (int i = 0; i < nLocal; ++i) { + putFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + currentLocals -= nLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - nLocal).putShort(offsetDelta); + break; + case Opcodes.F_SAME: + if (offsetDelta < 64) { + stackMapTableEntries.putByte(offsetDelta); + } else { + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta); + } + break; + case Opcodes.F_SAME1: + if (offsetDelta < 64) { + stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta); + } else { + stackMapTableEntries + .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(offsetDelta); + } + putFrameType(stack[0]); + break; + default: + throw new IllegalArgumentException(); + } - maxStack = Math.max(maxStack, nStack); - maxLocals = Math.max(maxLocals, currentLocals); - } - - @Override - public void visitInsn(final int opcode) { - // adds the instruction to the bytecode of the method - code.putByte(opcode); - // update currentBlock - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(opcode, 0, null, null); - } else { - // updates current and max stack sizes - int size = stackSize + Frame.SIZE[opcode]; - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - // if opcode == ATHROW or xRETURN, ends current block (no successor) - if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) - || opcode == Opcodes.ATHROW) { - noSuccessor(); - } - } + previousFrameOffset = code.length; + ++stackMapTableNumberOfEntries; } - @Override - public void visitIntInsn(final int opcode, final int operand) { - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(opcode, operand, null, null); - } else if (opcode != Opcodes.NEWARRAY) { - // updates current and max stack sizes only for NEWARRAY - // (stack size variation = 0 for BIPUSH or SIPUSH) - int size = stackSize + 1; - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - } - // adds the instruction to the bytecode of the method - if (opcode == Opcodes.SIPUSH) { - code.put12(opcode, operand); - } else { // BIPUSH or NEWARRAY - code.put11(opcode, operand); - } + if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + relativeStackSize = nStack; + if (nStack > maxRelativeStackSize) { + maxRelativeStackSize = relativeStackSize; + } } - @Override - public void visitVarInsn(final int opcode, final int var) { - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(opcode, var, null, null); - } else { - // updates current and max stack sizes - if (opcode == Opcodes.RET) { - // no stack change, but end of current block (no successor) - currentBlock.status |= Label.RET; - // save 'stackSize' here for future use - // (see {@link #findSubroutineSuccessors}) - currentBlock.inputStackTop = stackSize; - noSuccessor(); - } else { // xLOAD or xSTORE - int size = stackSize + Frame.SIZE[opcode]; - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - } + maxStack = Math.max(maxStack, nStack); + maxLocals = Math.max(maxLocals, currentLocals); + } + + @Override + public void visitInsn(final int opcode) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(opcode); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, null, null); + } else { + int size = relativeStackSize + STACK_SIZE_DELTA[opcode]; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) { + endCurrentBasicBlockWithNoSuccessor(); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // The stack size delta is 1 for BIPUSH or SIPUSH, and 0 for NEWARRAY. + int size = relativeStackSize + 1; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if (var < 4 && opcode != Opcodes.RET) { + int optimizedOpcode; + if (opcode < Opcodes.ISTORE) { + optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var; + } else { + optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var; + } + code.putByte(optimizedOpcode); + } else if (var >= 256) { + code.putByte(Constants.WIDE).put12(opcode, var); + } else { + code.put11(opcode, var); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, var, null, null); + } else { + if (opcode == Opcodes.RET) { + // No stack size delta. + currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_END; + currentBasicBlock.outputStackSize = (short) relativeStackSize; + endCurrentBasicBlockWithNoSuccessor(); + } else { // xLOAD or xSTORE + int size = relativeStackSize + STACK_SIZE_DELTA[opcode]; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + if (compute != COMPUTE_NOTHING) { + int currentMaxLocals; + if (opcode == Opcodes.LLOAD + || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE + || opcode == Opcodes.DSTORE) { + currentMaxLocals = var + 2; + } else { + currentMaxLocals = var + 1; + } + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + if (opcode >= Opcodes.ISTORE && compute == COMPUTE_ALL_FRAMES && firstHandler != null) { + // If there are exception handler blocks, each instruction within a handler range is, in + // theory, a basic block (since execution can jump from this instruction to the exception + // handler). As a consequence, the local variable types at the beginning of the handler + // block should be the merge of the local variable types at all the instructions within the + // handler range. However, instead of creating a basic block for each instruction, we can + // get the same result in a more efficient way. Namely, by starting a new basic block after + // each xSTORE instruction, which is what we do here. + visitLabel(new Label()); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol typeSymbol = symbolTable.addConstantClass(type); + code.put12(opcode, typeSymbol.index); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, lastBytecodeOffset, typeSymbol, symbolTable); + } else if (opcode == Opcodes.NEW) { + // The stack size delta is 1 for NEW, and 0 for ANEWARRAY, CHECKCAST, or INSTANCEOF. + int size = relativeStackSize + 1; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol fieldrefSymbol = symbolTable.addConstantFieldref(owner, name, descriptor); + code.put12(opcode, fieldrefSymbol.index); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, fieldrefSymbol, symbolTable); + } else { + int size; + char firstDescChar = descriptor.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 1 : 0); + break; + case Opcodes.PUTFIELD: + default: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -3 : -2); + break; } - if (compute != NOTHING) { - // updates max locals - int n; - if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD - || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) { - n = var + 2; - } else { - n = var + 1; - } - if (n > maxLocals) { - maxLocals = n; - } + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; } - // adds the instruction to the bytecode of the method - if (var < 4 && opcode != Opcodes.RET) { - int opt; - if (opcode < Opcodes.ISTORE) { - /* ILOAD_0 */ - opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var; - } else { - /* ISTORE_0 */ - opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var; - } - code.putByte(opt); - } else if (var >= 256) { - code.putByte(196 /* WIDE */).put12(opcode, var); + relativeStackSize = size; + } + } + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol methodrefSymbol = symbolTable.addConstantMethodref(owner, name, descriptor, isInterface); + if (opcode == Opcodes.INVOKEINTERFACE) { + code.put12(Opcodes.INVOKEINTERFACE, methodrefSymbol.index) + .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0); + } else { + code.put12(opcode, methodrefSymbol.index); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, methodrefSymbol, symbolTable); + } else { + int argumentsAndReturnSize = methodrefSymbol.getArgumentsAndReturnSizes(); + int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2); + int size; + if (opcode == Opcodes.INVOKESTATIC) { + size = relativeStackSize + stackSizeDelta + 1; } else { - code.put11(opcode, var); + size = relativeStackSize + stackSizeDelta; } - if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) { - visitLabel(new Label()); + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; } + relativeStackSize = size; + } } - - @Override - public void visitTypeInsn(final int opcode, final String type) { - Item i = cw.newClassItem(type); - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(opcode, code.length, cw, i); - } else if (opcode == Opcodes.NEW) { - // updates current and max stack sizes only if opcode == NEW - // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF) - int size = stackSize + 1; - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - } - // adds the instruction to the bytecode of the method - code.put12(opcode, i.index); - } - - @Override - public void visitFieldInsn(final int opcode, final String owner, - final String name, final String desc) { - Item i = cw.newFieldItem(owner, name, desc); - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(opcode, 0, cw, i); - } else { - int size; - // computes the stack size variation - char c = desc.charAt(0); - switch (opcode) { - case Opcodes.GETSTATIC: - size = stackSize + (c == 'D' || c == 'J' ? 2 : 1); - break; - case Opcodes.PUTSTATIC: - size = stackSize + (c == 'D' || c == 'J' ? -2 : -1); - break; - case Opcodes.GETFIELD: - size = stackSize + (c == 'D' || c == 'J' ? 1 : 0); - break; - // case Constants.PUTFIELD: - default: - size = stackSize + (c == 'D' || c == 'J' ? -3 : -2); - break; - } - // updates current and max stack sizes - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - } - // adds the instruction to the bytecode of the method - code.put12(opcode, i.index); - } - - @Override - public void visitMethodInsn(final int opcode, final String owner, - final String name, final String desc) { - boolean itf = opcode == Opcodes.INVOKEINTERFACE; - Item i = cw.newMethodItem(owner, name, desc, itf); - int argSize = i.intVal; - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(opcode, 0, cw, i); - } else { - /* - * computes the stack size variation. In order not to recompute - * several times this variation for the same Item, we use the - * intVal field of this item to store this variation, once it - * has been computed. More precisely this intVal field stores - * the sizes of the arguments and of the return value - * corresponding to desc. - */ - if (argSize == 0) { - // the above sizes have not been computed yet, - // so we compute them... - argSize = Type.getArgumentsAndReturnSizes(desc); - // ... and we save them in order - // not to recompute them in the future - i.intVal = argSize; - } - int size; - if (opcode == Opcodes.INVOKESTATIC) { - size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; - } else { - size = stackSize - (argSize >> 2) + (argSize & 0x03); - } - // updates current and max stack sizes - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - } - // adds the instruction to the bytecode of the method - if (itf) { - if (argSize == 0) { - argSize = Type.getArgumentsAndReturnSizes(desc); - i.intVal = argSize; - } - code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0); - } else { - code.put12(opcode, i.index); - } + } + + @Override + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol invokeDynamicSymbol = + symbolTable.addConstantInvokeDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + code.put12(Opcodes.INVOKEDYNAMIC, invokeDynamicSymbol.index); + code.putShort(0); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, invokeDynamicSymbol, symbolTable); + } else { + int argumentsAndReturnSize = invokeDynamicSymbol.getArgumentsAndReturnSizes(); + int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2) + 1; + int size = relativeStackSize + stackSizeDelta; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + // Compute the 'base' opcode, i.e. GOTO or JSR if opcode is GOTO_W or JSR_W, otherwise opcode. + int baseOpcode = + opcode >= Constants.GOTO_W ? opcode - Constants.WIDE_JUMP_OPCODE_DELTA : opcode; + boolean nextInsnIsJumpTarget = false; + if ((label.flags & Label.FLAG_RESOLVED) != 0 + && label.bytecodeOffset - code.length < Short.MIN_VALUE) { + // Case of a backward jump with an offset < -32768. In this case we automatically replace GOTO + // with GOTO_W, JSR with JSR_W and IFxxx with IFNOTxxx GOTO_W L:..., where + // IFNOTxxx is the "opposite" opcode of IFxxx (e.g. IFNE for IFEQ) and where designates + // the instruction just after the GOTO_W. + if (baseOpcode == Opcodes.GOTO) { + code.putByte(Constants.GOTO_W); + } else if (baseOpcode == Opcodes.JSR) { + code.putByte(Constants.JSR_W); + } else { + // Put the "opposite" opcode of baseOpcode. This can be done by flipping the least + // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ (with a + // pre and post offset by 1). The jump offset is 8 bytes (3 for IFNOTxxx, 5 for GOTO_W). + code.putByte(baseOpcode >= Opcodes.IFNULL ? baseOpcode ^ 1 : ((baseOpcode + 1) ^ 1) - 1); + code.putShort(8); + // Here we could put a GOTO_W in theory, but if ASM specific instructions are used in this + // method or another one, and if the class has frames, we will need to insert a frame after + // this GOTO_W during the additional ClassReader -> ClassWriter round trip to remove the ASM + // specific instructions. To not miss this additional frame, we need to use an ASM_GOTO_W + // here, which has the unfortunate effect of forcing this additional round trip (which in + // some case would not have been really necessary, but we can't know this at this point). + code.putByte(Constants.ASM_GOTO_W); + hasAsmInstructions = true; + // The instruction after the GOTO_W becomes the target of the IFNOT instruction. + nextInsnIsJumpTarget = true; + } + label.put(code, code.length - 1, true); + } else if (baseOpcode != opcode) { + // Case of a GOTO_W or JSR_W specified by the user (normally ClassReader when used to remove + // ASM specific instructions). In this case we keep the original instruction. + code.putByte(opcode); + label.put(code, code.length - 1, true); + } else { + // Case of a jump with an offset >= -32768, or of a jump with an unknown offset. In these + // cases we store the offset in 2 bytes (which will be increased via a ClassReader -> + // ClassWriter round trip if it turns out that 2 bytes are not sufficient). + code.putByte(baseOpcode); + label.put(code, code.length - 1, false); } - @Override - public void visitInvokeDynamicInsn(final String name, final String desc, - final Handle bsm, final Object... bsmArgs) { - Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs); - int argSize = i.intVal; - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); - } else { - /* - * computes the stack size variation. In order not to recompute - * several times this variation for the same Item, we use the - * intVal field of this item to store this variation, once it - * has been computed. More precisely this intVal field stores - * the sizes of the arguments and of the return value - * corresponding to desc. - */ - if (argSize == 0) { - // the above sizes have not been computed yet, - // so we compute them... - argSize = Type.getArgumentsAndReturnSizes(desc); - // ... and we save them in order - // not to recompute them in the future - i.intVal = argSize; - } - int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; - - // updates current and max stack sizes - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - } - // adds the instruction to the bytecode of the method - code.put12(Opcodes.INVOKEDYNAMIC, i.index); - code.putShort(0); - } - - @Override - public void visitJumpInsn(final int opcode, final Label label) { - Label nextInsn = null; - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(opcode, 0, null, null); - // 'label' is the target of a jump instruction - label.getFirst().status |= Label.TARGET; - // adds 'label' as a successor of this basic block - addSuccessor(Edge.NORMAL, label); - if (opcode != Opcodes.GOTO) { - // creates a Label for the next basic block - nextInsn = new Label(); - } - } else { - if (opcode == Opcodes.JSR) { - if ((label.status & Label.SUBROUTINE) == 0) { - label.status |= Label.SUBROUTINE; - ++subroutines; - } - currentBlock.status |= Label.JSR; - addSuccessor(stackSize + 1, label); - // creates a Label for the next basic block - nextInsn = new Label(); - /* - * note that, by construction in this method, a JSR block - * has at least two successors in the control flow graph: - * the first one leads the next instruction after the JSR, - * while the second one leads to the JSR target. - */ - } else { - // updates current stack size (max stack size unchanged - // because stack size variation always negative in this - // case) - stackSize += Frame.SIZE[opcode]; - addSuccessor(stackSize, label); - } - } - } - // adds the instruction to the bytecode of the method - if ((label.status & Label.RESOLVED) != 0 - && label.position - code.length < Short.MIN_VALUE) { - /* - * case of a backward jump with an offset < -32768. In this case we - * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx - * with IFNOTxxx GOTO_W , where IFNOTxxx is the - * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where - * designates the instruction just after the GOTO_W. - */ - if (opcode == Opcodes.GOTO) { - code.putByte(200); // GOTO_W - } else if (opcode == Opcodes.JSR) { - code.putByte(201); // JSR_W - } else { - // if the IF instruction is transformed into IFNOT GOTO_W the - // next instruction becomes the target of the IFNOT instruction - if (nextInsn != null) { - nextInsn.status |= Label.TARGET; - } - code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 - : opcode ^ 1); - code.putShort(8); // jump offset - code.putByte(200); // GOTO_W - } - label.put(this, code, code.length - 1, true); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + Label nextBasicBlock = null; + if (compute == COMPUTE_ALL_FRAMES) { + currentBasicBlock.frame.execute(baseOpcode, 0, null, null); + // Record the fact that 'label' is the target of a jump instruction. + label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + // Add 'label' as a successor of the current basic block. + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + if (baseOpcode != Opcodes.GOTO) { + // The next instruction starts a new basic block (except for GOTO: by default the code + // following a goto is unreachable - unless there is an explicit label for it - and we + // should not compute stack frame types for its instructions). + nextBasicBlock = new Label(); + } + } else if (compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(baseOpcode, 0, null, null); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += STACK_SIZE_DELTA[baseOpcode]; + } else { + if (baseOpcode == Opcodes.JSR) { + // Record the fact that 'label' designates a subroutine, if not already done. + if ((label.flags & Label.FLAG_SUBROUTINE_START) == 0) { + label.flags |= Label.FLAG_SUBROUTINE_START; + hasSubroutines = true; + } + currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_CALLER; + // Note that, by construction in this method, a block which calls a subroutine has at + // least two successors in the control flow graph: the first one (added below) leads to + // the instruction after the JSR, while the second one (added here) leads to the JSR + // target. Note that the first successor is virtual (it does not correspond to a possible + // execution path): it is only used to compute the successors of the basic blocks ending + // with a ret, in {@link Label#addSubroutineRetSuccessors}. + addSuccessorToCurrentBasicBlock(relativeStackSize + 1, label); + // The instruction after the JSR starts a new basic block. + nextBasicBlock = new Label(); } else { - /* - * case of a backward jump with an offset >= -32768, or of a forward - * jump with, of course, an unknown offset. In these cases we store - * the offset in 2 bytes (which will be increased in - * resizeInstructions, if needed). - */ - code.putByte(opcode); - label.put(this, code, code.length - 1, false); - } - if (currentBlock != null) { - if (nextInsn != null) { - // if the jump instruction is not a GOTO, the next instruction - // is also a successor of this instruction. Calling visitLabel - // adds the label of this next instruction as a successor of the - // current block, and starts a new basic block - visitLabel(nextInsn); - } - if (opcode == Opcodes.GOTO) { - noSuccessor(); - } - } + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += STACK_SIZE_DELTA[baseOpcode]; + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + } + // If the next instruction starts a new basic block, call visitLabel to add the label of this + // instruction as a successor of the current block, and to start a new basic block. + if (nextBasicBlock != null) { + if (nextInsnIsJumpTarget) { + nextBasicBlock.flags |= Label.FLAG_JUMP_TARGET; + } + visitLabel(nextBasicBlock); + } + if (baseOpcode == Opcodes.GOTO) { + endCurrentBasicBlockWithNoSuccessor(); + } } - - @Override - public void visitLabel(final Label label) { - // resolves previous forward references to label, if any - resize |= label.resolve(this, code.length, code.data); - // updates currentBlock - if ((label.status & Label.DEBUG) != 0) { - return; - } - if (compute == FRAMES) { - if (currentBlock != null) { - if (label.position == currentBlock.position) { - // successive labels, do not start a new basic block - currentBlock.status |= (label.status & Label.TARGET); - label.frame = currentBlock.frame; - return; - } - // ends current block (with one new successor) - addSuccessor(Edge.NORMAL, label); - } - // begins a new current block - currentBlock = label; - if (label.frame == null) { - label.frame = new Frame(); - label.frame.owner = label; - } - // updates the basic block list - if (previousBlock != null) { - if (label.position == previousBlock.position) { - previousBlock.status |= (label.status & Label.TARGET); - label.frame = previousBlock.frame; - currentBlock = previousBlock; - return; - } - previousBlock.successor = label; - } - previousBlock = label; - } else if (compute == MAXS) { - if (currentBlock != null) { - // ends current block (with one new successor) - currentBlock.outputStackMax = maxStackSize; - addSuccessor(stackSize, label); - } - // begins a new current block - currentBlock = label; - // resets the relative current and max stack sizes - stackSize = 0; - maxStackSize = 0; - // updates the basic block list - if (previousBlock != null) { - previousBlock.successor = label; - } - previousBlock = label; - } + } + + @Override + public void visitLabel(final Label label) { + // Resolve the forward references to this label, if any. + hasAsmInstructions |= label.resolve(code.data, code.length); + // visitLabel starts a new basic block (except for debug only labels), so we need to update the + // previous and current block references and list of successors. + if ((label.flags & Label.FLAG_DEBUG_ONLY) != 0) { + return; + } + if (compute == COMPUTE_ALL_FRAMES) { + if (currentBasicBlock != null) { + if (label.bytecodeOffset == currentBasicBlock.bytecodeOffset) { + // We use {@link Label#getCanonicalInstance} to store the state of a basic block in only + // one place, but this does not work for labels which have not been visited yet. + // Therefore, when we detect here two labels having the same bytecode offset, we need to + // - consolidate the state scattered in these two instances into the canonical instance: + currentBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET); + // - make sure the two instances share the same Frame instance (the implementation of + // {@link Label#getCanonicalInstance} relies on this property; here label.frame should be + // null): + label.frame = currentBasicBlock.frame; + // - and make sure to NOT assign 'label' into 'currentBasicBlock' or 'lastBasicBlock', so + // that they still refer to the canonical instance for this bytecode offset. + return; + } + // End the current basic block (with one new successor). + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + } + // Append 'label' at the end of the basic block list. + if (lastBasicBlock != null) { + if (label.bytecodeOffset == lastBasicBlock.bytecodeOffset) { + // Same comment as above. + lastBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET); + // Here label.frame should be null. + label.frame = lastBasicBlock.frame; + currentBasicBlock = lastBasicBlock; + return; + } + lastBasicBlock.nextBasicBlock = label; + } + lastBasicBlock = label; + // Make it the new current basic block. + currentBasicBlock = label; + // Here label.frame should be null. + label.frame = new Frame(label); + } else if (compute == COMPUTE_INSERTED_FRAMES) { + if (currentBasicBlock == null) { + // This case should happen only once, for the visitLabel call in the constructor. Indeed, if + // compute is equal to COMPUTE_INSERTED_FRAMES, currentBasicBlock stays unchanged. + currentBasicBlock = label; + } else { + // Update the frame owner so that a correct frame offset is computed in Frame.accept(). + currentBasicBlock.frame.owner = label; + } + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + if (currentBasicBlock != null) { + // End the current basic block (with one new successor). + currentBasicBlock.outputStackMax = (short) maxRelativeStackSize; + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + // Start a new current basic block, and reset the current and maximum relative stack sizes. + currentBasicBlock = label; + relativeStackSize = 0; + maxRelativeStackSize = 0; + // Append the new basic block at the end of the basic block list. + if (lastBasicBlock != null) { + lastBasicBlock.nextBasicBlock = label; + } + lastBasicBlock = label; + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES && currentBasicBlock == null) { + // This case should happen only once, for the visitLabel call in the constructor. Indeed, if + // compute is equal to COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES, currentBasicBlock stays + // unchanged. + currentBasicBlock = label; + } + } + + @Override + public void visitLdcInsn(final Object value) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol constantSymbol = symbolTable.addConstant(value); + int constantIndex = constantSymbol.index; + boolean isLongOrDouble = + constantSymbol.tag == Symbol.CONSTANT_LONG_TAG + || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG; + if (isLongOrDouble) { + code.put12(Constants.LDC2_W, constantIndex); + } else if (constantIndex >= 256) { + code.put12(Constants.LDC_W, constantIndex); + } else { + code.put11(Opcodes.LDC, constantIndex); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.LDC, 0, constantSymbol, symbolTable); + } else { + int size = relativeStackSize + (isLongOrDouble ? 2 : 1); + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitIincInsn(final int var, final int increment) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if ((var > 255) || (increment > 127) || (increment < -128)) { + code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(var, increment); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null + && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) { + currentBasicBlock.frame.execute(Opcodes.IINC, var, null, null); + } + if (compute != COMPUTE_NOTHING) { + int currentMaxLocals = var + 1; + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + } + + @Override + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(Opcodes.TABLESWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(code, lastBytecodeOffset, true); + code.putInt(min).putInt(max); + for (Label label : labels) { + label.put(code, lastBytecodeOffset, true); } + // If needed, update the maximum stack size and number of locals, and stack map frames. + visitSwitchInsn(dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(Opcodes.LOOKUPSWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(code, lastBytecodeOffset, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(code, lastBytecodeOffset, true); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // Add all the labels as successors of the current basic block. + addSuccessorToCurrentBasicBlock(Edge.JUMP, dflt); + dflt.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + for (Label label : labels) { + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + } + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + --relativeStackSize; + // Add all the labels as successors of the current basic block. + addSuccessorToCurrentBasicBlock(relativeStackSize, dflt); + for (Label label : labels) { + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + } + // End the current basic block. + endCurrentBasicBlockWithNoSuccessor(); + } + } + + @Override + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol descSymbol = symbolTable.addConstantClass(descriptor); + code.put12(Opcodes.MULTIANEWARRAY, descSymbol.index).putByte(numDimensions); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute( + Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable); + } else { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += 1 - numDimensions; + } + } + } + + @Override + public AnnotationVisitor visitInsnAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget((typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitTryCatchBlock( + final Label start, final Label end, final Label handler, final String type) { + Handler newHandler = + new Handler( + start, end, handler, type != null ? symbolTable.addConstantClass(type).index : 0, type); + if (firstHandler == null) { + firstHandler = newHandler; + } else { + lastHandler.nextHandler = newHandler; + } + lastHandler = newHandler; + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget(typeRef, typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitLocalVariable( + final String name, + final String descriptor, + final String signature, + final Label start, + final Label end, + final int index) { + if (signature != null) { + if (localVariableTypeTable == null) { + localVariableTypeTable = new ByteVector(); + } + ++localVariableTypeTableLength; + localVariableTypeTable + .putShort(start.bytecodeOffset) + .putShort(end.bytecodeOffset - start.bytecodeOffset) + .putShort(symbolTable.addConstantUtf8(name)) + .putShort(symbolTable.addConstantUtf8(signature)) + .putShort(index); + } + if (localVariableTable == null) { + localVariableTable = new ByteVector(); + } + ++localVariableTableLength; + localVariableTable + .putShort(start.bytecodeOffset) + .putShort(end.bytecodeOffset - start.bytecodeOffset) + .putShort(symbolTable.addConstantUtf8(name)) + .putShort(symbolTable.addConstantUtf8(descriptor)) + .putShort(index); + if (compute != COMPUTE_NOTHING) { + char firstDescChar = descriptor.charAt(0); + int currentMaxLocals = index + (firstDescChar == 'J' || firstDescChar == 'D' ? 2 : 1); + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation( + final int typeRef, + final TypePath typePath, + final Label[] start, + final Label[] end, + final int[] index, + final String descriptor, + final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + typeAnnotation.putByte(typeRef >>> 24).putShort(start.length); + for (int i = 0; i < start.length; ++i) { + typeAnnotation + .putShort(start[i].bytecodeOffset) + .putShort(end[i].bytecodeOffset - start[i].bytecodeOffset) + .putShort(index[i]); + } + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation); + } + } - @Override - public void visitLdcInsn(final Object cst) { - Item i = cw.newConstItem(cst); - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); - } else { - int size; - // computes the stack size variation - if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { - size = stackSize + 2; - } else { - size = stackSize + 1; - } - // updates current and max stack sizes - if (size > maxStackSize) { - maxStackSize = size; - } - stackSize = size; - } - } - // adds the instruction to the bytecode of the method - int index = i.index; - if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { - code.put12(20 /* LDC2_W */, index); - } else if (index >= 256) { - code.put12(19 /* LDC_W */, index); - } else { - code.put11(Opcodes.LDC, index); - } + @Override + public void visitLineNumber(final int line, final Label start) { + if (lineNumberTable == null) { + lineNumberTable = new ByteVector(); + } + ++lineNumberTableLength; + lineNumberTable.putShort(start.bytecodeOffset); + lineNumberTable.putShort(line); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (compute == COMPUTE_ALL_FRAMES) { + computeAllFrames(); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + computeMaxStackAndLocal(); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + this.maxStack = maxRelativeStackSize; + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + /** Computes all the stack map frames of the method, from scratch. */ + private void computeAllFrames() { + // Complete the control flow graph with exception handler blocks. + Handler handler = firstHandler; + while (handler != null) { + String catchTypeDescriptor = + handler.catchTypeDescriptor == null ? "java/lang/Throwable" : handler.catchTypeDescriptor; + int catchType = Frame.getAbstractTypeFromInternalName(symbolTable, catchTypeDescriptor); + // Mark handlerBlock as an exception handler. + Label handlerBlock = handler.handlerPc.getCanonicalInstance(); + handlerBlock.flags |= Label.FLAG_JUMP_TARGET; + // Add handlerBlock as a successor of all the basic blocks in the exception handler range. + Label handlerRangeBlock = handler.startPc.getCanonicalInstance(); + Label handlerRangeEnd = handler.endPc.getCanonicalInstance(); + while (handlerRangeBlock != handlerRangeEnd) { + handlerRangeBlock.outgoingEdges = + new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges); + handlerRangeBlock = handlerRangeBlock.nextBasicBlock; + } + handler = handler.nextHandler; } - @Override - public void visitIincInsn(final int var, final int increment) { - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(Opcodes.IINC, var, null, null); - } - } - if (compute != NOTHING) { - // updates max locals - int n = var + 1; - if (n > maxLocals) { - maxLocals = n; - } - } - // adds the instruction to the bytecode of the method - if ((var > 255) || (increment > 127) || (increment < -128)) { - code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var) - .putShort(increment); - } else { - code.putByte(Opcodes.IINC).put11(var, increment); - } + // Create and visit the first (implicit) frame. + Frame firstFrame = firstBasicBlock.frame; + firstFrame.setInputFrameFromDescriptor(symbolTable, accessFlags, descriptor, this.maxLocals); + firstFrame.accept(this); + + // Fix point algorithm: add the first basic block to a list of blocks to process (i.e. blocks + // whose stack map frame has changed) and, while there are blocks to process, remove one from + // the list and update the stack map frames of its successor blocks in the control flow graph + // (which might change them, in which case these blocks must be processed too, and are thus + // added to the list of blocks to process). Also compute the maximum stack size of the method, + // as a by-product. + Label listOfBlocksToProcess = firstBasicBlock; + listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST; + int maxStackSize = 0; + while (listOfBlocksToProcess != Label.EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + basicBlock.nextListElement = null; + // By definition, basicBlock is reachable. + basicBlock.flags |= Label.FLAG_REACHABLE; + // Update the (absolute) maximum stack size. + int maxBlockStackSize = basicBlock.frame.getInputStackSize() + basicBlock.outputStackMax; + if (maxBlockStackSize > maxStackSize) { + maxStackSize = maxBlockStackSize; + } + // Update the successor blocks of basicBlock in the control flow graph. + Edge outgoingEdge = basicBlock.outgoingEdges; + while (outgoingEdge != null) { + Label successorBlock = outgoingEdge.successor.getCanonicalInstance(); + boolean successorBlockChanged = + basicBlock.frame.merge(symbolTable, successorBlock.frame, outgoingEdge.info); + if (successorBlockChanged && successorBlock.nextListElement == null) { + // If successorBlock has changed it must be processed. Thus, if it is not already in the + // list of blocks to process, add it to this list. + successorBlock.nextListElement = listOfBlocksToProcess; + listOfBlocksToProcess = successorBlock; + } + outgoingEdge = outgoingEdge.nextEdge; + } } - @Override - public void visitTableSwitchInsn(final int min, final int max, - final Label dflt, final Label... labels) { - // adds the instruction to the bytecode of the method - int source = code.length; - code.putByte(Opcodes.TABLESWITCH); - code.putByteArray(null, 0, (4 - code.length % 4) % 4); - dflt.put(this, code, source, true); - code.putInt(min).putInt(max); - for (int i = 0; i < labels.length; ++i) { - labels[i].put(this, code, source, true); - } - // updates currentBlock - visitSwitchInsn(dflt, labels); - } - - @Override - public void visitLookupSwitchInsn(final Label dflt, final int[] keys, - final Label[] labels) { - // adds the instruction to the bytecode of the method - int source = code.length; - code.putByte(Opcodes.LOOKUPSWITCH); - code.putByteArray(null, 0, (4 - code.length % 4) % 4); - dflt.put(this, code, source, true); - code.putInt(labels.length); - for (int i = 0; i < labels.length; ++i) { - code.putInt(keys[i]); - labels[i].put(this, code, source, true); - } - // updates currentBlock - visitSwitchInsn(dflt, labels); - } - - private void visitSwitchInsn(final Label dflt, final Label[] labels) { - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); - // adds current block successors - addSuccessor(Edge.NORMAL, dflt); - dflt.getFirst().status |= Label.TARGET; - for (int i = 0; i < labels.length; ++i) { - addSuccessor(Edge.NORMAL, labels[i]); - labels[i].getFirst().status |= Label.TARGET; - } - } else { - // updates current stack size (max stack size unchanged) - --stackSize; - // adds current block successors - addSuccessor(stackSize, dflt); - for (int i = 0; i < labels.length; ++i) { - addSuccessor(stackSize, labels[i]); - } - } - // ends current block - noSuccessor(); - } + // Loop over all the basic blocks and visit the stack map frames that must be stored in the + // StackMapTable attribute. Also replace unreachable code with NOP* ATHROW, and remove it from + // exception handler ranges. + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) + == (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) { + basicBlock.frame.accept(this); + } + if ((basicBlock.flags & Label.FLAG_REACHABLE) == 0) { + // Find the start and end bytecode offsets of this unreachable block. + Label nextBasicBlock = basicBlock.nextBasicBlock; + int startOffset = basicBlock.bytecodeOffset; + int endOffset = (nextBasicBlock == null ? code.length : nextBasicBlock.bytecodeOffset) - 1; + if (endOffset >= startOffset) { + // Replace its instructions with NOP ... NOP ATHROW. + for (int i = startOffset; i < endOffset; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[endOffset] = (byte) Opcodes.ATHROW; + // Emit a frame for this unreachable block, with no local and a Throwable on the stack + // (so that the ATHROW could consume this Throwable if it were reachable). + int frameIndex = visitFrameStart(startOffset, /* nLocal = */ 0, /* nStack = */ 1); + currentFrame[frameIndex] = + Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable"); + visitFrameEnd(); + // Remove this unreachable basic block from the exception handler ranges. + firstHandler = Handler.removeRange(firstHandler, basicBlock, nextBasicBlock); + // The maximum stack size is now at least one, because of the Throwable declared above. + maxStackSize = Math.max(maxStackSize, 1); + } + } + basicBlock = basicBlock.nextBasicBlock; } - @Override - public void visitMultiANewArrayInsn(final String desc, final int dims) { - Item i = cw.newClassItem(desc); - // Label currentBlock = this.currentBlock; - if (currentBlock != null) { - if (compute == FRAMES) { - currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); - } else { - // updates current stack size (max stack size unchanged because - // stack size variation always negative or null) - stackSize += 1 - dims; - } - } - // adds the instruction to the bytecode of the method - code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims); - } - - @Override - public void visitTryCatchBlock(final Label start, final Label end, - final Label handler, final String type) { - ++handlerCount; - Handler h = new Handler(); - h.start = start; - h.end = end; - h.handler = handler; - h.desc = type; - h.type = type != null ? cw.newClass(type) : 0; - if (lastHandler == null) { - firstHandler = h; + this.maxStack = maxStackSize; + } + + /** Computes the maximum stack size of the method. */ + private void computeMaxStackAndLocal() { + // Complete the control flow graph with exception handler blocks. + Handler handler = firstHandler; + while (handler != null) { + Label handlerBlock = handler.handlerPc; + Label handlerRangeBlock = handler.startPc; + Label handlerRangeEnd = handler.endPc; + // Add handlerBlock as a successor of all the basic blocks in the exception handler range. + while (handlerRangeBlock != handlerRangeEnd) { + if ((handlerRangeBlock.flags & Label.FLAG_SUBROUTINE_CALLER) == 0) { + handlerRangeBlock.outgoingEdges = + new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges); } else { - lastHandler.next = h; - } - lastHandler = h; + // If handlerRangeBlock is a JSR block, add handlerBlock after the first two outgoing + // edges to preserve the hypothesis about JSR block successors order (see + // {@link #visitJumpInsn}). + handlerRangeBlock.outgoingEdges.nextEdge.nextEdge = + new Edge( + Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge); + } + handlerRangeBlock = handlerRangeBlock.nextBasicBlock; + } + handler = handler.nextHandler; } - @Override - public void visitLocalVariable(final String name, final String desc, - final String signature, final Label start, final Label end, - final int index) { - if (signature != null) { - if (localVarType == null) { - localVarType = new ByteVector(); - } - ++localVarTypeCount; - localVarType.putShort(start.position) - .putShort(end.position - start.position) - .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature)) - .putShort(index); - } - if (localVar == null) { - localVar = new ByteVector(); - } - ++localVarCount; - localVar.putShort(start.position) - .putShort(end.position - start.position) - .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc)) - .putShort(index); - if (compute != NOTHING) { - // updates max locals - char c = desc.charAt(0); - int n = index + (c == 'J' || c == 'D' ? 2 : 1); - if (n > maxLocals) { - maxLocals = n; - } - } + // Complete the control flow graph with the successor blocks of subroutines, if needed. + if (hasSubroutines) { + // First step: find the subroutines. This step determines, for each basic block, to which + // subroutine(s) it belongs. Start with the main "subroutine": + short numSubroutines = 1; + firstBasicBlock.markSubroutine(numSubroutines); + // Then, mark the subroutines called by the main subroutine, then the subroutines called by + // those called by the main subroutine, etc. + for (short currentSubroutine = 1; currentSubroutine <= numSubroutines; ++currentSubroutine) { + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0 + && basicBlock.subroutineId == currentSubroutine) { + Label jsrTarget = basicBlock.outgoingEdges.nextEdge.successor; + if (jsrTarget.subroutineId == 0) { + // If this subroutine has not been marked yet, find its basic blocks. + jsrTarget.markSubroutine(++numSubroutines); + } + } + basicBlock = basicBlock.nextBasicBlock; + } + } + // Second step: find the successors in the control flow graph of each subroutine basic block + // 'r' ending with a RET instruction. These successors are the virtual successors of the basic + // blocks ending with JSR instructions (see {@link #visitJumpInsn)} that can reach 'r'. + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) { + // By construction, jsr targets are stored in the second outgoing edge of basic blocks + // that ends with a jsr instruction (see {@link #FLAG_SUBROUTINE_CALLER}). + Label subroutine = basicBlock.outgoingEdges.nextEdge.successor; + subroutine.addSubroutineRetSuccessors(basicBlock); + } + basicBlock = basicBlock.nextBasicBlock; + } } - @Override - public void visitLineNumber(final int line, final Label start) { - if (lineNumber == null) { - lineNumber = new ByteVector(); - } - ++lineNumberCount; - lineNumber.putShort(start.position); - lineNumber.putShort(line); - } - - @Override - public void visitMaxs(final int maxStack, final int maxLocals) { - if (ClassReader.FRAMES && compute == FRAMES) { - // completes the control flow graph with exception handler blocks - Handler handler = firstHandler; - while (handler != null) { - Label l = handler.start.getFirst(); - Label h = handler.handler.getFirst(); - Label e = handler.end.getFirst(); - // computes the kind of the edges to 'h' - String t = handler.desc == null ? "java/lang/Throwable" - : handler.desc; - int kind = Frame.OBJECT | cw.addType(t); - // h is an exception handler - h.status |= Label.TARGET; - // adds 'h' as a successor of labels between 'start' and 'end' - while (l != e) { - // creates an edge to 'h' - Edge b = new Edge(); - b.info = kind; - b.successor = h; - // adds it to the successors of 'l' - b.next = l.successors; - l.successors = b; - // goes to the next label - l = l.successor; - } - handler = handler.next; - } - - // creates and visits the first (implicit) frame - Frame f = labels.frame; - Type[] args = Type.getArgumentTypes(descriptor); - f.initInputFrame(cw, access, args, this.maxLocals); - visitFrame(f); - - /* - * fix point algorithm: mark the first basic block as 'changed' - * (i.e. put it in the 'changed' list) and, while there are changed - * basic blocks, choose one, mark it as unchanged, and update its - * successors (which can be changed in the process). - */ - int max = 0; - Label changed = labels; - while (changed != null) { - // removes a basic block from the list of changed basic blocks - Label l = changed; - changed = changed.next; - l.next = null; - f = l.frame; - // a reachable jump target must be stored in the stack map - if ((l.status & Label.TARGET) != 0) { - l.status |= Label.STORE; - } - // all visited labels are reachable, by definition - l.status |= Label.REACHABLE; - // updates the (absolute) maximum stack size - int blockMax = f.inputStack.length + l.outputStackMax; - if (blockMax > max) { - max = blockMax; - } - // updates the successors of the current basic block - Edge e = l.successors; - while (e != null) { - Label n = e.successor.getFirst(); - boolean change = f.merge(cw, n.frame, e.info); - if (change && n.next == null) { - // if n has changed and is not already in the 'changed' - // list, adds it to this list - n.next = changed; - changed = n; - } - e = e.next; - } - } - - // visits all the frames that must be stored in the stack map - Label l = labels; - while (l != null) { - f = l.frame; - if ((l.status & Label.STORE) != 0) { - visitFrame(f); - } - if ((l.status & Label.REACHABLE) == 0) { - // finds start and end of dead basic block - Label k = l.successor; - int start = l.position; - int end = (k == null ? code.length : k.position) - 1; - // if non empty basic block - if (end >= start) { - max = Math.max(max, 1); - // replaces instructions with NOP ... NOP ATHROW - for (int i = start; i < end; ++i) { - code.data[i] = Opcodes.NOP; - } - code.data[end] = (byte) Opcodes.ATHROW; - // emits a frame for this unreachable block - int frameIndex = startFrame(start, 0, 1); - frame[frameIndex] = Frame.OBJECT - | cw.addType("java/lang/Throwable"); - endFrame(); - // removes the start-end range from the exception - // handlers - firstHandler = Handler.remove(firstHandler, l, k); - } - } - l = l.successor; - } - - handler = firstHandler; - handlerCount = 0; - while (handler != null) { - handlerCount += 1; - handler = handler.next; - } - - this.maxStack = max; - } else if (compute == MAXS) { - // completes the control flow graph with exception handler blocks - Handler handler = firstHandler; - while (handler != null) { - Label l = handler.start; - Label h = handler.handler; - Label e = handler.end; - // adds 'h' as a successor of labels between 'start' and 'end' - while (l != e) { - // creates an edge to 'h' - Edge b = new Edge(); - b.info = Edge.EXCEPTION; - b.successor = h; - // adds it to the successors of 'l' - if ((l.status & Label.JSR) == 0) { - b.next = l.successors; - l.successors = b; - } else { - // if l is a JSR block, adds b after the first two edges - // to preserve the hypothesis about JSR block successors - // order (see {@link #visitJumpInsn}) - b.next = l.successors.next.next; - l.successors.next.next = b; - } - // goes to the next label - l = l.successor; - } - handler = handler.next; - } - - if (subroutines > 0) { - // completes the control flow graph with the RET successors - /* - * first step: finds the subroutines. This step determines, for - * each basic block, to which subroutine(s) it belongs. - */ - // finds the basic blocks that belong to the "main" subroutine - int id = 0; - labels.visitSubroutine(null, 1, subroutines); - // finds the basic blocks that belong to the real subroutines - Label l = labels; - while (l != null) { - if ((l.status & Label.JSR) != 0) { - // the subroutine is defined by l's TARGET, not by l - Label subroutine = l.successors.next.successor; - // if this subroutine has not been visited yet... - if ((subroutine.status & Label.VISITED) == 0) { - // ...assigns it a new id and finds its basic blocks - id += 1; - subroutine.visitSubroutine(null, (id / 32L) << 32 - | (1L << (id % 32)), subroutines); - } - } - l = l.successor; - } - // second step: finds the successors of RET blocks - l = labels; - while (l != null) { - if ((l.status & Label.JSR) != 0) { - Label L = labels; - while (L != null) { - L.status &= ~Label.VISITED2; - L = L.successor; - } - // the subroutine is defined by l's TARGET, not by l - Label subroutine = l.successors.next.successor; - subroutine.visitSubroutine(l, 0, subroutines); - } - l = l.successor; - } - } - - /* - * control flow analysis algorithm: while the block stack is not - * empty, pop a block from this stack, update the max stack size, - * compute the true (non relative) begin stack size of the - * successors of this block, and push these successors onto the - * stack (unless they have already been pushed onto the stack). - * Note: by hypothesis, the {@link Label#inputStackTop} of the - * blocks in the block stack are the true (non relative) beginning - * stack sizes of these blocks. - */ - int max = 0; - Label stack = labels; - while (stack != null) { - // pops a block from the stack - Label l = stack; - stack = stack.next; - // computes the true (non relative) max stack size of this block - int start = l.inputStackTop; - int blockMax = start + l.outputStackMax; - // updates the global max stack size - if (blockMax > max) { - max = blockMax; - } - // analyzes the successors of the block - Edge b = l.successors; - if ((l.status & Label.JSR) != 0) { - // ignores the first edge of JSR blocks (virtual successor) - b = b.next; - } - while (b != null) { - l = b.successor; - // if this successor has not already been pushed... - if ((l.status & Label.PUSHED) == 0) { - // computes its true beginning stack size... - l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start - + b.info; - // ...and pushes it onto the stack - l.status |= Label.PUSHED; - l.next = stack; - stack = l; - } - b = b.next; - } - } - this.maxStack = Math.max(maxStack, max); - } else { - this.maxStack = maxStack; - this.maxLocals = maxLocals; - } + // Data flow algorithm: put the first basic block in a list of blocks to process (i.e. blocks + // whose input stack size has changed) and, while there are blocks to process, remove one + // from the list, update the input stack size of its successor blocks in the control flow + // graph, and add these blocks to the list of blocks to process (if not already done). + Label listOfBlocksToProcess = firstBasicBlock; + listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST; + int maxStackSize = maxStack; + while (listOfBlocksToProcess != Label.EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. Note that we don't reset + // basicBlock.nextListElement to null on purpose, to make sure we don't reprocess already + // processed basic blocks. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + // Compute the (absolute) input stack size and maximum stack size of this block. + int inputStackTop = basicBlock.inputStackSize; + int maxBlockStackSize = inputStackTop + basicBlock.outputStackMax; + // Update the absolute maximum stack size of the method. + if (maxBlockStackSize > maxStackSize) { + maxStackSize = maxBlockStackSize; + } + // Update the input stack size of the successor blocks of basicBlock in the control flow + // graph, and add these blocks to the list of blocks to process, if not already done. + Edge outgoingEdge = basicBlock.outgoingEdges; + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) { + // Ignore the first outgoing edge of the basic blocks ending with a jsr: these are virtual + // edges which lead to the instruction just after the jsr, and do not correspond to a + // possible execution path (see {@link #visitJumpInsn} and + // {@link Label#FLAG_SUBROUTINE_CALLER}). + outgoingEdge = outgoingEdge.nextEdge; + } + while (outgoingEdge != null) { + Label successorBlock = outgoingEdge.successor; + if (successorBlock.nextListElement == null) { + successorBlock.inputStackSize = + (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info); + successorBlock.nextListElement = listOfBlocksToProcess; + listOfBlocksToProcess = successorBlock; + } + outgoingEdge = outgoingEdge.nextEdge; + } } - - @Override - public void visitEnd() { - } - - // ------------------------------------------------------------------------ - // Utility methods: control flow analysis algorithm - // ------------------------------------------------------------------------ - - /** - * Adds a successor to the {@link #currentBlock currentBlock} block. - * - * @param info - * information about the control flow edge to be added. - * @param successor - * the successor block to be added to the current block. - */ - private void addSuccessor(final int info, final Label successor) { - // creates and initializes an Edge object... - Edge b = new Edge(); - b.info = info; - b.successor = successor; - // ...and adds it to the successor list of the currentBlock block - b.next = currentBlock.successors; - currentBlock.successors = b; - } - - /** - * Ends the current basic block. This method must be used in the case where - * the current basic block does not have any successor. - */ - private void noSuccessor() { - if (compute == FRAMES) { - Label l = new Label(); - l.frame = new Frame(); - l.frame.owner = l; - l.resolve(this, code.length, code.data); - previousBlock.successor = l; - previousBlock = l; - } else { - currentBlock.outputStackMax = maxStackSize; - } - currentBlock = null; - } - - // ------------------------------------------------------------------------ - // Utility methods: stack map frames - // ------------------------------------------------------------------------ - - /** - * Visits a frame that has been computed from scratch. - * - * @param f - * the frame that must be visited. - */ - private void visitFrame(final Frame f) { - int i, t; - int nTop = 0; - int nLocal = 0; - int nStack = 0; - int[] locals = f.inputLocals; - int[] stacks = f.inputStack; - // computes the number of locals (ignores TOP types that are just after - // a LONG or a DOUBLE, and all trailing TOP types) - for (i = 0; i < locals.length; ++i) { - t = locals[i]; - if (t == Frame.TOP) { - ++nTop; - } else { - nLocal += nTop + 1; - nTop = 0; - } - if (t == Frame.LONG || t == Frame.DOUBLE) { - ++i; - } - } - // computes the stack size (ignores TOP types that are just after - // a LONG or a DOUBLE) - for (i = 0; i < stacks.length; ++i) { - t = stacks[i]; - ++nStack; - if (t == Frame.LONG || t == Frame.DOUBLE) { - ++i; - } - } - // visits the frame and its content - int frameIndex = startFrame(f.owner.position, nLocal, nStack); - for (i = 0; nLocal > 0; ++i, --nLocal) { - t = locals[i]; - frame[frameIndex++] = t; - if (t == Frame.LONG || t == Frame.DOUBLE) { - ++i; - } - } - for (i = 0; i < stacks.length; ++i) { - t = stacks[i]; - frame[frameIndex++] = t; - if (t == Frame.LONG || t == Frame.DOUBLE) { - ++i; - } - } - endFrame(); - } - - /** - * Visit the implicit first frame of this method. - */ - private void visitImplicitFirstFrame() { - // There can be at most descriptor.length() + 1 locals - int frameIndex = startFrame(0, descriptor.length() + 1, 0); - if ((access & Opcodes.ACC_STATIC) == 0) { - if ((access & ACC_CONSTRUCTOR) == 0) { - frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName); - } else { - frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS; - } - } - int i = 1; - loop: while (true) { - int j = i; - switch (descriptor.charAt(i++)) { - case 'Z': - case 'C': - case 'B': - case 'S': - case 'I': - frame[frameIndex++] = 1; // Opcodes.INTEGER; - break; - case 'F': - frame[frameIndex++] = 2; // Opcodes.FLOAT; - break; - case 'J': - frame[frameIndex++] = 4; // Opcodes.LONG; - break; - case 'D': - frame[frameIndex++] = 3; // Opcodes.DOUBLE; - break; - case '[': - while (descriptor.charAt(i) == '[') { - ++i; - } - if (descriptor.charAt(i) == 'L') { - ++i; - while (descriptor.charAt(i) != ';') { - ++i; - } - } - frame[frameIndex++] = Frame.OBJECT - | cw.addType(descriptor.substring(j, ++i)); - break; - case 'L': - while (descriptor.charAt(i) != ';') { - ++i; - } - frame[frameIndex++] = Frame.OBJECT - | cw.addType(descriptor.substring(j + 1, i++)); - break; - default: - break loop; - } - } - frame[1] = frameIndex - 3; - endFrame(); - } - - /** - * Starts the visit of a stack map frame. - * - * @param offset - * the offset of the instruction to which the frame corresponds. - * @param nLocal - * the number of local variables in the frame. - * @param nStack - * the number of stack elements in the frame. - * @return the index of the next element to be written in this frame. - */ - private int startFrame(final int offset, final int nLocal, final int nStack) { - int n = 3 + nLocal + nStack; - if (frame == null || frame.length < n) { - frame = new int[n]; - } - frame[0] = offset; - frame[1] = nLocal; - frame[2] = nStack; - return 3; - } - - /** - * Checks if the visit of the current frame {@link #frame} is finished, and - * if yes, write it in the StackMapTable attribute. - */ - private void endFrame() { - if (previousFrame != null) { // do not write the first frame - if (stackMap == null) { - stackMap = new ByteVector(); - } - writeFrame(); - ++frameCount; - } - previousFrame = frame; - frame = null; - } - - /** - * Compress and writes the current frame {@link #frame} in the StackMapTable - * attribute. - */ - private void writeFrame() { - int clocalsSize = frame[1]; - int cstackSize = frame[2]; - if ((cw.version & 0xFFFF) < Opcodes.V1_6) { - stackMap.putShort(frame[0]).putShort(clocalsSize); - writeFrameTypes(3, 3 + clocalsSize); - stackMap.putShort(cstackSize); - writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); - return; - } - int localsSize = previousFrame[1]; - int type = FULL_FRAME; - int k = 0; - int delta; - if (frameCount == 0) { - delta = frame[0]; - } else { - delta = frame[0] - previousFrame[0] - 1; - } - if (cstackSize == 0) { - k = clocalsSize - localsSize; - switch (k) { - case -3: - case -2: - case -1: - type = CHOP_FRAME; - localsSize = clocalsSize; - break; - case 0: - type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED; - break; - case 1: - case 2: - case 3: - type = APPEND_FRAME; - break; - } - } else if (clocalsSize == localsSize && cstackSize == 1) { - type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME - : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; - } - if (type != FULL_FRAME) { - // verify if locals are the same - int l = 3; - for (int j = 0; j < localsSize; j++) { - if (frame[l] != previousFrame[l]) { - type = FULL_FRAME; - break; - } - l++; - } - } - switch (type) { - case SAME_FRAME: - stackMap.putByte(delta); - break; - case SAME_LOCALS_1_STACK_ITEM_FRAME: - stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); - writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); - break; - case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: - stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort( - delta); - writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); - break; - case SAME_FRAME_EXTENDED: - stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); - break; - case CHOP_FRAME: - stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); - break; - case APPEND_FRAME: - stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); - writeFrameTypes(3 + localsSize, 3 + clocalsSize); - break; - // case FULL_FRAME: + this.maxStack = maxStackSize; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: control flow analysis algorithm + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a successor to {@link #currentBasicBlock} in the control flow graph. + * + * @param info information about the control flow edge to be added. + * @param successor the successor block to be added to the current basic block. + */ + private void addSuccessorToCurrentBasicBlock(final int info, final Label successor) { + currentBasicBlock.outgoingEdges = new Edge(info, successor, currentBasicBlock.outgoingEdges); + } + + /** + * Ends the current basic block. This method must be used in the case where the current basic + * block does not have any successor. + * + *

WARNING: this method must be called after the currently visited instruction has been put in + * {@link #code} (if frames are computed, this method inserts a new Label to start a new basic + * block after the current instruction). + */ + private void endCurrentBasicBlockWithNoSuccessor() { + if (compute == COMPUTE_ALL_FRAMES) { + Label nextBasicBlock = new Label(); + nextBasicBlock.frame = new Frame(nextBasicBlock); + nextBasicBlock.resolve(code.data, code.length); + lastBasicBlock.nextBasicBlock = nextBasicBlock; + lastBasicBlock = nextBasicBlock; + currentBasicBlock = null; + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + currentBasicBlock.outputStackMax = (short) maxRelativeStackSize; + currentBasicBlock = null; + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Starts the visit of a new stack map frame, stored in {@link #currentFrame}. + * + * @param offset the bytecode offset of the instruction to which the frame corresponds. + * @param nLocal the number of local variables in the frame. + * @param nStack the number of stack elements in the frame. + * @return the index of the next element to be written in this frame. + */ + int visitFrameStart(final int offset, final int nLocal, final int nStack) { + int frameLength = 3 + nLocal + nStack; + if (currentFrame == null || currentFrame.length < frameLength) { + currentFrame = new int[frameLength]; + } + currentFrame[0] = offset; + currentFrame[1] = nLocal; + currentFrame[2] = nStack; + return 3; + } + + /** + * Sets an abstract type in {@link #currentFrame}. + * + * @param frameIndex the index of the element to be set in {@link #currentFrame}. + * @param abstractType an abstract type. + */ + void visitAbstractType(final int frameIndex, final int abstractType) { + currentFrame[frameIndex] = abstractType; + } + + /** + * Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by + * updating the StackMapTable number_of_entries (except if the current frame is the first one, + * which is implicit in StackMapTable). Then resets {@link #currentFrame} to null. + */ + void visitFrameEnd() { + if (previousFrame != null) { + if (stackMapTableEntries == null) { + stackMapTableEntries = new ByteVector(); + } + putFrame(); + ++stackMapTableNumberOfEntries; + } + previousFrame = currentFrame; + currentFrame = null; + } + + /** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */ + private void putFrame() { + final int nLocal = currentFrame[1]; + final int nStack = currentFrame[2]; + if (symbolTable.getMajorVersion() < Opcodes.V1_6) { + // Generate a StackMap attribute entry, which are always uncompressed. + stackMapTableEntries.putShort(currentFrame[0]).putShort(nLocal); + putAbstractTypes(3, 3 + nLocal); + stackMapTableEntries.putShort(nStack); + putAbstractTypes(3 + nLocal, 3 + nLocal + nStack); + return; + } + final int offsetDelta = + stackMapTableNumberOfEntries == 0 + ? currentFrame[0] + : currentFrame[0] - previousFrame[0] - 1; + final int previousNlocal = previousFrame[1]; + final int nLocalDelta = nLocal - previousNlocal; + int type = Frame.FULL_FRAME; + if (nStack == 0) { + switch (nLocalDelta) { + case -3: + case -2: + case -1: + type = Frame.CHOP_FRAME; + break; + case 0: + type = offsetDelta < 64 ? Frame.SAME_FRAME : Frame.SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = Frame.APPEND_FRAME; + break; default: - stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize); - writeFrameTypes(3, 3 + clocalsSize); - stackMap.putShort(cstackSize); - writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); - } + // Keep the FULL_FRAME type. + break; + } + } else if (nLocalDelta == 0 && nStack == 1) { + type = + offsetDelta < 63 + ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; } - - /** - * Writes some types of the current frame {@link #frame} into the - * StackMapTableAttribute. This method converts types from the format used - * in {@link Label} to the format used in StackMapTable attributes. In - * particular, it converts type table indexes to constant pool indexes. - * - * @param start - * index of the first type in {@link #frame} to write. - * @param end - * index of last type in {@link #frame} to write (exclusive). - */ - private void writeFrameTypes(final int start, final int end) { - for (int i = start; i < end; ++i) { - int t = frame[i]; - int d = t & Frame.DIM; - if (d == 0) { - int v = t & Frame.BASE_VALUE; - switch (t & Frame.BASE_KIND) { - case Frame.OBJECT: - stackMap.putByte(7).putShort( - cw.newClass(cw.typeTable[v].strVal1)); - break; - case Frame.UNINITIALIZED: - stackMap.putByte(8).putShort(cw.typeTable[v].intVal); - break; - default: - stackMap.putByte(v); - } - } else { - StringBuffer buf = new StringBuffer(); - d >>= 28; - while (d-- > 0) { - buf.append('['); - } - if ((t & Frame.BASE_KIND) == Frame.OBJECT) { - buf.append('L'); - buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); - buf.append(';'); - } else { - switch (t & 0xF) { - case 1: - buf.append('I'); - break; - case 2: - buf.append('F'); - break; - case 3: - buf.append('D'); - break; - case 9: - buf.append('Z'); - break; - case 10: - buf.append('B'); - break; - case 11: - buf.append('C'); - break; - case 12: - buf.append('S'); - break; - default: - buf.append('J'); - } - } - stackMap.putByte(7).putShort(cw.newClass(buf.toString())); - } - } + if (type != Frame.FULL_FRAME) { + // Verify if locals are the same as in the previous frame. + int frameIndex = 3; + for (int i = 0; i < previousNlocal && i < nLocal; i++) { + if (currentFrame[frameIndex] != previousFrame[frameIndex]) { + type = Frame.FULL_FRAME; + break; + } + frameIndex++; + } } - - private void writeFrameType(final Object type) { - if (type instanceof String) { - stackMap.putByte(7).putShort(cw.newClass((String) type)); - } else if (type instanceof Integer) { - stackMap.putByte(((Integer) type).intValue()); - } else { - stackMap.putByte(8).putShort(((Label) type).position); - } + switch (type) { + case Frame.SAME_FRAME: + stackMapTableEntries.putByte(offsetDelta); + break; + case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta); + putAbstractTypes(3 + nLocal, 4 + nLocal); + break; + case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMapTableEntries + .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(offsetDelta); + putAbstractTypes(3 + nLocal, 4 + nLocal); + break; + case Frame.SAME_FRAME_EXTENDED: + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta); + break; + case Frame.CHOP_FRAME: + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta); + break; + case Frame.APPEND_FRAME: + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta); + putAbstractTypes(3 + previousNlocal, 3 + nLocal); + break; + case Frame.FULL_FRAME: + default: + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal); + putAbstractTypes(3, 3 + nLocal); + stackMapTableEntries.putShort(nStack); + putAbstractTypes(3 + nLocal, 3 + nLocal + nStack); } - - // ------------------------------------------------------------------------ - // Utility methods: dump bytecode array - // ------------------------------------------------------------------------ - - /** - * Returns the size of the bytecode of this method. - * - * @return the size of the bytecode of this method. - */ - final int getSize() { - if (classReaderOffset != 0) { - return 6 + classReaderLength; - } - if (resize) { - // replaces the temporary jump opcodes introduced by Label.resolve. - if (ClassReader.RESIZE) { - resizeInstructions(); - } else { - throw new RuntimeException("Method code too large!"); - } - } - int size = 8; - if (code.length > 0) { - if (code.length > 65536) { - throw new RuntimeException("Method code too large!"); - } - cw.newUTF8("Code"); - size += 18 + code.length + 8 * handlerCount; - if (localVar != null) { - cw.newUTF8("LocalVariableTable"); - size += 8 + localVar.length; - } - if (localVarType != null) { - cw.newUTF8("LocalVariableTypeTable"); - size += 8 + localVarType.length; - } - if (lineNumber != null) { - cw.newUTF8("LineNumberTable"); - size += 8 + lineNumber.length; - } - if (stackMap != null) { - boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; - cw.newUTF8(zip ? "StackMapTable" : "StackMap"); - size += 8 + stackMap.length; - } - if (cattrs != null) { - size += cattrs.getSize(cw, code.data, code.length, maxStack, - maxLocals); - } - } - if (exceptionCount > 0) { - cw.newUTF8("Exceptions"); - size += 8 + 2 * exceptionCount; - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((cw.version & 0xFFFF) < Opcodes.V1_5 - || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { - cw.newUTF8("Synthetic"); - size += 6; - } - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - cw.newUTF8("Deprecated"); - size += 6; - } - if (ClassReader.SIGNATURES && signature != null) { - cw.newUTF8("Signature"); - cw.newUTF8(signature); - size += 8; - } - if (ClassReader.ANNOTATIONS && annd != null) { - cw.newUTF8("AnnotationDefault"); - size += 6 + annd.length; - } - if (ClassReader.ANNOTATIONS && anns != null) { - cw.newUTF8("RuntimeVisibleAnnotations"); - size += 8 + anns.getSize(); - } - if (ClassReader.ANNOTATIONS && ianns != null) { - cw.newUTF8("RuntimeInvisibleAnnotations"); - size += 8 + ianns.getSize(); - } - if (ClassReader.ANNOTATIONS && panns != null) { - cw.newUTF8("RuntimeVisibleParameterAnnotations"); - size += 7 + 2 * (panns.length - synthetics); - for (int i = panns.length - 1; i >= synthetics; --i) { - size += panns[i] == null ? 0 : panns[i].getSize(); - } - } - if (ClassReader.ANNOTATIONS && ipanns != null) { - cw.newUTF8("RuntimeInvisibleParameterAnnotations"); - size += 7 + 2 * (ipanns.length - synthetics); - for (int i = ipanns.length - 1; i >= synthetics; --i) { - size += ipanns[i] == null ? 0 : ipanns[i].getSize(); - } - } - if (attrs != null) { - size += attrs.getSize(cw, null, 0, -1, -1); - } - return size; - } - - /** - * Puts the bytecode of this method in the given byte vector. - * - * @param out - * the byte vector into which the bytecode of this method must be - * copied. - */ - final void put(final ByteVector out) { - final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; - int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED - | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE - | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); - out.putShort(access & ~mask).putShort(name).putShort(desc); - if (classReaderOffset != 0) { - out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength); - return; - } - int attributeCount = 0; - if (code.length > 0) { - ++attributeCount; - } - if (exceptionCount > 0) { - ++attributeCount; - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((cw.version & 0xFFFF) < Opcodes.V1_5 - || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { - ++attributeCount; - } - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - ++attributeCount; - } - if (ClassReader.SIGNATURES && signature != null) { - ++attributeCount; - } - if (ClassReader.ANNOTATIONS && annd != null) { - ++attributeCount; - } - if (ClassReader.ANNOTATIONS && anns != null) { - ++attributeCount; - } - if (ClassReader.ANNOTATIONS && ianns != null) { - ++attributeCount; - } - if (ClassReader.ANNOTATIONS && panns != null) { - ++attributeCount; - } - if (ClassReader.ANNOTATIONS && ipanns != null) { - ++attributeCount; - } - if (attrs != null) { - attributeCount += attrs.getCount(); - } - out.putShort(attributeCount); - if (code.length > 0) { - int size = 12 + code.length + 8 * handlerCount; - if (localVar != null) { - size += 8 + localVar.length; - } - if (localVarType != null) { - size += 8 + localVarType.length; - } - if (lineNumber != null) { - size += 8 + lineNumber.length; - } - if (stackMap != null) { - size += 8 + stackMap.length; - } - if (cattrs != null) { - size += cattrs.getSize(cw, code.data, code.length, maxStack, - maxLocals); - } - out.putShort(cw.newUTF8("Code")).putInt(size); - out.putShort(maxStack).putShort(maxLocals); - out.putInt(code.length).putByteArray(code.data, 0, code.length); - out.putShort(handlerCount); - if (handlerCount > 0) { - Handler h = firstHandler; - while (h != null) { - out.putShort(h.start.position).putShort(h.end.position) - .putShort(h.handler.position).putShort(h.type); - h = h.next; - } - } - attributeCount = 0; - if (localVar != null) { - ++attributeCount; - } - if (localVarType != null) { - ++attributeCount; - } - if (lineNumber != null) { - ++attributeCount; - } - if (stackMap != null) { - ++attributeCount; - } - if (cattrs != null) { - attributeCount += cattrs.getCount(); - } - out.putShort(attributeCount); - if (localVar != null) { - out.putShort(cw.newUTF8("LocalVariableTable")); - out.putInt(localVar.length + 2).putShort(localVarCount); - out.putByteArray(localVar.data, 0, localVar.length); - } - if (localVarType != null) { - out.putShort(cw.newUTF8("LocalVariableTypeTable")); - out.putInt(localVarType.length + 2).putShort(localVarTypeCount); - out.putByteArray(localVarType.data, 0, localVarType.length); - } - if (lineNumber != null) { - out.putShort(cw.newUTF8("LineNumberTable")); - out.putInt(lineNumber.length + 2).putShort(lineNumberCount); - out.putByteArray(lineNumber.data, 0, lineNumber.length); - } - if (stackMap != null) { - boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; - out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap")); - out.putInt(stackMap.length + 2).putShort(frameCount); - out.putByteArray(stackMap.data, 0, stackMap.length); - } - if (cattrs != null) { - cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out); - } - } - if (exceptionCount > 0) { - out.putShort(cw.newUTF8("Exceptions")).putInt( - 2 * exceptionCount + 2); - out.putShort(exceptionCount); - for (int i = 0; i < exceptionCount; ++i) { - out.putShort(exceptions[i]); - } - } - if ((access & Opcodes.ACC_SYNTHETIC) != 0) { - if ((cw.version & 0xFFFF) < Opcodes.V1_5 - || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { - out.putShort(cw.newUTF8("Synthetic")).putInt(0); - } - } - if ((access & Opcodes.ACC_DEPRECATED) != 0) { - out.putShort(cw.newUTF8("Deprecated")).putInt(0); - } - if (ClassReader.SIGNATURES && signature != null) { - out.putShort(cw.newUTF8("Signature")).putInt(2) - .putShort(cw.newUTF8(signature)); - } - if (ClassReader.ANNOTATIONS && annd != null) { - out.putShort(cw.newUTF8("AnnotationDefault")); - out.putInt(annd.length); - out.putByteArray(annd.data, 0, annd.length); - } - if (ClassReader.ANNOTATIONS && anns != null) { - out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); - anns.put(out); - } - if (ClassReader.ANNOTATIONS && ianns != null) { - out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); - ianns.put(out); - } - if (ClassReader.ANNOTATIONS && panns != null) { - out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations")); - AnnotationWriter.put(panns, synthetics, out); - } - if (ClassReader.ANNOTATIONS && ipanns != null) { - out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations")); - AnnotationWriter.put(ipanns, synthetics, out); - } - if (attrs != null) { - attrs.put(cw, null, 0, -1, -1, out); - } + } + + /** + * Puts some abstract types of {@link #currentFrame} in {@link #stackMapTableEntries} , using the + * JVMS verification_type_info format used in StackMapTable attributes. + * + * @param start index of the first type in {@link #currentFrame} to write. + * @param end index of last type in {@link #currentFrame} to write (exclusive). + */ + private void putAbstractTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + Frame.putAbstractType(symbolTable, currentFrame[i], stackMapTableEntries); } - - // ------------------------------------------------------------------------ - // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) - // ------------------------------------------------------------------------ - - /** - * Resizes and replaces the temporary instructions inserted by - * {@link Label#resolve} for wide forward jumps, while keeping jump offsets - * and instruction addresses consistent. This may require to resize other - * existing instructions, or even to introduce new instructions: for - * example, increasing the size of an instruction by 2 at the middle of a - * method can increases the offset of an IFEQ instruction from 32766 to - * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W - * 32765. This, in turn, may require to increase the size of another jump - * instruction, and so on... All these operations are handled automatically - * by this method. - *

- * This method must be called after all the method that is being built - * has been visited. In particular, the {@link Label Label} objects used - * to construct the method are no longer valid after this method has been - * called. - */ - private void resizeInstructions() { - byte[] b = code.data; // bytecode of the method - int u, v, label; // indexes in b - int i, j; // loop indexes - /* - * 1st step: As explained above, resizing an instruction may require to - * resize another one, which may require to resize yet another one, and - * so on. The first step of the algorithm consists in finding all the - * instructions that need to be resized, without modifying the code. - * This is done by the following "fix point" algorithm: - * - * Parse the code to find the jump instructions whose offset will need - * more than 2 bytes to be stored (the future offset is computed from - * the current offset and from the number of bytes that will be inserted - * or removed between the source and target instructions). For each such - * instruction, adds an entry in (a copy of) the indexes and sizes - * arrays (if this has not already been done in a previous iteration!). - * - * If at least one entry has been added during the previous step, go - * back to the beginning, otherwise stop. - * - * In fact the real algorithm is complicated by the fact that the size - * of TABLESWITCH and LOOKUPSWITCH instructions depends on their - * position in the bytecode (because of padding). In order to ensure the - * convergence of the algorithm, the number of bytes to be added or - * removed from these instructions is over estimated during the previous - * loop, and computed exactly only after the loop is finished (this - * requires another pass to parse the bytecode of the method). - */ - int[] allIndexes = new int[0]; // copy of indexes - int[] allSizes = new int[0]; // copy of sizes - boolean[] resize; // instructions to be resized - int newOffset; // future offset of a jump instruction - - resize = new boolean[code.length]; - - // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done - int state = 3; - do { - if (state == 3) { - state = 2; - } - u = 0; - while (u < b.length) { - int opcode = b[u] & 0xFF; // opcode of current instruction - int insert = 0; // bytes to be added after this instruction - - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - case ClassWriter.IMPLVAR_INSN: - u += 1; - break; - case ClassWriter.LABEL_INSN: - if (opcode > 201) { - // converts temporary opcodes 202 to 217, 218 and - // 219 to IFEQ ... JSR (inclusive), IFNULL and - // IFNONNULL - opcode = opcode < 218 ? opcode - 49 : opcode - 20; - label = u + readUnsignedShort(b, u + 1); - } else { - label = u + readShort(b, u + 1); - } - newOffset = getNewOffset(allIndexes, allSizes, u, label); - if (newOffset < Short.MIN_VALUE - || newOffset > Short.MAX_VALUE) { - if (!resize[u]) { - if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { - // two additional bytes will be required to - // replace this GOTO or JSR instruction with - // a GOTO_W or a JSR_W - insert = 2; - } else { - // five additional bytes will be required to - // replace this IFxxx instruction with - // IFNOTxxx GOTO_W , where IFNOTxxx - // is the "opposite" opcode of IFxxx (i.e., - // IFNE for IFEQ) and where designates - // the instruction just after the GOTO_W. - insert = 5; - } - resize[u] = true; - } - } - u += 3; - break; - case ClassWriter.LABELW_INSN: - u += 5; - break; - case ClassWriter.TABL_INSN: - if (state == 1) { - // true number of bytes to be added (or removed) - // from this instruction = (future number of padding - // bytes - current number of padding byte) - - // previously over estimated variation = - // = ((3 - newOffset%4) - (3 - u%4)) - u%4 - // = (-newOffset%4 + u%4) - u%4 - // = -(newOffset & 3) - newOffset = getNewOffset(allIndexes, allSizes, 0, u); - insert = -(newOffset & 3); - } else if (!resize[u]) { - // over estimation of the number of bytes to be - // added to this instruction = 3 - current number - // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 - insert = u & 3; - resize[u] = true; - } - // skips instruction - u = u + 4 - (u & 3); - u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; - break; - case ClassWriter.LOOK_INSN: - if (state == 1) { - // like TABL_INSN - newOffset = getNewOffset(allIndexes, allSizes, 0, u); - insert = -(newOffset & 3); - } else if (!resize[u]) { - // like TABL_INSN - insert = u & 3; - resize[u] = true; - } - // skips instruction - u = u + 4 - (u & 3); - u += 8 * readInt(b, u + 4) + 8; - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - u += 6; - } else { - u += 4; - } - break; - case ClassWriter.VAR_INSN: - case ClassWriter.SBYTE_INSN: - case ClassWriter.LDC_INSN: - u += 2; - break; - case ClassWriter.SHORT_INSN: - case ClassWriter.LDCW_INSN: - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.TYPE_INSN: - case ClassWriter.IINC_INSN: - u += 3; - break; - case ClassWriter.ITFMETH_INSN: - case ClassWriter.INDYMETH_INSN: - u += 5; - break; - // case ClassWriter.MANA_INSN: - default: - u += 4; - break; - } - if (insert != 0) { - // adds a new (u, insert) entry in the allIndexes and - // allSizes arrays - int[] newIndexes = new int[allIndexes.length + 1]; - int[] newSizes = new int[allSizes.length + 1]; - System.arraycopy(allIndexes, 0, newIndexes, 0, - allIndexes.length); - System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); - newIndexes[allIndexes.length] = u; - newSizes[allSizes.length] = insert; - allIndexes = newIndexes; - allSizes = newSizes; - if (insert > 0) { - state = 3; - } - } - } - if (state < 3) { - --state; - } - } while (state != 0); - - // 2nd step: - // copies the bytecode of the method into a new bytevector, updates the - // offsets, and inserts (or removes) bytes as requested. - - ByteVector newCode = new ByteVector(code.length); - - u = 0; - while (u < code.length) { - int opcode = b[u] & 0xFF; - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - case ClassWriter.IMPLVAR_INSN: - newCode.putByte(opcode); - u += 1; - break; - case ClassWriter.LABEL_INSN: - if (opcode > 201) { - // changes temporary opcodes 202 to 217 (inclusive), 218 - // and 219 to IFEQ ... JSR (inclusive), IFNULL and - // IFNONNULL - opcode = opcode < 218 ? opcode - 49 : opcode - 20; - label = u + readUnsignedShort(b, u + 1); - } else { - label = u + readShort(b, u + 1); - } - newOffset = getNewOffset(allIndexes, allSizes, u, label); - if (resize[u]) { - // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx - // with IFNOTxxx GOTO_W , where IFNOTxxx is - // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) - // and where designates the instruction just after - // the GOTO_W. - if (opcode == Opcodes.GOTO) { - newCode.putByte(200); // GOTO_W - } else if (opcode == Opcodes.JSR) { - newCode.putByte(201); // JSR_W - } else { - newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 - : opcode ^ 1); - newCode.putShort(8); // jump offset - newCode.putByte(200); // GOTO_W - // newOffset now computed from start of GOTO_W - newOffset -= 3; - } - newCode.putInt(newOffset); - } else { - newCode.putByte(opcode); - newCode.putShort(newOffset); - } - u += 3; - break; - case ClassWriter.LABELW_INSN: - label = u + readInt(b, u + 1); - newOffset = getNewOffset(allIndexes, allSizes, u, label); - newCode.putByte(opcode); - newCode.putInt(newOffset); - u += 5; - break; - case ClassWriter.TABL_INSN: - // skips 0 to 3 padding bytes - v = u; - u = u + 4 - (v & 3); - // reads and copies instruction - newCode.putByte(Opcodes.TABLESWITCH); - newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - j = readInt(b, u); - u += 4; - newCode.putInt(j); - j = readInt(b, u) - j + 1; - u += 4; - newCode.putInt(readInt(b, u - 4)); - for (; j > 0; --j) { - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - } - break; - case ClassWriter.LOOK_INSN: - // skips 0 to 3 padding bytes - v = u; - u = u + 4 - (v & 3); - // reads and copies instruction - newCode.putByte(Opcodes.LOOKUPSWITCH); - newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - j = readInt(b, u); - u += 4; - newCode.putInt(j); - for (; j > 0; --j) { - newCode.putInt(readInt(b, u)); - u += 4; - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - } - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - newCode.putByteArray(b, u, 6); - u += 6; - } else { - newCode.putByteArray(b, u, 4); - u += 4; - } - break; - case ClassWriter.VAR_INSN: - case ClassWriter.SBYTE_INSN: - case ClassWriter.LDC_INSN: - newCode.putByteArray(b, u, 2); - u += 2; - break; - case ClassWriter.SHORT_INSN: - case ClassWriter.LDCW_INSN: - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.TYPE_INSN: - case ClassWriter.IINC_INSN: - newCode.putByteArray(b, u, 3); - u += 3; - break; - case ClassWriter.ITFMETH_INSN: - case ClassWriter.INDYMETH_INSN: - newCode.putByteArray(b, u, 5); - u += 5; - break; - // case MANA_INSN: - default: - newCode.putByteArray(b, u, 4); - u += 4; - break; - } - } - - // recomputes the stack map frames - if (frameCount > 0) { - if (compute == FRAMES) { - frameCount = 0; - stackMap = null; - previousFrame = null; - frame = null; - Frame f = new Frame(); - f.owner = labels; - Type[] args = Type.getArgumentTypes(descriptor); - f.initInputFrame(cw, access, args, maxLocals); - visitFrame(f); - Label l = labels; - while (l != null) { - /* - * here we need the original label position. getNewOffset - * must therefore never have been called for this label. - */ - u = l.position - 3; - if ((l.status & Label.STORE) != 0 || (u >= 0 && resize[u])) { - getNewOffset(allIndexes, allSizes, l); - // TODO update offsets in UNINITIALIZED values - visitFrame(l.frame); - } - l = l.successor; - } - } else { - /* - * Resizing an existing stack map frame table is really hard. - * Not only the table must be parsed to update the offets, but - * new frames may be needed for jump instructions that were - * inserted by this method. And updating the offsets or - * inserting frames can change the format of the following - * frames, in case of packed frames. In practice the whole table - * must be recomputed. For this the frames are marked as - * potentially invalid. This will cause the whole class to be - * reread and rewritten with the COMPUTE_FRAMES option (see the - * ClassWriter.toByteArray method). This is not very efficient - * but is much easier and requires much less code than any other - * method I can think of. - */ - cw.invalidFrames = true; - } - } - // updates the exception handler block labels - Handler h = firstHandler; - while (h != null) { - getNewOffset(allIndexes, allSizes, h.start); - getNewOffset(allIndexes, allSizes, h.end); - getNewOffset(allIndexes, allSizes, h.handler); - h = h.next; - } - // updates the instructions addresses in the - // local var and line number tables - for (i = 0; i < 2; ++i) { - ByteVector bv = i == 0 ? localVar : localVarType; - if (bv != null) { - b = bv.data; - u = 0; - while (u < bv.length) { - label = readUnsignedShort(b, u); - newOffset = getNewOffset(allIndexes, allSizes, 0, label); - writeShort(b, u, newOffset); - label += readUnsignedShort(b, u + 2); - newOffset = getNewOffset(allIndexes, allSizes, 0, label) - - newOffset; - writeShort(b, u + 2, newOffset); - u += 10; - } - } - } - if (lineNumber != null) { - b = lineNumber.data; - u = 0; - while (u < lineNumber.length) { - writeShort( - b, - u, - getNewOffset(allIndexes, allSizes, 0, - readUnsignedShort(b, u))); - u += 4; - } - } - // updates the labels of the other attributes - Attribute attr = cattrs; - while (attr != null) { - Label[] labels = attr.getLabels(); - if (labels != null) { - for (i = labels.length - 1; i >= 0; --i) { - getNewOffset(allIndexes, allSizes, labels[i]); - } - } - attr = attr.next; - } - - // replaces old bytecodes with new ones - code = newCode; - } - - /** - * Reads an unsigned short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static int readUnsignedShort(final byte[] b, final int index) { - return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); - } - - /** - * Reads a signed short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static short readShort(final byte[] b, final int index) { - return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); - } - - /** - * Reads a signed int value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static int readInt(final byte[] b, final int index) { - return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) - | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); - } - - /** - * Writes a short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * where the first byte of the short value must be written. - * @param s - * the value to be written in the given byte array. - */ - static void writeShort(final byte[] b, final int index, final int s) { - b[index] = (byte) (s >>> 8); - b[index + 1] = (byte) s; - } - - /** - * Computes the future value of a bytecode offset. - *

- * Note: it is possible to have several entries for the same instruction in - * the indexes and sizes: two entries (index=a,size=b) and - * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b'). - * - * @param indexes - * current positions of the instructions to be resized. Each - * instruction must be designated by the index of its last - * byte, plus one (or, in other words, by the index of the - * first byte of the next instruction). - * @param sizes - * the number of bytes to be added to the above - * instructions. More precisely, for each i < len, - * sizes[i] bytes will be added at the end of the - * instruction designated by indexes[i] or, if - * sizes[i] is negative, the last | - * sizes[i]| bytes of the instruction will be removed - * (the instruction size must not become negative or - * null). - * @param begin - * index of the first byte of the source instruction. - * @param end - * index of the first byte of the target instruction. - * @return the future value of the given bytecode offset. - */ - static int getNewOffset(final int[] indexes, final int[] sizes, - final int begin, final int end) { - int offset = end - begin; - for (int i = 0; i < indexes.length; ++i) { - if (begin < indexes[i] && indexes[i] <= end) { - // forward jump - offset += sizes[i]; - } else if (end < indexes[i] && indexes[i] <= begin) { - // backward jump - offset -= sizes[i]; - } - } - return offset; - } - - /** - * Updates the offset of the given label. - * - * @param indexes - * current positions of the instructions to be resized. Each - * instruction must be designated by the index of its last - * byte, plus one (or, in other words, by the index of the - * first byte of the next instruction). - * @param sizes - * the number of bytes to be added to the above - * instructions. More precisely, for each i < len, - * sizes[i] bytes will be added at the end of the - * instruction designated by indexes[i] or, if - * sizes[i] is negative, the last | - * sizes[i]| bytes of the instruction will be removed - * (the instruction size must not become negative or - * null). - * @param label - * the label whose offset must be updated. - */ - static void getNewOffset(final int[] indexes, final int[] sizes, - final Label label) { - if ((label.status & Label.RESIZED) == 0) { - label.position = getNewOffset(indexes, sizes, 0, label.position); - label.status |= Label.RESIZED; - } + } + + /** + * Puts the given public API frame element type in {@link #stackMapTableEntries} , using the JVMS + * verification_type_info format used in StackMapTable attributes. + * + * @param type a frame element type described using the same format as in {@link + * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or + * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating + * a NEW instruction (for uninitialized types). + */ + private void putFrameType(final Object type) { + if (type instanceof Integer) { + stackMapTableEntries.putByte(((Integer) type).intValue()); + } else if (type instanceof String) { + stackMapTableEntries + .putByte(Frame.ITEM_OBJECT) + .putShort(symbolTable.addConstantClass((String) type).index); + } else { + stackMapTableEntries + .putByte(Frame.ITEM_UNINITIALIZED) + .putShort(((Label) type).bytecodeOffset); + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns whether the attributes of this method can be copied from the attributes of the given + * method (assuming there is no method visitor between the given ClassReader and this + * MethodWriter). This method should only be called just after this MethodWriter has been created, + * and before any content is visited. It returns true if the attributes corresponding to the + * constructor arguments (at most a Signature, an Exception, a Deprecated and a Synthetic + * attribute) are the same as the corresponding attributes in the given method. + * + * @param source the source ClassReader from which the attributes of this method might be copied. + * @param methodInfoOffset the offset in 'source.b' of the method_info JVMS structure from which + * the attributes of this method might be copied. + * @param methodInfoLength the length in 'source.b' of the method_info JVMS structure from which + * the attributes of this method might be copied. + * @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes + * of this method might be copied contains a Synthetic attribute. + * @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes + * of this method might be copied contains a Deprecated attribute. + * @param signatureIndex the constant pool index contained in the Signature attribute of the + * method_info JVMS structure from which the attributes of this method might be copied, or 0. + * @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info + * JVMS structure from which the attributes of this method might be copied, or 0. + * @return whether the attributes of this method can be copied from the attributes of the + * method_info JVMS structure in 'source.b', between 'methodInfoOffset' and 'methodInfoOffset' + * + 'methodInfoLength'. + */ + boolean canCopyMethodAttributes( + final ClassReader source, + final int methodInfoOffset, + final int methodInfoLength, + final boolean hasSyntheticAttribute, + final boolean hasDeprecatedAttribute, + final int signatureIndex, + final int exceptionsOffset) { + if (source != symbolTable.getSource() + || signatureIndex != this.signatureIndex + || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) { + return false; + } + boolean needSyntheticAttribute = + symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0; + if (hasSyntheticAttribute != needSyntheticAttribute) { + return false; + } + if (exceptionsOffset == 0) { + if (numberOfExceptions != 0) { + return false; + } + } else if (source.readUnsignedShort(exceptionsOffset) == numberOfExceptions) { + int currentExceptionOffset = exceptionsOffset + 2; + for (int i = 0; i < numberOfExceptions; ++i) { + if (source.readUnsignedShort(currentExceptionOffset) != exceptionIndexTable[i]) { + return false; + } + currentExceptionOffset += 2; + } + } + // Don't copy the attributes yet, instead store their location in the source class reader so + // they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes + // of the method_info JVMS structure. + this.sourceOffset = methodInfoOffset + 6; + this.sourceLength = methodInfoLength - 6; + return true; + } + + /** + * Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the + * names of the attributes of this method in the constant pool. + * + * @return the size in bytes of the method_info JVMS structure. + */ + int computeMethodInfoSize() { + // If this method_info must be copied from an existing one, the size computation is trivial. + if (sourceOffset != 0) { + // sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index. + return 6 + sourceLength; + } + // 2 bytes each for access_flags, name_index, descriptor_index and attributes_count. + int size = 8; + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (code.length > 0) { + if (code.length > 65535) { + throw new IndexOutOfBoundsException("Method code too large!"); + } + symbolTable.addConstantUtf8(Constants.CODE); + // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack, + // max_locals, code_length and attributes_count, plus the bytecode and the exception table. + size += 16 + code.length + Handler.getExceptionTableSize(firstHandler); + if (stackMapTableEntries != null) { + boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6; + symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap"); + // 6 header bytes and 2 bytes for number_of_entries. + size += 8 + stackMapTableEntries.length; + } + if (lineNumberTable != null) { + symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE); + // 6 header bytes and 2 bytes for line_number_table_length. + size += 8 + lineNumberTable.length; + } + if (localVariableTable != null) { + symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE); + // 6 header bytes and 2 bytes for local_variable_table_length. + size += 8 + localVariableTable.length; + } + if (localVariableTypeTable != null) { + symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE); + // 6 header bytes and 2 bytes for local_variable_type_table_length. + size += 8 + localVariableTypeTable.length; + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + size += + lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + size += + lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (firstCodeAttribute != null) { + size += + firstCodeAttribute.computeAttributesSize( + symbolTable, code.data, code.length, maxStack, maxLocals); + } + } + if (numberOfExceptions > 0) { + symbolTable.addConstantUtf8(Constants.EXCEPTIONS); + size += 8 + 2 * numberOfExceptions; + } + boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + size += 6; + } + if (signatureIndex != 0) { + symbolTable.addConstantUtf8(Constants.SIGNATURE); + size += 8; + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + symbolTable.addConstantUtf8(Constants.DEPRECATED); + size += 6; + } + if (lastRuntimeVisibleAnnotation != null) { + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleParameterAnnotations != null) { + size += + AnnotationWriter.computeParameterAnnotationsSize( + Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, + lastRuntimeVisibleParameterAnnotations, + visibleAnnotableParameterCount == 0 + ? lastRuntimeVisibleParameterAnnotations.length + : visibleAnnotableParameterCount); + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + size += + AnnotationWriter.computeParameterAnnotationsSize( + Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, + lastRuntimeInvisibleParameterAnnotations, + invisibleAnnotableParameterCount == 0 + ? lastRuntimeInvisibleParameterAnnotations.length + : invisibleAnnotableParameterCount); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (defaultValue != null) { + symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT); + size += 6 + defaultValue.length; + } + if (parameters != null) { + symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS); + // 6 header bytes and 1 byte for parameters_count. + size += 7 + parameters.length; + } + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the method_info JVMS structure generated by this MethodWriter into the + * given ByteVector. + * + * @param output where the method_info structure must be put. + */ + void putMethodInfo(final ByteVector output) { + boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; + int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0; + output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); + // If this method_info must be copied from an existing one, copy it now and return early. + if (sourceOffset != 0) { + output.putByteArray(symbolTable.getSource().b, sourceOffset, sourceLength); + return; + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (numberOfExceptions > 0) { + ++attributeCount; + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + ++attributeCount; + } + if (signatureIndex != 0) { + ++attributeCount; + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeVisibleParameterAnnotations != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + ++attributeCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributeCount; + } + if (defaultValue != null) { + ++attributeCount; + } + if (parameters != null) { + ++attributeCount; + } + if (firstAttribute != null) { + attributeCount += firstAttribute.getAttributeCount(); + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + output.putShort(attributeCount); + if (code.length > 0) { + // 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and + // attributes_count, plus the bytecode and the exception table. + int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler); + int codeAttributeCount = 0; + if (stackMapTableEntries != null) { + // 6 header bytes and 2 bytes for number_of_entries. + size += 8 + stackMapTableEntries.length; + ++codeAttributeCount; + } + if (lineNumberTable != null) { + // 6 header bytes and 2 bytes for line_number_table_length. + size += 8 + lineNumberTable.length; + ++codeAttributeCount; + } + if (localVariableTable != null) { + // 6 header bytes and 2 bytes for local_variable_table_length. + size += 8 + localVariableTable.length; + ++codeAttributeCount; + } + if (localVariableTypeTable != null) { + // 6 header bytes and 2 bytes for local_variable_type_table_length. + size += 8 + localVariableTypeTable.length; + ++codeAttributeCount; + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + size += + lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + ++codeAttributeCount; + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + size += + lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + ++codeAttributeCount; + } + if (firstCodeAttribute != null) { + size += + firstCodeAttribute.computeAttributesSize( + symbolTable, code.data, code.length, maxStack, maxLocals); + codeAttributeCount += firstCodeAttribute.getAttributeCount(); + } + output + .putShort(symbolTable.addConstantUtf8(Constants.CODE)) + .putInt(size) + .putShort(maxStack) + .putShort(maxLocals) + .putInt(code.length) + .putByteArray(code.data, 0, code.length); + Handler.putExceptionTable(firstHandler, output); + output.putShort(codeAttributeCount); + if (stackMapTableEntries != null) { + boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6; + output + .putShort( + symbolTable.addConstantUtf8( + useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap")) + .putInt(2 + stackMapTableEntries.length) + .putShort(stackMapTableNumberOfEntries) + .putByteArray(stackMapTableEntries.data, 0, stackMapTableEntries.length); + } + if (lineNumberTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE)) + .putInt(2 + lineNumberTable.length) + .putShort(lineNumberTableLength) + .putByteArray(lineNumberTable.data, 0, lineNumberTable.length); + } + if (localVariableTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE)) + .putInt(2 + localVariableTable.length) + .putShort(localVariableTableLength) + .putByteArray(localVariableTable.data, 0, localVariableTable.length); + } + if (localVariableTypeTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE)) + .putInt(2 + localVariableTypeTable.length) + .putShort(localVariableTypeTableLength) + .putByteArray(localVariableTypeTable.data, 0, localVariableTypeTable.length); + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + lastCodeRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + if (firstCodeAttribute != null) { + firstCodeAttribute.putAttributes( + symbolTable, code.data, code.length, maxStack, maxLocals, output); + } + } + if (numberOfExceptions > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS)) + .putInt(2 + 2 * numberOfExceptions) + .putShort(numberOfExceptions); + for (int exceptionIndex : exceptionIndexTable) { + output.putShort(exceptionIndex); + } + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + if (lastRuntimeVisibleAnnotation != null) { + lastRuntimeVisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleAnnotation != null) { + lastRuntimeInvisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeVisibleParameterAnnotations != null) { + AnnotationWriter.putParameterAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS), + lastRuntimeVisibleParameterAnnotations, + visibleAnnotableParameterCount == 0 + ? lastRuntimeVisibleParameterAnnotations.length + : visibleAnnotableParameterCount, + output); + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + AnnotationWriter.putParameterAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS), + lastRuntimeInvisibleParameterAnnotations, + invisibleAnnotableParameterCount == 0 + ? lastRuntimeInvisibleParameterAnnotations.length + : invisibleAnnotableParameterCount, + output); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + lastRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + lastRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + if (defaultValue != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT)) + .putInt(defaultValue.length) + .putByteArray(defaultValue.data, 0, defaultValue.length); + } + if (parameters != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS)) + .putInt(1 + parameters.length) + .putByte(parametersCount) + .putByteArray(parameters.data, 0, parameters.length); + } + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); } + } + + /** + * Collects the attributes of this method into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + attributePrototypes.addAttributes(firstCodeAttribute); + } } diff --git a/src/jvm/clojure/asm/ModuleVisitor.java b/src/jvm/clojure/asm/ModuleVisitor.java new file mode 100644 index 0000000000..26d2e365cd --- /dev/null +++ b/src/jvm/clojure/asm/ModuleVisitor.java @@ -0,0 +1,175 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * A visitor to visit a Java module. The methods of this class must be called in the following + * order: visitMainClass | ( visitPackage | visitRequire | + * visitExport | visitOpen | visitUse | visitProvide )* + * visitEnd. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public abstract class ModuleVisitor { + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + */ + protected final int api; + + /** The module visitor to which this visitor must delegate method calls. May be null. */ + protected ModuleVisitor mv; + + /** + * Constructs a new {@link ModuleVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} + * or {@link Opcodes#ASM7_EXPERIMENTAL}. + */ + public ModuleVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ModuleVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} + * or {@link Opcodes#ASM7_EXPERIMENTAL}. + * @param moduleVisitor the module visitor to which this visitor must delegate method calls. May + * be null. + */ + public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { + if (api != Opcodes.ASM6 && api != Opcodes.ASM7_EXPERIMENTAL) { + throw new IllegalArgumentException(); + } + this.api = api; + this.mv = moduleVisitor; + } + + /** + * Visit the main class of the current module. + * + * @param mainClass the internal name of the main class of the current module. + */ + public void visitMainClass(final String mainClass) { + if (mv != null) { + mv.visitMainClass(mainClass); + } + } + + /** + * Visit a package of the current module. + * + * @param packaze the internal name of a package. + */ + public void visitPackage(final String packaze) { + if (mv != null) { + mv.visitPackage(packaze); + } + } + + /** + * Visits a dependence of the current module. + * + * @param module the fully qualified name (using dots) of the dependence. + * @param access the access flag of the dependence among {@code ACC_TRANSITIVE}, {@code + * ACC_STATIC_PHASE}, {@code ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param version the module version at compile time, or null. + */ + public void visitRequire(final String module, final int access, final String version) { + if (mv != null) { + mv.visitRequire(module, access, version); + } + } + + /** + * Visit an exported package of the current module. + * + * @param packaze the internal name of the exported package. + * @param access the access flag of the exported package, valid values are among {@code + * ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param modules the fully qualified names (using dots) of the modules that can access the public + * classes of the exported package, or null. + */ + public void visitExport(final String packaze, final int access, final String... modules) { + if (mv != null) { + mv.visitExport(packaze, access, modules); + } + } + + /** + * Visit an open package of the current module. + * + * @param packaze the internal name of the opened package. + * @param access the access flag of the opened package, valid values are among {@code + * ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param modules the fully qualified names (using dots) of the modules that can use deep + * reflection to the classes of the open package, or null. + */ + public void visitOpen(final String packaze, final int access, final String... modules) { + if (mv != null) { + mv.visitOpen(packaze, access, modules); + } + } + + /** + * Visit a service used by the current module. The name must be the internal name of an interface + * or a class. + * + * @param service the internal name of the service. + */ + public void visitUse(final String service) { + if (mv != null) { + mv.visitUse(service); + } + } + + /** + * Visit an implementation of a service. + * + * @param service the internal name of the service. + * @param providers the internal names of the implementations of the service (there is at least + * one provider). + */ + public void visitProvide(final String service, final String... providers) { + if (mv != null) { + mv.visitProvide(service, providers); + } + } + + /** + * Visits the end of the module. This method, which is the last one to be called, is used to + * inform the visitor that everything have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/src/jvm/clojure/asm/ModuleWriter.java b/src/jvm/clojure/asm/ModuleWriter.java new file mode 100644 index 0000000000..7eb7abda91 --- /dev/null +++ b/src/jvm/clojure/asm/ModuleWriter.java @@ -0,0 +1,253 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * A {@link ModuleVisitor} that generates the corresponding Module, ModulePackages and + * ModuleMainClass attributes, as defined in the Java Virtual Machine Specification (JVMS). + * + * @see JVMS + * 4.7.25 + * @see JVMS + * 4.7.26 + * @see JVMS + * 4.7.27 + * @author Remi Forax + * @author Eric Bruneton + */ +final class ModuleWriter extends ModuleVisitor { + + /** Where the constants used in this AnnotationWriter must be stored. */ + private final SymbolTable symbolTable; + + /** The module_name_index field of the JVMS Module attribute. */ + private final int moduleNameIndex; + + /** The module_flags field of the JVMS Module attribute. */ + private final int moduleFlags; + + /** The module_version_index field of the JVMS Module attribute. */ + private final int moduleVersionIndex; + + /** The requires_count field of the JVMS Module attribute. */ + private int requiresCount; + + /** The binary content of the 'requires' array of the JVMS Module attribute. */ + private final ByteVector requires; + + /** The exports_count field of the JVMS Module attribute. */ + private int exportsCount; + + /** The binary content of the 'exports' array of the JVMS Module attribute. */ + private final ByteVector exports; + + /** The opens_count field of the JVMS Module attribute. */ + private int opensCount; + + /** The binary content of the 'opens' array of the JVMS Module attribute. */ + private final ByteVector opens; + + /** The uses_count field of the JVMS Module attribute. */ + private int usesCount; + + /** The binary content of the 'uses_index' array of the JVMS Module attribute. */ + private final ByteVector usesIndex; + + /** The provides_count field of the JVMS Module attribute. */ + private int providesCount; + + /** The binary content of the 'provides' array of the JVMS Module attribute. */ + private final ByteVector provides; + + /** The provides_count field of the JVMS ModulePackages attribute. */ + private int packageCount; + + /** The binary content of the 'package_index' array of the JVMS ModulePackages attribute. */ + private final ByteVector packageIndex; + + /** The main_class_index field of the JVMS ModuleMainClass attribute, or 0. */ + private int mainClassIndex; + + ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) { + super(Opcodes.ASM6); + this.symbolTable = symbolTable; + this.moduleNameIndex = name; + this.moduleFlags = access; + this.moduleVersionIndex = version; + this.requires = new ByteVector(); + this.exports = new ByteVector(); + this.opens = new ByteVector(); + this.usesIndex = new ByteVector(); + this.provides = new ByteVector(); + this.packageIndex = new ByteVector(); + } + + @Override + public void visitMainClass(final String mainClass) { + this.mainClassIndex = symbolTable.addConstantClass(mainClass).index; + } + + @Override + public void visitPackage(final String packaze) { + packageIndex.putShort(symbolTable.addConstantPackage(packaze).index); + packageCount++; + } + + @Override + public void visitRequire(final String module, final int access, final String version) { + requires + .putShort(symbolTable.addConstantModule(module).index) + .putShort(access) + .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version)); + requiresCount++; + } + + @Override + public void visitExport(final String packaze, final int access, final String... modules) { + exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access); + if (modules == null) { + exports.putShort(0); + } else { + exports.putShort(modules.length); + for (String module : modules) { + exports.putShort(symbolTable.addConstantModule(module).index); + } + } + exportsCount++; + } + + @Override + public void visitOpen(final String packaze, final int access, final String... modules) { + opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access); + if (modules == null) { + opens.putShort(0); + } else { + opens.putShort(modules.length); + for (String module : modules) { + opens.putShort(symbolTable.addConstantModule(module).index); + } + } + opensCount++; + } + + @Override + public void visitUse(final String service) { + usesIndex.putShort(symbolTable.addConstantClass(service).index); + usesCount++; + } + + @Override + public void visitProvide(final String service, final String... providers) { + provides.putShort(symbolTable.addConstantClass(service).index); + provides.putShort(providers.length); + for (String provider : providers) { + provides.putShort(symbolTable.addConstantClass(provider).index); + } + providesCount++; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + /** + * Returns the number of Module, ModulePackages and ModuleMainClass attributes generated by this + * ModuleWriter. + * + * @return the number of Module, ModulePackages and ModuleMainClass attributes (between 1 and 3). + */ + int getAttributeCount() { + return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0); + } + + /** + * Returns the size of the Module, ModulePackages and ModuleMainClass attributes generated by this + * ModuleWriter. Also add the names of these attributes in the constant pool. + * + * @return the size in bytes of the Module, ModulePackages and ModuleMainClass attributes. + */ + int computeAttributesSize() { + symbolTable.addConstantUtf8(Constants.MODULE); + // 6 attribute header bytes, 6 bytes for name, flags and version, and 5 * 2 bytes for counts. + int size = + 22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length; + if (packageCount > 0) { + symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES); + // 6 attribute header bytes, and 2 bytes for package_count. + size += 8 + packageIndex.length; + } + if (mainClassIndex > 0) { + symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS); + // 6 attribute header bytes, and 2 bytes for main_class_index. + size += 8; + } + return size; + } + + /** + * Puts the Module, ModulePackages and ModuleMainClass attributes generated by this ModuleWriter + * in the given ByteVector. + * + * @param output where the attributes must be put. + */ + void putAttributes(final ByteVector output) { + // 6 bytes for name, flags and version, and 5 * 2 bytes for counts. + int moduleAttributeLength = + 16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length; + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE)) + .putInt(moduleAttributeLength) + .putShort(moduleNameIndex) + .putShort(moduleFlags) + .putShort(moduleVersionIndex) + .putShort(requiresCount) + .putByteArray(requires.data, 0, requires.length) + .putShort(exportsCount) + .putByteArray(exports.data, 0, exports.length) + .putShort(opensCount) + .putByteArray(opens.data, 0, opens.length) + .putShort(usesCount) + .putByteArray(usesIndex.data, 0, usesIndex.length) + .putShort(providesCount) + .putByteArray(provides.data, 0, provides.length); + if (packageCount > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES)) + .putInt(2 + packageIndex.length) + .putShort(packageCount) + .putByteArray(packageIndex.data, 0, packageIndex.length); + } + if (mainClassIndex > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS)) + .putInt(2) + .putShort(mainClassIndex); + } + } +} diff --git a/src/jvm/clojure/asm/Opcodes.java b/src/jvm/clojure/asm/Opcodes.java index b1268072f8..5694eb3aa0 100644 --- a/src/jvm/clojure/asm/Opcodes.java +++ b/src/jvm/clojure/asm/Opcodes.java @@ -1,358 +1,347 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; /** - * Defines the JVM opcodes, access flags and array type codes. This interface - * does not define all the JVM opcodes because some opcodes are automatically - * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced - * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n - * opcodes are therefore not defined in this interface. Likewise for LDC, - * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and - * JSR_W. + * The JVM opcodes, access flags and array type codes. This interface does not define all the JVM + * opcodes because some opcodes are automatically handled. For example, the xLOAD and xSTORE opcodes + * are automatically replaced by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and + * xSTORE_n opcodes are therefore not defined in this interface. Likewise for LDC, automatically + * replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and JSR_W. * + * @see JVMS 6 * @author Eric Bruneton * @author Eugene Kuleshov */ public interface Opcodes { - // ASM API versions + // ASM API versions. + + int ASM4 = 4 << 16 | 0 << 8; + int ASM5 = 5 << 16 | 0 << 8; + int ASM6 = 6 << 16 | 0 << 8; + + /** + * Experimental, use at your own risk. This field will be renamed when it becomes stable, this + * will break existing code using it. + * + * @deprecated This API is experimental. + */ + @Deprecated int ASM7_EXPERIMENTAL = 1 << 24 | 7 << 16 | 0 << 8; + + // Java ClassFile versions (the minor version is stored in the 16 most + // significant bits, and the + // major version in the 16 least significant bits). - int ASM4 = 4 << 16 | 0 << 8 | 0; + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + int V1_8 = 0 << 16 | 52; + int V9 = 0 << 16 | 53; + int V10 = 0 << 16 | 54; + int V11 = 0 << 16 | 55; - // versions + /** + * Version flag indicating that the class is using 'preview' features. + * + *

{@code version & V_PREVIEW_EXPERIMENTAL == V_PREVIEW_EXPERIMENTAL} tests if a version is + * flagged with {@code V_PREVIEW_EXPERIMENTAL}. + * + * @deprecated This API is experimental. + */ + @Deprecated int V_PREVIEW_EXPERIMENTAL = 0xFFFF0000; - int V1_1 = 3 << 16 | 45; - int V1_2 = 0 << 16 | 46; - int V1_3 = 0 << 16 | 47; - int V1_4 = 0 << 16 | 48; - int V1_5 = 0 << 16 | 49; - int V1_6 = 0 << 16 | 50; - int V1_7 = 0 << 16 | 51; + // Access flags values, defined in + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.5-200-A.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.6-200-A.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.25 - // access flags + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method, parameter + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_OPEN = 0x0020; // module + int ACC_TRANSITIVE = 0x0020; // module requires + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_STATIC_PHASE = 0x0040; // module requires + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module * + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + int ACC_MANDATED = 0x8000; // parameter, module, module * + int ACC_MODULE = 0x8000; // class - int ACC_PUBLIC = 0x0001; // class, field, method - int ACC_PRIVATE = 0x0002; // class, field, method - int ACC_PROTECTED = 0x0004; // class, field, method - int ACC_STATIC = 0x0008; // field, method - int ACC_FINAL = 0x0010; // class, field, method - int ACC_SUPER = 0x0020; // class - int ACC_SYNCHRONIZED = 0x0020; // method - int ACC_VOLATILE = 0x0040; // field - int ACC_BRIDGE = 0x0040; // method - int ACC_VARARGS = 0x0080; // method - int ACC_TRANSIENT = 0x0080; // field - int ACC_NATIVE = 0x0100; // method - int ACC_INTERFACE = 0x0200; // class - int ACC_ABSTRACT = 0x0400; // class, method - int ACC_STRICT = 0x0800; // method - int ACC_SYNTHETIC = 0x1000; // class, field, method - int ACC_ANNOTATION = 0x2000; // class - int ACC_ENUM = 0x4000; // class(?) field inner + // ASM specific access flags. + // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard + // access flags, and also to make sure that these flags are automatically filtered out when + // written in class files (because access flags are stored using 16 bits only). - // ASM specific pseudo access flags + int ACC_DEPRECATED = 0x20000; // class, field, method - int ACC_DEPRECATED = 0x20000; // class, field, method + // Possible values for the type operand of the NEWARRAY instruction. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.newarray. - // types for NEWARRAY + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; - int T_BOOLEAN = 4; - int T_CHAR = 5; - int T_FLOAT = 6; - int T_DOUBLE = 7; - int T_BYTE = 8; - int T_SHORT = 9; - int T_INT = 10; - int T_LONG = 11; + // Possible values for the reference_kind field of CONSTANT_MethodHandle_info structures. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4.8. - // tags for Handle + int H_GETFIELD = 1; + int H_GETSTATIC = 2; + int H_PUTFIELD = 3; + int H_PUTSTATIC = 4; + int H_INVOKEVIRTUAL = 5; + int H_INVOKESTATIC = 6; + int H_INVOKESPECIAL = 7; + int H_NEWINVOKESPECIAL = 8; + int H_INVOKEINTERFACE = 9; - int H_GETFIELD = 1; - int H_GETSTATIC = 2; - int H_PUTFIELD = 3; - int H_PUTSTATIC = 4; - int H_INVOKEVIRTUAL = 5; - int H_INVOKESTATIC = 6; - int H_INVOKESPECIAL = 7; - int H_NEWINVOKESPECIAL = 8; - int H_INVOKEINTERFACE = 9; + // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}. - // stack map frame types + /** An expanded frame. See {@link ClassReader#EXPAND_FRAMES}. */ + int F_NEW = -1; - /** - * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}. - */ - int F_NEW = -1; + /** A compressed frame with complete frame data. */ + int F_FULL = 0; - /** - * Represents a compressed frame with complete frame data. - */ - int F_FULL = 0; + /** + * A compressed frame where locals are the same as the locals in the previous frame, except that + * additional 1-3 locals are defined, and with an empty stack. + */ + int F_APPEND = 1; - /** - * Represents a compressed frame where locals are the same as the locals in - * the previous frame, except that additional 1-3 locals are defined, and - * with an empty stack. - */ - int F_APPEND = 1; + /** + * A compressed frame where locals are the same as the locals in the previous frame, except that + * the last 1-3 locals are absent and with an empty stack. + */ + int F_CHOP = 2; - /** - * Represents a compressed frame where locals are the same as the locals in - * the previous frame, except that the last 1-3 locals are absent and with - * an empty stack. - */ - int F_CHOP = 2; + /** + * A compressed frame with exactly the same locals as the previous frame and with an empty stack. + */ + int F_SAME = 3; - /** - * Represents a compressed frame with exactly the same locals as the - * previous frame and with an empty stack. - */ - int F_SAME = 3; + /** + * A compressed frame with exactly the same locals as the previous frame and with a single value + * on the stack. + */ + int F_SAME1 = 4; - /** - * Represents a compressed frame with exactly the same locals as the - * previous frame and with a single value on the stack. - */ - int F_SAME1 = 4; + // Standard stack map frame element types, used in {@link ClassVisitor#visitFrame}. - Integer TOP = new Integer(0); - Integer INTEGER = new Integer(1); - Integer FLOAT = new Integer(2); - Integer DOUBLE = new Integer(3); - Integer LONG = new Integer(4); - Integer NULL = new Integer(5); - Integer UNINITIALIZED_THIS = new Integer(6); + Integer TOP = Frame.ITEM_TOP; + Integer INTEGER = Frame.ITEM_INTEGER; + Integer FLOAT = Frame.ITEM_FLOAT; + Integer DOUBLE = Frame.ITEM_DOUBLE; + Integer LONG = Frame.ITEM_LONG; + Integer NULL = Frame.ITEM_NULL; + Integer UNINITIALIZED_THIS = Frame.ITEM_UNINITIALIZED_THIS; - // opcodes // visit method (- = idem) + // The JVM opcode values (with the MethodVisitor method name used to visit them in comment, and + // where '-' means 'same method name as on the previous line'). + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html. - int NOP = 0; // visitInsn - int ACONST_NULL = 1; // - - int ICONST_M1 = 2; // - - int ICONST_0 = 3; // - - int ICONST_1 = 4; // - - int ICONST_2 = 5; // - - int ICONST_3 = 6; // - - int ICONST_4 = 7; // - - int ICONST_5 = 8; // - - int LCONST_0 = 9; // - - int LCONST_1 = 10; // - - int FCONST_0 = 11; // - - int FCONST_1 = 12; // - - int FCONST_2 = 13; // - - int DCONST_0 = 14; // - - int DCONST_1 = 15; // - - int BIPUSH = 16; // visitIntInsn - int SIPUSH = 17; // - - int LDC = 18; // visitLdcInsn - // int LDC_W = 19; // - - // int LDC2_W = 20; // - - int ILOAD = 21; // visitVarInsn - int LLOAD = 22; // - - int FLOAD = 23; // - - int DLOAD = 24; // - - int ALOAD = 25; // - - // int ILOAD_0 = 26; // - - // int ILOAD_1 = 27; // - - // int ILOAD_2 = 28; // - - // int ILOAD_3 = 29; // - - // int LLOAD_0 = 30; // - - // int LLOAD_1 = 31; // - - // int LLOAD_2 = 32; // - - // int LLOAD_3 = 33; // - - // int FLOAD_0 = 34; // - - // int FLOAD_1 = 35; // - - // int FLOAD_2 = 36; // - - // int FLOAD_3 = 37; // - - // int DLOAD_0 = 38; // - - // int DLOAD_1 = 39; // - - // int DLOAD_2 = 40; // - - // int DLOAD_3 = 41; // - - // int ALOAD_0 = 42; // - - // int ALOAD_1 = 43; // - - // int ALOAD_2 = 44; // - - // int ALOAD_3 = 45; // - - int IALOAD = 46; // visitInsn - int LALOAD = 47; // - - int FALOAD = 48; // - - int DALOAD = 49; // - - int AALOAD = 50; // - - int BALOAD = 51; // - - int CALOAD = 52; // - - int SALOAD = 53; // - - int ISTORE = 54; // visitVarInsn - int LSTORE = 55; // - - int FSTORE = 56; // - - int DSTORE = 57; // - - int ASTORE = 58; // - - // int ISTORE_0 = 59; // - - // int ISTORE_1 = 60; // - - // int ISTORE_2 = 61; // - - // int ISTORE_3 = 62; // - - // int LSTORE_0 = 63; // - - // int LSTORE_1 = 64; // - - // int LSTORE_2 = 65; // - - // int LSTORE_3 = 66; // - - // int FSTORE_0 = 67; // - - // int FSTORE_1 = 68; // - - // int FSTORE_2 = 69; // - - // int FSTORE_3 = 70; // - - // int DSTORE_0 = 71; // - - // int DSTORE_1 = 72; // - - // int DSTORE_2 = 73; // - - // int DSTORE_3 = 74; // - - // int ASTORE_0 = 75; // - - // int ASTORE_1 = 76; // - - // int ASTORE_2 = 77; // - - // int ASTORE_3 = 78; // - - int IASTORE = 79; // visitInsn - int LASTORE = 80; // - - int FASTORE = 81; // - - int DASTORE = 82; // - - int AASTORE = 83; // - - int BASTORE = 84; // - - int CASTORE = 85; // - - int SASTORE = 86; // - - int POP = 87; // - - int POP2 = 88; // - - int DUP = 89; // - - int DUP_X1 = 90; // - - int DUP_X2 = 91; // - - int DUP2 = 92; // - - int DUP2_X1 = 93; // - - int DUP2_X2 = 94; // - - int SWAP = 95; // - - int IADD = 96; // - - int LADD = 97; // - - int FADD = 98; // - - int DADD = 99; // - - int ISUB = 100; // - - int LSUB = 101; // - - int FSUB = 102; // - - int DSUB = 103; // - - int IMUL = 104; // - - int LMUL = 105; // - - int FMUL = 106; // - - int DMUL = 107; // - - int IDIV = 108; // - - int LDIV = 109; // - - int FDIV = 110; // - - int DDIV = 111; // - - int IREM = 112; // - - int LREM = 113; // - - int FREM = 114; // - - int DREM = 115; // - - int INEG = 116; // - - int LNEG = 117; // - - int FNEG = 118; // - - int DNEG = 119; // - - int ISHL = 120; // - - int LSHL = 121; // - - int ISHR = 122; // - - int LSHR = 123; // - - int IUSHR = 124; // - - int LUSHR = 125; // - - int IAND = 126; // - - int LAND = 127; // - - int IOR = 128; // - - int LOR = 129; // - - int IXOR = 130; // - - int LXOR = 131; // - - int IINC = 132; // visitIincInsn - int I2L = 133; // visitInsn - int I2F = 134; // - - int I2D = 135; // - - int L2I = 136; // - - int L2F = 137; // - - int L2D = 138; // - - int F2I = 139; // - - int F2L = 140; // - - int F2D = 141; // - - int D2I = 142; // - - int D2L = 143; // - - int D2F = 144; // - - int I2B = 145; // - - int I2C = 146; // - - int I2S = 147; // - - int LCMP = 148; // - - int FCMPL = 149; // - - int FCMPG = 150; // - - int DCMPL = 151; // - - int DCMPG = 152; // - - int IFEQ = 153; // visitJumpInsn - int IFNE = 154; // - - int IFLT = 155; // - - int IFGE = 156; // - - int IFGT = 157; // - - int IFLE = 158; // - - int IF_ICMPEQ = 159; // - - int IF_ICMPNE = 160; // - - int IF_ICMPLT = 161; // - - int IF_ICMPGE = 162; // - - int IF_ICMPGT = 163; // - - int IF_ICMPLE = 164; // - - int IF_ACMPEQ = 165; // - - int IF_ACMPNE = 166; // - - int GOTO = 167; // - - int JSR = 168; // - - int RET = 169; // visitVarInsn - int TABLESWITCH = 170; // visiTableSwitchInsn - int LOOKUPSWITCH = 171; // visitLookupSwitch - int IRETURN = 172; // visitInsn - int LRETURN = 173; // - - int FRETURN = 174; // - - int DRETURN = 175; // - - int ARETURN = 176; // - - int RETURN = 177; // - - int GETSTATIC = 178; // visitFieldInsn - int PUTSTATIC = 179; // - - int GETFIELD = 180; // - - int PUTFIELD = 181; // - - int INVOKEVIRTUAL = 182; // visitMethodInsn - int INVOKESPECIAL = 183; // - - int INVOKESTATIC = 184; // - - int INVOKEINTERFACE = 185; // - - int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn - int NEW = 187; // visitTypeInsn - int NEWARRAY = 188; // visitIntInsn - int ANEWARRAY = 189; // visitTypeInsn - int ARRAYLENGTH = 190; // visitInsn - int ATHROW = 191; // - - int CHECKCAST = 192; // visitTypeInsn - int INSTANCEOF = 193; // - - int MONITORENTER = 194; // visitInsn - int MONITOREXIT = 195; // - - // int WIDE = 196; // NOT VISITED - int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn - int IFNULL = 198; // visitJumpInsn - int IFNONNULL = 199; // - - // int GOTO_W = 200; // - - // int JSR_W = 201; // - + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - } diff --git a/src/jvm/clojure/asm/Symbol.java b/src/jvm/clojure/asm/Symbol.java new file mode 100644 index 0000000000..aa56a4d022 --- /dev/null +++ b/src/jvm/clojure/asm/Symbol.java @@ -0,0 +1,240 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * An entry of the constant pool, of the BootstrapMethods attribute, or of the (ASM specific) type + * table of a class. + * + * @see JVMS + * 4.4 + * @see JVMS + * 4.7.23 + * @author Eric Bruneton + */ +abstract class Symbol { + + // Tag values for the constant pool entries (using the same order as in the JVMS). + + /** The tag value of CONSTANT_Class_info JVMS structures. */ + static final int CONSTANT_CLASS_TAG = 7; + + /** The tag value of CONSTANT_Fieldref_info JVMS structures. */ + static final int CONSTANT_FIELDREF_TAG = 9; + + /** The tag value of CONSTANT_Methodref_info JVMS structures. */ + static final int CONSTANT_METHODREF_TAG = 10; + + /** The tag value of CONSTANT_InterfaceMethodref_info JVMS structures. */ + static final int CONSTANT_INTERFACE_METHODREF_TAG = 11; + + /** The tag value of CONSTANT_String_info JVMS structures. */ + static final int CONSTANT_STRING_TAG = 8; + + /** The tag value of CONSTANT_Integer_info JVMS structures. */ + static final int CONSTANT_INTEGER_TAG = 3; + + /** The tag value of CONSTANT_Float_info JVMS structures. */ + static final int CONSTANT_FLOAT_TAG = 4; + + /** The tag value of CONSTANT_Long_info JVMS structures. */ + static final int CONSTANT_LONG_TAG = 5; + + /** The tag value of CONSTANT_Double_info JVMS structures. */ + static final int CONSTANT_DOUBLE_TAG = 6; + + /** The tag value of CONSTANT_NameAndType_info JVMS structures. */ + static final int CONSTANT_NAME_AND_TYPE_TAG = 12; + + /** The tag value of CONSTANT_Utf8_info JVMS structures. */ + static final int CONSTANT_UTF8_TAG = 1; + + /** The tag value of CONSTANT_MethodHandle_info JVMS structures. */ + static final int CONSTANT_METHOD_HANDLE_TAG = 15; + + /** The tag value of CONSTANT_MethodType_info JVMS structures. */ + static final int CONSTANT_METHOD_TYPE_TAG = 16; + + /** The tag value of CONSTANT_Dynamic_info JVMS structures. */ + static final int CONSTANT_DYNAMIC_TAG = 17; + + /** The tag value of CONSTANT_InvokeDynamic_info JVMS structures. */ + static final int CONSTANT_INVOKE_DYNAMIC_TAG = 18; + + /** The tag value of CONSTANT_Module_info JVMS structures. */ + static final int CONSTANT_MODULE_TAG = 19; + + /** The tag value of CONSTANT_Package_info JVMS structures. */ + static final int CONSTANT_PACKAGE_TAG = 20; + + // Tag values for the BootstrapMethods attribute entries (ASM specific tag). + + /** The tag value of the BootstrapMethods attribute entries. */ + static final int BOOTSTRAP_METHOD_TAG = 64; + + // Tag values for the type table entries (ASM specific tags). + + /** The tag value of a normal type entry in the (ASM specific) type table of a class. */ + static final int TYPE_TAG = 128; + + /** + * The tag value of an {@link Frame#ITEM_UNINITIALIZED} type entry in the type table of a class. + */ + static final int UNINITIALIZED_TYPE_TAG = 129; + + /** The tag value of a merged type entry in the (ASM specific) type table of a class. */ + static final int MERGED_TYPE_TAG = 130; + + // Instance fields. + + /** + * The index of this symbol in the constant pool, in the BootstrapMethods attribute, or in the + * (ASM specific) type table of a class (depending on the {@link #tag} value). + */ + final int index; + + /** + * A tag indicating the type of this symbol. Must be one of the static tag values defined in this + * class. + */ + final int tag; + + /** + * The internal name of the owner class of this symbol. Only used for {@link + * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link + * #CONSTANT_INTERFACE_METHODREF_TAG}, and {@link #CONSTANT_METHOD_HANDLE_TAG} symbols. + */ + final String owner; + + /** + * The name of the class field or method corresponding to this symbol. Only used for {@link + * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link + * #CONSTANT_INTERFACE_METHODREF_TAG}, {@link #CONSTANT_NAME_AND_TYPE_TAG}, {@link + * #CONSTANT_METHOD_HANDLE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols. + */ + final String name; + + /** + * The string value of this symbol. This is: + * + *

    + *
  • a field or method descriptor for {@link #CONSTANT_FIELDREF_TAG}, {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG}, {@link + * #CONSTANT_NAME_AND_TYPE_TAG}, {@link #CONSTANT_METHOD_HANDLE_TAG}, {@link + * #CONSTANT_METHOD_TYPE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • an arbitrary string for {@link #CONSTANT_UTF8_TAG} and {@link #CONSTANT_STRING_TAG} + * symbols, + *
  • an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG} and {@link + * #UNINITIALIZED_TYPE_TAG} symbols, + *
  • null for the other types of symbol. + *
+ */ + final String value; + + /** + * The numeric value of this symbol. This is: + * + *
    + *
  • the symbol's value for {@link #CONSTANT_INTEGER_TAG},{@link #CONSTANT_FLOAT_TAG}, {@link + * #CONSTANT_LONG_TAG}, {@link #CONSTANT_DOUBLE_TAG}, + *
  • the CONSTANT_MethodHandle_info reference_kind field value for {@link + * #CONSTANT_METHOD_HANDLE_TAG} symbols, + *
  • the CONSTANT_InvokeDynamic_info bootstrap_method_attr_index field value for {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • the offset of a bootstrap method in the BootstrapMethods boostrap_methods array, for + * {@link #CONSTANT_DYNAMIC_TAG} or {@link #BOOTSTRAP_METHOD_TAG} symbols, + *
  • the bytecode offset of the NEW instruction that created an {@link + * Frame#ITEM_UNINITIALIZED} type for {@link #UNINITIALIZED_TYPE_TAG} symbols, + *
  • the indices (in the class' type table) of two {@link #TYPE_TAG} source types for {@link + * #MERGED_TYPE_TAG} symbols, + *
  • 0 for the other types of symbol. + *
+ */ + final long data; + + /** + * Additional information about this symbol, generally computed lazily. Warning: the value of + * this field is ignored when comparing Symbol instances (to avoid duplicate entries in a + * SymbolTable). Therefore, this field should only contain data that can be computed from the + * other fields of this class. It contains: + * + *
    + *
  • the {@link Type#getArgumentsAndReturnSizes} of the symbol's method descriptor for {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • the index in the InnerClasses_attribute 'classes' array (plus one) corresponding to this + * class, for {@link #CONSTANT_CLASS_TAG} symbols, + *
  • the index (in the class' type table) of the merged type of the two source types for + * {@link #MERGED_TYPE_TAG} symbols, + *
  • 0 for the other types of symbol, or if this field has not been computed yet. + *
+ */ + int info; + + /** + * Constructs a new Symbol. This constructor can't be used directly because the Symbol class is + * abstract. Instead, use the factory methods of the {@link SymbolTable} class. + * + * @param index the symbol index in the constant pool, in the BootstrapMethods attribute, or in + * the (ASM specific) type table of a class (depending on 'tag'). + * @param tag the symbol type. Must be one of the static tag values defined in this class. + * @param owner The internal name of the symbol's owner class. Maybe null. + * @param name The name of the symbol's corresponding class field or method. Maybe null. + * @param value The string value of this symbol. Maybe null. + * @param data The numeric value of this symbol. + */ + Symbol( + final int index, + final int tag, + final String owner, + final String name, + final String value, + final long data) { + this.index = index; + this.tag = tag; + this.owner = owner; + this.name = name; + this.value = value; + this.data = data; + } + + /** + * @return the result {@link Type#getArgumentsAndReturnSizes} on {@link #value} (memoized in + * {@link #info} for efficiency). This should only be used for {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols. + */ + int getArgumentsAndReturnSizes() { + if (info == 0) { + info = Type.getArgumentsAndReturnSizes(value); + } + return info; + } +} diff --git a/src/jvm/clojure/asm/SymbolTable.java b/src/jvm/clojure/asm/SymbolTable.java new file mode 100644 index 0000000000..30caf77af8 --- /dev/null +++ b/src/jvm/clojure/asm/SymbolTable.java @@ -0,0 +1,1277 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type + * table entries of a class. + * + * @see JVMS + * 4.4 + * @see JVMS + * 4.7.23 + * @author Eric Bruneton + */ +final class SymbolTable { + + /** + * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields + * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid + * duplicate symbols). See {@link #entries}. + * + * @author Eric Bruneton + */ + private static class Entry extends Symbol { + + /** The hash code of this entry. */ + final int hashCode; + + /** + * Another entry (and so on recursively) having the same hash code (modulo the size of {@link + * #entries}) as this one. + */ + Entry next; + + Entry( + final int index, + final int tag, + final String owner, + final String name, + final String value, + final long data, + final int hashCode) { + super(index, tag, owner, name, value, data); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, value, /* data = */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final long data, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, value, data); + this.hashCode = hashCode; + } + + Entry( + final int index, final int tag, final String name, final String value, final int hashCode) { + super(index, tag, /* owner = */ null, name, value, /* data = */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final long data, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, /* value = */ null, data); + this.hashCode = hashCode; + } + } + + /** + * The ClassWriter to which this SymbolTable belongs. This is only used to get access to {@link + * ClassWriter#getCommonSuperClass} and to serialize custom attributes with {@link + * Attribute#write}. + */ + final ClassWriter classWriter; + + /** + * The ClassReader from which this SymbolTable was constructed, or null if it was + * constructed from scratch. + */ + private final ClassReader sourceClassReader; + + /** The major version number of the class to which this symbol table belongs. */ + private int majorVersion; + + /** The internal name of the class to which this symbol table belongs. */ + private String className; + + /** + * The total number of {@link Entry} instances in {@link #entries}. This includes entries that are + * accessible (recursively) via {@link Entry#next}. + */ + private int entryCount; + + /** + * A hash set of all the entries in this SymbolTable (this includes the constant pool entries, the + * bootstrap method entries and the type table entries). Each {@link Entry} instance is stored at + * the array index given by its hash code modulo the array size. If several entries must be stored + * at the same array index, they are linked together via their {@link Entry#next} field. The + * factory methods of this class make sure that this table does not contain duplicated entries. + */ + private Entry[] entries; + + /** + * The number of constant pool items in {@link #constantPool}, plus 1. The first constant pool + * item has index 1, and long and double items count for two items. + */ + private int constantPoolCount; + + /** + * The content of the ClassFile's constant_pool JVMS structure corresponding to this SymbolTable. + * The ClassFile's constant_pool_count field is not included. + */ + private ByteVector constantPool; + + /** + * The number of bootstrap methods in {@link #bootstrapMethods}. Corresponds to the + * BootstrapMethods_attribute's num_bootstrap_methods field value. + */ + private int bootstrapMethodCount; + + /** + * The content of the BootstrapMethods attribute 'bootstrap_methods' array corresponding to this + * SymbolTable. Note that the first 6 bytes of the BootstrapMethods_attribute, and its + * num_bootstrap_methods field, are not included. + */ + private ByteVector bootstrapMethods; + + /** + * The actual number of elements in {@link #typeTable}. These elements are stored from index 0 to + * typeCount (excluded). The other array entries are empty. + */ + private int typeCount; + + /** + * An ASM specific type table used to temporarily store internal names that will not necessarily + * be stored in the constant pool. This type table is used by the control flow and data flow + * analysis algorithm used to compute stack map frames from scratch. This array stores {@link + * Symbol#TYPE_TAG} and {@link Symbol#UNINITIALIZED_TYPE_TAG}) Symbol. The type symbol at index + * i has its {@link Symbol#index} equal to i (and vice versa). + */ + private Entry[] typeTable; + + /** + * Constructs a new, empty SymbolTable for the given ClassWriter. + * + * @param classWriter a ClassWriter. + */ + SymbolTable(final ClassWriter classWriter) { + this.classWriter = classWriter; + this.sourceClassReader = null; + this.entries = new Entry[256]; + this.constantPoolCount = 1; + this.constantPool = new ByteVector(); + } + + /** + * Constructs a new SymbolTable for the given ClassWriter, initialized with the constant pool and + * bootstrap methods of the given ClassReader. + * + * @param classWriter a ClassWriter. + * @param classReader the ClassReader whose constant pool and bootstrap methods must be copied to + * initialize the SymbolTable. + */ + SymbolTable(final ClassWriter classWriter, final ClassReader classReader) { + this.classWriter = classWriter; + this.sourceClassReader = classReader; + + // Copy the constant pool binary content. + byte[] inputBytes = classReader.b; + int constantPoolOffset = classReader.getItem(1) - 1; + int constantPoolLength = classReader.header - constantPoolOffset; + constantPoolCount = classReader.getItemCount(); + constantPool = new ByteVector(constantPoolLength); + constantPool.putByteArray(inputBytes, constantPoolOffset, constantPoolLength); + + // Add the constant pool items in the symbol table entries. Reserve enough space in 'entries' to + // avoid too many hash set collisions (entries is not dynamically resized by the addConstant* + // method calls below), and to account for bootstrap method entries. + entries = new Entry[constantPoolCount * 2]; + char[] charBuffer = new char[classReader.getMaxStringLength()]; + int itemIndex = 1; + while (itemIndex < constantPoolCount) { + int itemOffset = classReader.getItem(itemIndex); + int itemTag = inputBytes[itemOffset - 1]; + int nameAndTypeItemOffset; + switch (itemTag) { + case Symbol.CONSTANT_FIELDREF_TAG: + case Symbol.CONSTANT_METHODREF_TAG: + case Symbol.CONSTANT_INTERFACE_METHODREF_TAG: + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 2)); + addConstantMemberReference( + itemIndex, + itemTag, + classReader.readClass(itemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_INTEGER_TAG: + case Symbol.CONSTANT_FLOAT_TAG: + addConstantInteger(itemIndex, itemTag, classReader.readInt(itemOffset)); + break; + case Symbol.CONSTANT_NAME_AND_TYPE_TAG: + addConstantNameAndType( + itemIndex, + classReader.readUTF8(itemOffset, charBuffer), + classReader.readUTF8(itemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_LONG_TAG: + case Symbol.CONSTANT_DOUBLE_TAG: + addConstantLong(itemIndex, itemTag, classReader.readLong(itemOffset)); + break; + case Symbol.CONSTANT_UTF8_TAG: + addConstantUtf8(itemIndex, classReader.readUTF(itemIndex, charBuffer)); + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + int memberRefItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 1)); + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2)); + addConstantMethodHandle( + itemIndex, + classReader.readByte(itemOffset), + classReader.readClass(memberRefItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 2)); + addConstantDynamicOrInvokeDynamicReference( + itemTag, + itemIndex, + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer), + classReader.readUnsignedShort(itemOffset)); + break; + case Symbol.CONSTANT_STRING_TAG: + case Symbol.CONSTANT_CLASS_TAG: + case Symbol.CONSTANT_METHOD_TYPE_TAG: + case Symbol.CONSTANT_MODULE_TAG: + case Symbol.CONSTANT_PACKAGE_TAG: + addConstantUtf8Reference( + itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer)); + break; + default: + throw new IllegalArgumentException(); + } + itemIndex += + (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1; + } + + // Copy the BootstrapMethods 'bootstrap_methods' array binary content, if any. + int currentAttributeOffset = classReader.getFirstAttributeOffset(); + for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer); + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + bootstrapMethodCount = classReader.readUnsignedShort(currentAttributeOffset + 6); + break; + } + currentAttributeOffset += 6 + classReader.readInt(currentAttributeOffset + 2); + } + if (bootstrapMethodCount > 0) { + // Compute the offset and the length of the BootstrapMethods 'bootstrap_methods' array. + int bootstrapMethodsOffset = currentAttributeOffset + 8; + int bootstrapMethodsLength = classReader.readInt(currentAttributeOffset + 2) - 2; + bootstrapMethods = new ByteVector(bootstrapMethodsLength); + bootstrapMethods.putByteArray(inputBytes, bootstrapMethodsOffset, bootstrapMethodsLength); + + // Add each bootstrap method in the symbol table entries. + int currentOffset = bootstrapMethodsOffset; + for (int i = 0; i < bootstrapMethodCount; i++) { + int offset = currentOffset - bootstrapMethodsOffset; + int bootstrapMethodRef = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + int numBootstrapArguments = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + int hashCode = classReader.readConst(bootstrapMethodRef, charBuffer).hashCode(); + while (numBootstrapArguments-- > 0) { + int bootstrapArgument = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + hashCode ^= classReader.readConst(bootstrapArgument, charBuffer).hashCode(); + } + add(new Entry(i, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode & 0x7FFFFFFF)); + } + } + } + + /** + * @return the ClassReader from which this SymbolTable was constructed, or null if it was + * constructed from scratch. + */ + ClassReader getSource() { + return sourceClassReader; + } + + /** @return the major version of the class to which this symbol table belongs. */ + int getMajorVersion() { + return majorVersion; + } + + /** @return the internal name of the class to which this symbol table belongs. */ + String getClassName() { + return className; + } + + /** + * Sets the major version and the name of the class to which this symbol table belongs. Also adds + * the class name to the constant pool. + * + * @param majorVersion a major ClassFile version number. + * @param className an internal class name. + * @return the constant pool index of a new or already existing Symbol with the given class name. + */ + int setMajorVersionAndClassName(final int majorVersion, final String className) { + this.majorVersion = majorVersion; + this.className = className; + return addConstantClass(className).index; + } + + /** @return the number of items in this symbol table's constant_pool array (plus 1). */ + int getConstantPoolCount() { + return constantPoolCount; + } + + /** @return the length in bytes of this symbol table's constant_pool array. */ + int getConstantPoolLength() { + return constantPool.length; + } + + /** + * Puts this symbol table's constant_pool array in the given ByteVector, preceded by the + * constant_pool_count value. + * + * @param output where the JVMS ClassFile's constant_pool array must be put. + */ + void putConstantPool(final ByteVector output) { + output.putShort(constantPoolCount).putByteArray(constantPool.data, 0, constantPool.length); + } + + /** + * Returns the size in bytes of this symbol table's BootstrapMethods attribute. Also adds the + * attribute name in the constant pool. + * + * @return the size in bytes of this symbol table's BootstrapMethods attribute. + */ + int computeBootstrapMethodsSize() { + if (bootstrapMethods != null) { + addConstantUtf8(Constants.BOOTSTRAP_METHODS); + return 8 + bootstrapMethods.length; + } else { + return 0; + } + } + + /** + * Puts this symbol table's BootstrapMethods attribute in the given ByteVector. This includes the + * 6 attribute header bytes and the num_bootstrap_methods value. + * + * @param output where the JVMS BootstrapMethods attribute must be put. + */ + void putBootstrapMethods(final ByteVector output) { + if (bootstrapMethods != null) { + output + .putShort(addConstantUtf8(Constants.BOOTSTRAP_METHODS)) + .putInt(bootstrapMethods.length + 2) + .putShort(bootstrapMethodCount) + .putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); + } + } + + // ----------------------------------------------------------------------------------------------- + // Generic symbol table entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * @param hashCode a {@link Entry#hashCode} value. + * @return the list of entries which can potentially have the given hash code. The list is stored + * via the {@link Entry#next} field. + */ + private Entry get(final int hashCode) { + return entries[hashCode % entries.length]; + } + + /** + * Puts the given entry in the {@link #entries} hash set. This method does not check + * whether {@link #entries} already contains a similar entry or not. {@link #entries} is resized + * if necessary to avoid hash collisions (multiple entries needing to be stored at the same {@link + * #entries} array index) as much as possible, with reasonable memory usage. + * + * @param entry an Entry (which must not already be contained in {@link #entries}). + * @return the given entry + */ + private Entry put(final Entry entry) { + if (entryCount > (entries.length * 3) / 4) { + int currentCapacity = entries.length; + int newCapacity = currentCapacity * 2 + 1; + Entry[] newEntries = new Entry[newCapacity]; + for (int i = currentCapacity - 1; i >= 0; --i) { + Entry currentEntry = entries[i]; + while (currentEntry != null) { + int newCurrentEntryIndex = currentEntry.hashCode % newCapacity; + Entry nextEntry = currentEntry.next; + currentEntry.next = newEntries[newCurrentEntryIndex]; + newEntries[newCurrentEntryIndex] = currentEntry; + currentEntry = nextEntry; + } + } + entries = newEntries; + } + entryCount++; + int index = entry.hashCode % entries.length; + entry.next = entries[index]; + return entries[index] = entry; + } + + /** + * Adds the given entry in the {@link #entries} hash set. This method does not check + * whether {@link #entries} already contains a similar entry or not, and does not resize + * {@link #entries} if necessary. + * + * @param entry an Entry (which must not already be contained in {@link #entries}). + */ + private void add(final Entry entry) { + entryCount++; + int index = entry.hashCode % entries.length; + entry.next = entries[index]; + entries[index] = entry; + } + + // ----------------------------------------------------------------------------------------------- + // Constant pool entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a number or string constant to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value the value of the constant to be added to the constant pool. This parameter must be + * an {@link Integer}, {@link Byte}, {@link Character}, {@link Short}, {@link Boolean}, {@link + * Float}, {@link Long}, {@link Double}, {@link String}, {@link Type} or {@link Handle}. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstant(final Object value) { + if (value instanceof Integer) { + return addConstantInteger(((Integer) value).intValue()); + } else if (value instanceof Byte) { + return addConstantInteger(((Byte) value).intValue()); + } else if (value instanceof Character) { + return addConstantInteger(((Character) value).charValue()); + } else if (value instanceof Short) { + return addConstantInteger(((Short) value).intValue()); + } else if (value instanceof Boolean) { + return addConstantInteger(((Boolean) value).booleanValue() ? 1 : 0); + } else if (value instanceof Float) { + return addConstantFloat(((Float) value).floatValue()); + } else if (value instanceof Long) { + return addConstantLong(((Long) value).longValue()); + } else if (value instanceof Double) { + return addConstantDouble(((Double) value).doubleValue()); + } else if (value instanceof String) { + return addConstantString((String) value); + } else if (value instanceof Type) { + Type type = (Type) value; + int typeSort = type.getSort(); + if (typeSort == Type.OBJECT) { + return addConstantClass(type.getInternalName()); + } else if (typeSort == Type.METHOD) { + return addConstantMethodType(type.getDescriptor()); + } else { // type is a primitive or array type. + return addConstantClass(type.getDescriptor()); + } + } else if (value instanceof Handle) { + Handle handle = (Handle) value; + return addConstantMethodHandle( + handle.getTag(), + handle.getOwner(), + handle.getName(), + handle.getDesc(), + handle.isInterface()); + } else if (value instanceof ConstantDynamic) { + ConstantDynamic constantDynamic = (ConstantDynamic) value; + return addConstantDynamic( + constantDynamic.getName(), + constantDynamic.getDescriptor(), + constantDynamic.getBootstrapMethod(), + constantDynamic.getBootstrapMethodArguments()); + } else { + throw new IllegalArgumentException("value " + value); + } + } + + /** + * Adds a CONSTANT_Class_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value the internal name of a class. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantClass(final String value) { + return addConstantUtf8Reference(Symbol.CONSTANT_CLASS_TAG, value); + } + + /** + * Adds a CONSTANT_Fieldref_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param owner the internal name of a class. + * @param name a field name. + * @param descriptor a field descriptor. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantFieldref(final String owner, final String name, final String descriptor) { + return addConstantMemberReference(Symbol.CONSTANT_FIELDREF_TAG, owner, name, descriptor); + } + + /** + * Adds a CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to the constant pool of this + * symbol table. Does nothing if the constant pool already contains a similar item. + * + * @param owner the internal name of a class. + * @param name a method name. + * @param descriptor a method descriptor. + * @param isInterface whether owner is an interface or not. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodref( + final String owner, final String name, final String descriptor, final boolean isInterface) { + int tag = isInterface ? Symbol.CONSTANT_INTERFACE_METHODREF_TAG : Symbol.CONSTANT_METHODREF_TAG; + return addConstantMemberReference(tag, owner, name, descriptor); + } + + /** + * Adds a CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to + * the constant pool of this symbol table. Does nothing if the constant pool already contains a + * similar item. + * + * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG} + * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}. + * @param owner the internal name of a class. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @return a new or already existing Symbol with the given value. + */ + private Entry addConstantMemberReference( + final int tag, final String owner, final String name, final String descriptor) { + int hashCode = hash(tag, owner, name, descriptor); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.owner.equals(owner) + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + constantPool.put122( + tag, addConstantClass(owner).index, addConstantNameAndType(name, descriptor)); + return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, 0, hashCode)); + } + + /** + * Adds a new CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info + * to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG} + * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}. + * @param owner the internal name of a class. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantMemberReference( + final int index, + final int tag, + final String owner, + final String name, + final String descriptor) { + add(new Entry(index, tag, owner, name, descriptor, 0, hash(tag, owner, name, descriptor))); + } + + /** + * Adds a CONSTANT_String_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a string. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantString(final String value) { + return addConstantUtf8Reference(Symbol.CONSTANT_STRING_TAG, value); + } + + /** + * Adds a CONSTANT_Integer_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value an int. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantInteger(final int value) { + return addConstantInteger(Symbol.CONSTANT_INTEGER_TAG, value); + } + + /** + * Adds a CONSTANT_Float_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a float. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantFloat(final float value) { + return addConstantInteger(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value)); + } + + /** + * Adds a CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol table. + * Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}. + * @param value an int or float. + * @return a constant pool constant with the given tag and primitive values. + */ + private Symbol addConstantInteger(final int tag, final int value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) { + return entry; + } + entry = entry.next; + } + constantPool.putByte(tag).putInt(value); + return put(new Entry(constantPoolCount++, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol + * table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}. + * @param value an int or float. + */ + private void addConstantInteger(final int index, final int tag, final int value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + /** + * Adds a CONSTANT_Long_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a long. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantLong(final long value) { + return addConstantLong(Symbol.CONSTANT_LONG_TAG, value); + } + + /** + * Adds a CONSTANT_Double_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a double. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantDouble(final double value) { + return addConstantLong(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value)); + } + + /** + * Adds a CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol table. + * Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}. + * @param value a long or double. + * @return a constant pool constant with the given tag and primitive values. + */ + private Symbol addConstantLong(final int tag, final long value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) { + return entry; + } + entry = entry.next; + } + int index = constantPoolCount; + constantPool.putByte(tag).putLong(value); + constantPoolCount += 2; + return put(new Entry(index, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Double_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}. + * @param value a long or double. + */ + private void addConstantLong(final int index, final int tag, final long value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + /** + * Adds a CONSTANT_NameAndType_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @return a new or already existing Symbol with the given value. + */ + int addConstantNameAndType(final String name, final String descriptor) { + final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG; + int hashCode = hash(tag, name, descriptor); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry.index; + } + entry = entry.next; + } + constantPool.put122(tag, addConstantUtf8(name), addConstantUtf8(descriptor)); + return put(new Entry(constantPoolCount++, tag, name, descriptor, hashCode)).index; + } + + /** + * Adds a new CONSTANT_NameAndType_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantNameAndType(final int index, final String name, final String descriptor) { + final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG; + add(new Entry(index, tag, name, descriptor, hash(tag, name, descriptor))); + } + + /** + * Adds a CONSTANT_Utf8_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a string. + * @return a new or already existing Symbol with the given value. + */ + int addConstantUtf8(final String value) { + int hashCode = hash(Symbol.CONSTANT_UTF8_TAG, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.CONSTANT_UTF8_TAG + && entry.hashCode == hashCode + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + constantPool.putByte(Symbol.CONSTANT_UTF8_TAG).putUTF8(value); + return put(new Entry(constantPoolCount++, Symbol.CONSTANT_UTF8_TAG, value, hashCode)).index; + } + + /** + * Adds a new CONSTANT_String_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param value a string. + */ + private void addConstantUtf8(final int index, final String value) { + add(new Entry(index, Symbol.CONSTANT_UTF8_TAG, value, hash(Symbol.CONSTANT_UTF8_TAG, value))); + } + + /** + * Adds a CONSTANT_MethodHandle_info to the constant pool of this symbol table. Does nothing if + * the constant pool already contains a similar item. + * + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of a class of interface. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @param isInterface whether owner is an interface or not. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodHandle( + final int referenceKind, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + // Note that we don't need to include isInterface in the hash computation, because it is + // redundant with owner (we can't have the same owner with different isInterface values). + int hashCode = hash(tag, owner, name, descriptor, referenceKind); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.data == referenceKind + && entry.owner.equals(owner) + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + if (referenceKind <= Opcodes.H_PUTSTATIC) { + constantPool.put112(tag, referenceKind, addConstantFieldref(owner, name, descriptor).index); + } else { + constantPool.put112( + tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index); + } + return put( + new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode)); + } + + /** + * Adds a new CONSTANT_MethodHandle_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of a class of interface. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantMethodHandle( + final int index, + final int referenceKind, + final String owner, + final String name, + final String descriptor) { + final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + int hashCode = hash(tag, owner, name, descriptor, referenceKind); + add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode)); + } + + /** + * Adds a CONSTANT_MethodType_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param methodDescriptor a method descriptor. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodType(final String methodDescriptor) { + return addConstantUtf8Reference(Symbol.CONSTANT_METHOD_TYPE_TAG, methodDescriptor); + } + + /** + * Adds a CONSTANT_Dynamic_info to the constant pool of this symbol table. Also adds the related + * bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the constant + * pool already contains a similar item. + * + * @param name a method name. + * @param descriptor a field descriptor. + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments); + return addConstantDynamicOrInvokeDynamicReference( + Symbol.CONSTANT_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index); + } + + /** + * Adds a CONSTANT_InvokeDynamic_info to the constant pool of this symbol table. Also adds the + * related bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param name a method name. + * @param descriptor a method descriptor. + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantInvokeDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments); + return addConstantDynamicOrInvokeDynamicReference( + Symbol.CONSTANT_INVOKE_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index); + } + + /** + * Adds a CONSTANT_Dynamic or a CONSTANT_InvokeDynamic_info to the constant pool of this symbol + * table. Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link + * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}. + * @param name a method name. + * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG) or a method descriptor for + * CONSTANT_INVOKE_DYNAMIC_TAG. + * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addConstantDynamicOrInvokeDynamicReference( + final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) { + int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.data == bootstrapMethodIndex + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor)); + return put( + new Entry( + constantPoolCount++, tag, null, name, descriptor, bootstrapMethodIndex, hashCode)); + } + + /** + * Adds a new CONSTANT_Dynamic_info or CONSTANT_InvokeDynamic_info to the constant pool of this + * symbol table. + * + * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link + * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}. + * @param index the constant pool index of the new Symbol. + * @param name a method name. + * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG or a method descriptor for + * CONSTANT_INVOKE_DYNAMIC_TAG. + * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute. + */ + private void addConstantDynamicOrInvokeDynamicReference( + final int tag, + final int index, + final String name, + final String descriptor, + final int bootstrapMethodIndex) { + int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex); + add(new Entry(index, tag, null, name, descriptor, bootstrapMethodIndex, hashCode)); + } + + /** + * Adds a CONSTANT_Module_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param moduleName a fully qualified name (using dots) of a module. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantModule(final String moduleName) { + return addConstantUtf8Reference(Symbol.CONSTANT_MODULE_TAG, moduleName); + } + + /** + * Adds a CONSTANT_Package_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param packageName the internal name of a package. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantPackage(final String packageName) { + return addConstantUtf8Reference(Symbol.CONSTANT_PACKAGE_TAG, packageName); + } + + /** + * Adds a CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info, + * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. Does + * nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link + * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link + * Symbol#CONSTANT_PACKAGE_TAG}. + * @param value an internal class name, an arbitrary string, a method descriptor, a module or a + * package name, depending on tag. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addConstantUtf8Reference(final int tag, final String value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.value.equals(value)) { + return entry; + } + entry = entry.next; + } + constantPool.put12(tag, addConstantUtf8(value)); + return put(new Entry(constantPoolCount++, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info, + * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link + * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link + * Symbol#CONSTANT_PACKAGE_TAG}. + * @param value an internal class name, an arbitrary string, a method descriptor, a module or a + * package name, depending on tag. + */ + private void addConstantUtf8Reference(final int index, final int tag, final String value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + // ----------------------------------------------------------------------------------------------- + // Bootstrap method entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if + * the BootstrapMethods already contains a similar bootstrap method. + * + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addBootstrapMethod( + final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) { + ByteVector bootstrapMethodsAttribute = bootstrapMethods; + if (bootstrapMethodsAttribute == null) { + bootstrapMethodsAttribute = bootstrapMethods = new ByteVector(); + } + + // The bootstrap method arguments can be Constant_Dynamic values, which reference other + // bootstrap methods. We must therefore add the bootstrap method arguments to the constant pool + // and BootstrapMethods attribute first, so that the BootstrapMethods attribute is not modified + // while adding the given bootstrap method to it, in the rest of this method. + for (Object bootstrapMethodArgument : bootstrapMethodArguments) { + addConstant(bootstrapMethodArgument); + } + + // Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to + // compare it with existing ones, and will be reverted below if there is already a similar + // bootstrap method. + int bootstrapMethodOffset = bootstrapMethodsAttribute.length; + bootstrapMethodsAttribute.putShort( + addConstantMethodHandle( + bootstrapMethodHandle.getTag(), + bootstrapMethodHandle.getOwner(), + bootstrapMethodHandle.getName(), + bootstrapMethodHandle.getDesc(), + bootstrapMethodHandle.isInterface()) + .index); + int numBootstrapArguments = bootstrapMethodArguments.length; + bootstrapMethodsAttribute.putShort(numBootstrapArguments); + for (Object bootstrapMethodArgument : bootstrapMethodArguments) { + bootstrapMethodsAttribute.putShort(addConstant(bootstrapMethodArgument).index); + } + + // Compute the length and the hash code of the bootstrap method. + int bootstrapMethodlength = bootstrapMethodsAttribute.length - bootstrapMethodOffset; + int hashCode = bootstrapMethodHandle.hashCode(); + for (Object bootstrapMethodArgument : bootstrapMethodArguments) { + hashCode ^= bootstrapMethodArgument.hashCode(); + } + hashCode &= 0x7FFFFFFF; + + // Add the bootstrap method to the symbol table or revert the above changes. + return addBootstrapMethod(bootstrapMethodOffset, bootstrapMethodlength, hashCode); + } + + /** + * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if + * the BootstrapMethods already contains a similar bootstrap method (more precisely, reverts the + * content of {@link #bootstrapMethods} to remove the last, duplicate bootstrap method). + * + * @param offset the offset of the last bootstrap method in {@link #bootstrapMethods}, in bytes. + * @param length the length of this bootstrap method in {@link #bootstrapMethods}, in bytes. + * @param hashCode the hash code of this bootstrap method. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) { + final byte[] bootstrapMethodsData = bootstrapMethods.data; + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) { + int otherOffset = (int) entry.data; + boolean isSameBootstrapMethod = true; + for (int i = 0; i < length; ++i) { + if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) { + isSameBootstrapMethod = false; + break; + } + } + if (isSameBootstrapMethod) { + bootstrapMethods.length = offset; // Revert to old position. + return entry; + } + } + entry = entry.next; + } + return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode)); + } + + // ----------------------------------------------------------------------------------------------- + // Type table entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * @param typeIndex a type table index. + * @return the type table element whose index is given. + */ + Symbol getType(final int typeIndex) { + return typeTable[typeIndex]; + } + + /** + * Adds a type in the type table of this symbol table. Does nothing if the type table already + * contains a similar type. + * + * @param value an internal class name. + * @return the index of a new or already existing type Symbol with the given value. + */ + int addType(final String value) { + int hashCode = hash(Symbol.TYPE_TAG, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.TYPE_TAG && entry.hashCode == hashCode && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addType(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode)); + } + + /** + * Adds an {@link Frame#ITEM_UNINITIALIZED} type in the type table of this symbol table. Does + * nothing if the type table already contains a similar type. + * + * @param value an internal class name. + * @param bytecodeOffset the bytecode offset of the NEW instruction that created this {@link + * Frame#ITEM_UNINITIALIZED} type value. + * @return the index of a new or already existing type Symbol with the given value. + */ + int addUninitializedType(final String value, final int bytecodeOffset) { + int hashCode = hash(Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.UNINITIALIZED_TYPE_TAG + && entry.hashCode == hashCode + && entry.data == bytecodeOffset + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addType( + new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode)); + } + + /** + * Adds a merged type in the type table of this symbol table. Does nothing if the type table + * already contains a similar type. + * + * @param typeTableIndex1 a {@link Symbol#TYPE_TAG} type, specified by its index in the type + * table. + * @param typeTableIndex2 another {@link Symbol#TYPE_TAG} type, specified by its index in the type + * table. + * @return the index of a new or already existing {@link Symbol#TYPE_TAG} type Symbol, + * corresponding to the common super class of the given types. + */ + int addMergedType(final int typeTableIndex1, final int typeTableIndex2) { + // TODO sort the arguments? The merge result should be independent of their order. + long data = typeTableIndex1 | (((long) typeTableIndex2) << 32); + int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.MERGED_TYPE_TAG && entry.hashCode == hashCode && entry.data == data) { + return entry.info; + } + entry = entry.next; + } + String type1 = typeTable[typeTableIndex1].value; + String type2 = typeTable[typeTableIndex2].value; + int commonSuperTypeIndex = addType(classWriter.getCommonSuperClass(type1, type2)); + put(new Entry(typeCount, Symbol.MERGED_TYPE_TAG, data, hashCode)).info = commonSuperTypeIndex; + return commonSuperTypeIndex; + } + + /** + * Adds the given type Symbol to {@link #typeTable}. + * + * @param entry a {@link Symbol#TYPE_TAG} or {@link Symbol#UNINITIALIZED_TYPE_TAG} type symbol. + * The index of this Symbol must be equal to the current value of {@link #typeCount}. + * @return the index in {@link #typeTable} where the given type was added, which is also equal to + * entry's index by hypothesis. + */ + private int addType(final Entry entry) { + if (typeTable == null) { + typeTable = new Entry[16]; + } + if (typeCount == typeTable.length) { + Entry[] newTypeTable = new Entry[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTypeTable, 0, typeTable.length); + typeTable = newTypeTable; + } + typeTable[typeCount++] = entry; + return put(entry).index; + } + + // ----------------------------------------------------------------------------------------------- + // Static helper methods to compute hash codes. + // ----------------------------------------------------------------------------------------------- + + private static int hash(final int tag, final int value) { + return 0x7FFFFFFF & (tag + value); + } + + private static int hash(final int tag, final long value) { + return 0x7FFFFFFF & (tag + (int) value + (int) (value >>> 32)); + } + + private static int hash(final int tag, final String value) { + return 0x7FFFFFFF & (tag + value.hashCode()); + } + + private static int hash(final int tag, final String value1, final int value2) { + return 0x7FFFFFFF & (tag + value1.hashCode() + value2); + } + + private static int hash(final int tag, final String value1, final String value2) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode()); + } + + private static int hash( + final int tag, final String value1, final String value2, final int value3) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * (value3 + 1)); + } + + private static int hash( + final int tag, final String value1, final String value2, final String value3) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode()); + } + + private static int hash( + final int tag, + final String value1, + final String value2, + final String value3, + final int value4) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4); + } +} diff --git a/src/jvm/clojure/asm/Type.java b/src/jvm/clojure/asm/Type.java index 31db08d861..bcd173d097 100644 --- a/src/jvm/clojure/asm/Type.java +++ b/src/jvm/clojure/asm/Type.java @@ -1,895 +1,906 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; import java.lang.reflect.Constructor; import java.lang.reflect.Method; /** - * A Java field or method type. This class can be used to make it easier to - * manipulate type and method descriptors. + * A Java field or method type. This class can be used to make it easier to manipulate type and + * method descriptors. * * @author Eric Bruneton * @author Chris Nokleberg */ public class Type { - /** - * The sort of the void type. See {@link #getSort getSort}. - */ - public static final int VOID = 0; - - /** - * The sort of the boolean type. See {@link #getSort getSort}. - */ - public static final int BOOLEAN = 1; - - /** - * The sort of the char type. See {@link #getSort getSort}. - */ - public static final int CHAR = 2; - - /** - * The sort of the byte type. See {@link #getSort getSort}. - */ - public static final int BYTE = 3; - - /** - * The sort of the short type. See {@link #getSort getSort}. - */ - public static final int SHORT = 4; - - /** - * The sort of the int type. See {@link #getSort getSort}. - */ - public static final int INT = 5; - - /** - * The sort of the float type. See {@link #getSort getSort}. - */ - public static final int FLOAT = 6; - - /** - * The sort of the long type. See {@link #getSort getSort}. - */ - public static final int LONG = 7; - - /** - * The sort of the double type. See {@link #getSort getSort}. - */ - public static final int DOUBLE = 8; - - /** - * The sort of array reference types. See {@link #getSort getSort}. - */ - public static final int ARRAY = 9; - - /** - * The sort of object reference types. See {@link #getSort getSort}. - */ - public static final int OBJECT = 10; - - /** - * The sort of method types. See {@link #getSort getSort}. - */ - public static final int METHOD = 11; - - /** - * The void type. - */ - public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24) - | (5 << 16) | (0 << 8) | 0, 1); - - /** - * The boolean type. - */ - public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24) - | (0 << 16) | (5 << 8) | 1, 1); - - /** - * The char type. - */ - public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24) - | (0 << 16) | (6 << 8) | 1, 1); - - /** - * The byte type. - */ - public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24) - | (0 << 16) | (5 << 8) | 1, 1); - - /** - * The short type. - */ - public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24) - | (0 << 16) | (7 << 8) | 1, 1); - - /** - * The int type. - */ - public static final Type INT_TYPE = new Type(INT, null, ('I' << 24) - | (0 << 16) | (0 << 8) | 1, 1); - - /** - * The float type. - */ - public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24) - | (2 << 16) | (2 << 8) | 1, 1); - - /** - * The long type. - */ - public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24) - | (1 << 16) | (1 << 8) | 2, 1); - - /** - * The double type. - */ - public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24) - | (3 << 16) | (3 << 8) | 2, 1); - - // ------------------------------------------------------------------------ - // Fields - // ------------------------------------------------------------------------ - - /** - * The sort of this Java type. - */ - private final int sort; - - /** - * A buffer containing the internal name of this Java type. This field is - * only used for reference types. - */ - private final char[] buf; - - /** - * The offset of the internal name of this Java type in {@link #buf buf} or, - * for primitive types, the size, descriptor and getOpcode offsets for this - * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset - * for IALOAD or IASTORE, byte 3 the offset for all other instructions). - */ - private final int off; - - /** - * The length of the internal name of this Java type. - */ - private final int len; - - // ------------------------------------------------------------------------ - // Constructors - // ------------------------------------------------------------------------ - - /** - * Constructs a reference type. - * - * @param sort - * the sort of the reference type to be constructed. - * @param buf - * a buffer containing the descriptor of the previous type. - * @param off - * the offset of this descriptor in the previous buffer. - * @param len - * the length of this descriptor. - */ - private Type(final int sort, final char[] buf, final int off, final int len) { - this.sort = sort; - this.buf = buf; - this.off = off; - this.len = len; - } - - /** - * Returns the Java type corresponding to the given type descriptor. - * - * @param typeDescriptor - * a field or method type descriptor. - * @return the Java type corresponding to the given type descriptor. - */ - public static Type getType(final String typeDescriptor) { - return getType(typeDescriptor.toCharArray(), 0); - } + /** The sort of the void type. See {@link #getSort}. */ + public static final int VOID = 0; - /** - * Returns the Java type corresponding to the given internal name. - * - * @param internalName - * an internal name. - * @return the Java type corresponding to the given internal name. - */ - public static Type getObjectType(final String internalName) { - char[] buf = internalName.toCharArray(); - return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length); - } + /** The sort of the boolean type. See {@link #getSort}. */ + public static final int BOOLEAN = 1; - /** - * Returns the Java type corresponding to the given method descriptor. - * Equivalent to Type.getType(methodDescriptor). - * - * @param methodDescriptor - * a method descriptor. - * @return the Java type corresponding to the given method descriptor. - */ - public static Type getMethodType(final String methodDescriptor) { - return getType(methodDescriptor.toCharArray(), 0); - } + /** The sort of the char type. See {@link #getSort}. */ + public static final int CHAR = 2; - /** - * Returns the Java method type corresponding to the given argument and - * return types. - * - * @param returnType - * the return type of the method. - * @param argumentTypes - * the argument types of the method. - * @return the Java type corresponding to the given argument and return - * types. - */ - public static Type getMethodType(final Type returnType, - final Type... argumentTypes) { - return getType(getMethodDescriptor(returnType, argumentTypes)); - } + /** The sort of the byte type. See {@link #getSort}. */ + public static final int BYTE = 3; - /** - * Returns the Java type corresponding to the given class. - * - * @param c - * a class. - * @return the Java type corresponding to the given class. - */ - public static Type getType(final Class c) { - if (c.isPrimitive()) { - if (c == Integer.TYPE) { - return INT_TYPE; - } else if (c == Void.TYPE) { - return VOID_TYPE; - } else if (c == Boolean.TYPE) { - return BOOLEAN_TYPE; - } else if (c == Byte.TYPE) { - return BYTE_TYPE; - } else if (c == Character.TYPE) { - return CHAR_TYPE; - } else if (c == Short.TYPE) { - return SHORT_TYPE; - } else if (c == Double.TYPE) { - return DOUBLE_TYPE; - } else if (c == Float.TYPE) { - return FLOAT_TYPE; - } else /* if (c == Long.TYPE) */{ - return LONG_TYPE; - } - } else { - return getType(getDescriptor(c)); - } - } + /** The sort of the short type. See {@link #getSort}. */ + public static final int SHORT = 4; - /** - * Returns the Java method type corresponding to the given constructor. - * - * @param c - * a {@link Constructor Constructor} object. - * @return the Java method type corresponding to the given constructor. - */ - public static Type getType(final Constructor c) { - return getType(getConstructorDescriptor(c)); - } + /** The sort of the int type. See {@link #getSort}. */ + public static final int INT = 5; - /** - * Returns the Java method type corresponding to the given method. - * - * @param m - * a {@link Method Method} object. - * @return the Java method type corresponding to the given method. - */ - public static Type getType(final Method m) { - return getType(getMethodDescriptor(m)); - } + /** The sort of the float type. See {@link #getSort}. */ + public static final int FLOAT = 6; - /** - * Returns the Java types corresponding to the argument types of the given - * method descriptor. - * - * @param methodDescriptor - * a method descriptor. - * @return the Java types corresponding to the argument types of the given - * method descriptor. - */ - public static Type[] getArgumentTypes(final String methodDescriptor) { - char[] buf = methodDescriptor.toCharArray(); - int off = 1; - int size = 0; - while (true) { - char car = buf[off++]; - if (car == ')') { - break; - } else if (car == 'L') { - while (buf[off++] != ';') { - } - ++size; - } else if (car != '[') { - ++size; - } + /** The sort of the long type. See {@link #getSort}. */ + public static final int LONG = 7; + + /** The sort of the double type. See {@link #getSort}. */ + public static final int DOUBLE = 8; + + /** The sort of array reference types. See {@link #getSort}. */ + public static final int ARRAY = 9; + + /** The sort of object reference types. See {@link #getSort}. */ + public static final int OBJECT = 10; + + /** The sort of method types. See {@link #getSort}. */ + public static final int METHOD = 11; + + /** The (private) sort of object reference types represented with an internal name. */ + private static final int INTERNAL = 12; + + /** The descriptors of the primitive types. */ + private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD"; + + /** The void type. */ + public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1); + + /** The boolean type. */ + public static final Type BOOLEAN_TYPE = + new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1); + + /** The char type. */ + public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1); + + /** The byte type. */ + public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1); + + /** The short type. */ + public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1); + + /** The int type. */ + public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1); + + /** The float type. */ + public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1); + + /** The long type. */ + public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1); + + /** The double type. */ + public static final Type DOUBLE_TYPE = + new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1); + + // ----------------------------------------------------------------------------------------------- + // Fields + // ----------------------------------------------------------------------------------------------- + + /** + * The sort of this type. Either {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, + * {@link #SHORT}, {@link #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, + * {@link #OBJECT}, {@link #METHOD} or {@link #INTERNAL}. + */ + private final int sort; + + /** + * A buffer containing the value of this field or method type. This value is an internal name for + * {@link #OBJECT} and {@link #INTERNAL} types, and a field or method descriptor in the other + * cases. + * + *

For {@link #OBJECT} types, this field also contains the descriptor: the characters in + * [{@link #valueBegin},{@link #valueEnd}) contain the internal name, and those in [{@link + * #valueBegin} - 1, {@link #valueEnd} + 1) contain the descriptor. + */ + private final String valueBuffer; + + /** + * The beginning index, inclusive, of the value of this Java field or method type in {@link + * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types, + * and a field or method descriptor in the other cases. + */ + private final int valueBegin; + + /** + * The end index, exclusive, of the value of this Java field or method type in {@link + * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types, + * and a field or method descriptor in the other cases. + */ + private final int valueEnd; + + // ----------------------------------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a reference type. + * + * @param sort the sort of this type, see {@link #sort}. + * @param valueBuffer a buffer containing the value of this field or method type. + * @param valueBegin the beginning index, inclusive, of the value of this field or method type in + * valueBuffer. + * @param valueEnd tne end index, exclusive, of the value of this field or method type in + * valueBuffer. + */ + private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) { + this.sort = sort; + this.valueBuffer = valueBuffer; + this.valueBegin = valueBegin; + this.valueEnd = valueEnd; + } + + /** + * Returns the {@link Type} corresponding to the given type descriptor. + * + * @param typeDescriptor a field or method type descriptor. + * @return the {@link Type} corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getType(typeDescriptor, 0, typeDescriptor.length()); + } + + /** + * Returns the {@link Type} corresponding to the given internal name. + * + * @param internalName an internal name. + * @return the {@link Type} corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + return new Type( + internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length()); + } + + /** + * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to + * Type.getType(methodDescriptor). + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length()); + } + + /** + * Returns the method {@link Type} corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the method {@link Type} corresponding to the given argument and return types. + */ + public static Type getMethodType(final Type returnType, final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the {@link Type} corresponding to the given class. + * + * @param clazz a class. + * @return the {@link Type} corresponding to the given class. + */ + public static Type getType(final Class clazz) { + if (clazz.isPrimitive()) { + if (clazz == Integer.TYPE) { + return INT_TYPE; + } else if (clazz == Void.TYPE) { + return VOID_TYPE; + } else if (clazz == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (clazz == Byte.TYPE) { + return BYTE_TYPE; + } else if (clazz == Character.TYPE) { + return CHAR_TYPE; + } else if (clazz == Short.TYPE) { + return SHORT_TYPE; + } else if (clazz == Double.TYPE) { + return DOUBLE_TYPE; + } else if (clazz == Float.TYPE) { + return FLOAT_TYPE; + } else if (clazz == Long.TYPE) { + return LONG_TYPE; + } else { + throw new AssertionError(); + } + } else { + return getType(getDescriptor(clazz)); + } + } + + /** + * Returns the method {@link Type} corresponding to the given constructor. + * + * @param constructor a {@link Constructor} object. + * @return the method {@link Type} corresponding to the given constructor. + */ + public static Type getType(final Constructor constructor) { + return getType(getConstructorDescriptor(constructor)); + } + + /** + * Returns the method {@link Type} corresponding to the given method. + * + * @param method a {@link Method} object. + * @return the method {@link Type} corresponding to the given method. + */ + public static Type getType(final Method method) { + return getType(getMethodDescriptor(method)); + } + + /** + * Returns the {@link Type} values corresponding to the argument types of the given method + * descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} values corresponding to the argument types of the given method + * descriptor. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + // First step: compute the number of argument types in methodDescriptor. + int numArgumentTypes = 0; + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Parse the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + while (methodDescriptor.charAt(currentOffset++) != ';') { + // Skip the argument descriptor content. } - Type[] args = new Type[size]; - off = 1; - size = 0; - while (buf[off] != ')') { - args[size] = getType(buf, off); - off += args[size].len + (args[size].sort == OBJECT ? 2 : 0); - size += 1; + } + ++numArgumentTypes; + } + + // Second step: create a Type instance for each argument type. + Type[] argumentTypes = new Type[numArgumentTypes]; + // Skip the first character, which is always a '('. + currentOffset = 1; + // Parse and create the argument types, one at each loop iteration. + int currentArgumentTypeIndex = 0; + while (methodDescriptor.charAt(currentOffset) != ')') { + final int currentArgumentTypeOffset = currentOffset; + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + while (methodDescriptor.charAt(currentOffset++) != ';') { + // Skip the argument descriptor content. } - return args; - } - - /** - * Returns the Java types corresponding to the argument types of the given - * method. - * - * @param method - * a method. - * @return the Java types corresponding to the argument types of the given - * method. - */ - public static Type[] getArgumentTypes(final Method method) { - Class[] classes = method.getParameterTypes(); - Type[] types = new Type[classes.length]; - for (int i = classes.length - 1; i >= 0; --i) { - types[i] = getType(classes[i]); + } + argumentTypes[currentArgumentTypeIndex++] = + getType(methodDescriptor, currentArgumentTypeOffset, currentOffset); + } + return argumentTypes; + } + + /** + * Returns the {@link Type} values corresponding to the argument types of the given method. + * + * @param method a method. + * @return the {@link Type} values corresponding to the argument types of the given method. + */ + public static Type[] getArgumentTypes(final Method method) { + Class[] classes = method.getParameterTypes(); + Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * Returns the {@link Type} corresponding to the return type of the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} corresponding to the return type of the given method descriptor. + */ + public static Type getReturnType(final String methodDescriptor) { + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Skip the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + while (methodDescriptor.charAt(currentOffset++) != ';') { + // Skip the argument descriptor content. } - return types; - } - - /** - * Returns the Java type corresponding to the return type of the given - * method descriptor. - * - * @param methodDescriptor - * a method descriptor. - * @return the Java type corresponding to the return type of the given - * method descriptor. - */ - public static Type getReturnType(final String methodDescriptor) { - char[] buf = methodDescriptor.toCharArray(); - return getType(buf, methodDescriptor.indexOf(')') + 1); - } - - /** - * Returns the Java type corresponding to the return type of the given - * method. - * - * @param method - * a method. - * @return the Java type corresponding to the return type of the given - * method. - */ - public static Type getReturnType(final Method method) { - return getType(method.getReturnType()); - } - - /** - * Computes the size of the arguments and of the return value of a method. - * - * @param desc - * the descriptor of a method. - * @return the size of the arguments of the method (plus one for the - * implicit this argument), argSize, and the size of its return - * value, retSize, packed into a single int i = - * (argSize << 2) | retSize (argSize is therefore equal to - * i >> 2, and retSize to i & 0x03). - */ - public static int getArgumentsAndReturnSizes(final String desc) { - int n = 1; - int c = 1; - while (true) { - char car = desc.charAt(c++); - if (car == ')') { - car = desc.charAt(c); - return n << 2 - | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1)); - } else if (car == 'L') { - while (desc.charAt(c++) != ';') { - } - n += 1; - } else if (car == '[') { - while ((car = desc.charAt(c)) == '[') { - ++c; - } - if (car == 'D' || car == 'J') { - n -= 1; - } - } else if (car == 'D' || car == 'J') { - n += 2; - } else { - n += 1; - } + } + } + return getType(methodDescriptor, currentOffset + 1, methodDescriptor.length()); + } + + /** + * Returns the {@link Type} corresponding to the return type of the given method. + * + * @param method a method. + * @return the {@link Type} corresponding to the return type of the given method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param methodDescriptor a method descriptor. + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * (argumentsSize << 2) | returnSize (argumentsSize is therefore equal to i + * >> 2, and returnSize to i & 0x03). + */ + public static int getArgumentsAndReturnSizes(final String methodDescriptor) { + int argumentsSize = 1; + // Skip the first character, which is always a '('. + int currentOffset = 1; + int currentChar = methodDescriptor.charAt(currentOffset); + // Parse the argument types and compute their size, one at a each loop iteration. + while (currentChar != ')') { + if (currentChar == 'J' || currentChar == 'D') { + currentOffset++; + argumentsSize += 2; + } else { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; } - } - - /** - * Returns the Java type corresponding to the given type descriptor. For - * method descriptors, buf is supposed to contain nothing more than the - * descriptor itself. - * - * @param buf - * a buffer containing a type descriptor. - * @param off - * the offset of this descriptor in the previous buffer. - * @return the Java type corresponding to the given type descriptor. - */ - private static Type getType(final char[] buf, final int off) { - int len; - switch (buf[off]) { - case 'V': - return VOID_TYPE; - case 'Z': - return BOOLEAN_TYPE; - case 'C': - return CHAR_TYPE; - case 'B': - return BYTE_TYPE; - case 'S': - return SHORT_TYPE; - case 'I': - return INT_TYPE; - case 'F': - return FLOAT_TYPE; - case 'J': - return LONG_TYPE; - case 'D': - return DOUBLE_TYPE; - case '[': - len = 1; - while (buf[off + len] == '[') { - ++len; - } - if (buf[off + len] == 'L') { - ++len; - while (buf[off + len] != ';') { - ++len; - } - } - return new Type(ARRAY, buf, off, len + 1); - case 'L': - len = 1; - while (buf[off + len] != ';') { - ++len; - } - return new Type(OBJECT, buf, off + 1, len - 1); - // case '(': - default: - return new Type(METHOD, buf, off, buf.length - off); + if (methodDescriptor.charAt(currentOffset++) == 'L') { + while (methodDescriptor.charAt(currentOffset++) != ';') { + // Skip the argument descriptor content. + } } - } - - // ------------------------------------------------------------------------ - // Accessors - // ------------------------------------------------------------------------ - - /** - * Returns the sort of this Java type. - * - * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR}, - * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT}, - * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE}, - * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD - * METHOD}. - */ - public int getSort() { - return sort; - } - - /** - * Returns the number of dimensions of this array type. This method should - * only be used for an array type. - * - * @return the number of dimensions of this array type. - */ - public int getDimensions() { - int i = 1; - while (buf[off + i] == '[') { - ++i; + argumentsSize += 1; + } + currentChar = methodDescriptor.charAt(currentOffset); + } + currentChar = methodDescriptor.charAt(currentOffset + 1); + if (currentChar == 'V') { + return argumentsSize << 2; + } else { + int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1; + return argumentsSize << 2 | returnSize; + } + } + + /** + * Returns the {@link Type} corresponding to the given field or method descriptor. + * + * @param descriptorBuffer a buffer containing the field or method descriptor. + * @param descriptorBegin the beginning index, inclusive, of the field or method descriptor in + * descriptorBuffer. + * @param descriptorEnd the end index, exclusive, of the field or method descriptor in + * descriptorBuffer. + * @return the {@link Type} corresponding to the given type descriptor. + */ + private static Type getType( + final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) { + switch (descriptorBuffer.charAt(descriptorBegin)) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + return new Type(ARRAY, descriptorBuffer, descriptorBegin, descriptorEnd); + case 'L': + return new Type(OBJECT, descriptorBuffer, descriptorBegin + 1, descriptorEnd - 1); + case '(': + return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd); + default: + throw new IllegalArgumentException(); + } + } + + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the sort of this type. + * + * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link + * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or + * {@link #METHOD}. + */ + public int getSort() { + return sort == INTERNAL ? OBJECT : sort; + } + + /** + * Returns the number of dimensions of this array type. This method should only be used for an + * array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int numDimensions = 1; + while (valueBuffer.charAt(valueBegin + numDimensions) == '[') { + numDimensions++; + } + return numDimensions; + } + + /** + * Returns the type of the elements of this array type. This method should only be used for an + * array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + final int numDimensions = getDimensions(); + return getType(valueBuffer, valueBegin + numDimensions, valueEnd); + } + + /** + * Returns the binary name of the class corresponding to this type. This method must not be used + * on method types. + * + * @return the binary name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuilder stringBuilder = new StringBuilder(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + stringBuilder.append("[]"); } - return i; - } - - /** - * Returns the type of the elements of this array type. This method should - * only be used for an array type. - * - * @return Returns the type of the elements of this array type. - */ - public Type getElementType() { - return getType(buf, off + getDimensions()); - } - - /** - * Returns the binary name of the class corresponding to this type. This - * method must not be used on method types. - * - * @return the binary name of the class corresponding to this type. - */ - public String getClassName() { - switch (sort) { - case VOID: - return "void"; + return stringBuilder.toString(); + case OBJECT: + case INTERNAL: + return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.'); + default: + throw new AssertionError(); + } + } + + /** + * Returns the internal name of the class corresponding to this object or array type. The internal + * name of a class is its fully qualified name (as returned by Class.getName(), where '.' are + * replaced by '/'). This method should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + */ + public String getInternalName() { + return valueBuffer.substring(valueBegin, valueEnd); + } + + /** + * Returns the argument types of methods of this type. This method should only be used for method + * types. + * + * @return the argument types of methods of this type. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + + /** + * Returns the return type of methods of this type. This method should only be used for method + * types. + * + * @return the return type of methods of this type. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + + /** + * Returns the size of the arguments and of the return value of methods of this type. This method + * should only be used for method types. + * + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * (argumentsSize << 2) | returnSize (argumentsSize is therefore equal to i + * >> 2, and returnSize to i & 0x03). + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + // ----------------------------------------------------------------------------------------------- + // Conversion to type descriptors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the descriptor corresponding to this type. + * + * @return the descriptor corresponding to this type. + */ + public String getDescriptor() { + if (sort == OBJECT) { + return valueBuffer.substring(valueBegin - 1, valueEnd + 1); + } else if (sort == INTERNAL) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('L'); + stringBuilder.append(valueBuffer, valueBegin, valueEnd); + stringBuilder.append(';'); + return stringBuilder.toString(); + } else { + return valueBuffer.substring(valueBegin, valueEnd); + } + } + + /** + * Returns the descriptor corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the descriptor corresponding to the given argument and return types. + */ + public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + for (int i = 0; i < argumentTypes.length; ++i) { + argumentTypes[i].appendDescriptor(stringBuilder); + } + stringBuilder.append(')'); + returnType.appendDescriptor(stringBuilder); + return stringBuilder.toString(); + } + + /** + * Appends the descriptor corresponding to this type to the given string buffer. + * + * @param stringBuilder the string builder to which the descriptor must be appended. + */ + private void appendDescriptor(final StringBuilder stringBuilder) { + if (sort == OBJECT) { + stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1); + } else if (sort == INTERNAL) { + stringBuilder.append('L'); + stringBuilder.append(valueBuffer, valueBegin, valueEnd); + stringBuilder.append(';'); + } else { + stringBuilder.append(valueBuffer, valueBegin, valueEnd); + } + } + + // ----------------------------------------------------------------------------------------------- + // Direct conversion from classes to type descriptors, + // without intermediate Type objects + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the internal name of the given class. The internal name of a class is its fully + * qualified name, as returned by Class.getName(), where '.' are replaced by '/'. + * + * @param clazz an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class clazz) { + return clazz.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to the given class. + * + * @param clazz an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class clazz) { + StringBuilder stringBuilder = new StringBuilder(); + appendDescriptor(stringBuilder, clazz); + return stringBuilder.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param constructor a {@link Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor constructor) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + Class[] parameters = constructor.getParameterTypes(); + for (int i = 0; i < parameters.length; ++i) { + appendDescriptor(stringBuilder, parameters[i]); + } + return stringBuilder.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param method a {@link Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method method) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + Class[] parameters = method.getParameterTypes(); + for (int i = 0; i < parameters.length; ++i) { + appendDescriptor(stringBuilder, parameters[i]); + } + stringBuilder.append(')'); + appendDescriptor(stringBuilder, method.getReturnType()); + return stringBuilder.toString(); + } + + /** + * Appends the descriptor of the given class to the given string builder. + * + * @param stringBuilder the string builder to which the descriptor must be appended. + * @param clazz the class whose descriptor must be computed. + */ + private static void appendDescriptor(final StringBuilder stringBuilder, final Class clazz) { + Class currentClass = clazz; + while (currentClass.isArray()) { + stringBuilder.append('['); + currentClass = currentClass.getComponentType(); + } + if (currentClass.isPrimitive()) { + char descriptor; + if (currentClass == Integer.TYPE) { + descriptor = 'I'; + } else if (currentClass == Void.TYPE) { + descriptor = 'V'; + } else if (currentClass == Boolean.TYPE) { + descriptor = 'Z'; + } else if (currentClass == Byte.TYPE) { + descriptor = 'B'; + } else if (currentClass == Character.TYPE) { + descriptor = 'C'; + } else if (currentClass == Short.TYPE) { + descriptor = 'S'; + } else if (currentClass == Double.TYPE) { + descriptor = 'D'; + } else if (currentClass == Float.TYPE) { + descriptor = 'F'; + } else if (currentClass == Long.TYPE) { + descriptor = 'J'; + } else { + throw new AssertionError(); + } + stringBuilder.append(descriptor); + } else { + stringBuilder.append('L'); + String name = currentClass.getName(); + int nameLength = name.length(); + for (int i = 0; i < nameLength; ++i) { + char car = name.charAt(i); + stringBuilder.append(car == '.' ? '/' : car); + } + stringBuilder.append(';'); + } + } + + // ----------------------------------------------------------------------------------------------- + // Corresponding size and opcodes + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of values of this type. This method must not be used for method types. + * + * @return the size of values of this type, i.e., 2 for long and double, 0 for + * void and 1 otherwise. + */ + public int getSize() { + switch (sort) { + case VOID: + return 0; + case BOOLEAN: + case CHAR: + case BYTE: + case SHORT: + case INT: + case FLOAT: + case ARRAY: + case OBJECT: + case INTERNAL: + return 1; + case LONG: + case DOUBLE: + return 2; + default: + throw new AssertionError(); + } + } + + /** + * Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for + * method types. + * + * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD, + * IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and + * IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to this {@link Type}. For + * example, if this type is float and opcode is IRETURN, this method returns + * FRETURN. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + switch (sort) { case BOOLEAN: - return "boolean"; + case BYTE: + return opcode + (Opcodes.BALOAD - Opcodes.IALOAD); case CHAR: - return "char"; + return opcode + (Opcodes.CALOAD - Opcodes.IALOAD); + case SHORT: + return opcode + (Opcodes.SALOAD - Opcodes.IALOAD); + case INT: + return opcode; + case FLOAT: + return opcode + (Opcodes.FALOAD - Opcodes.IALOAD); + case LONG: + return opcode + (Opcodes.LALOAD - Opcodes.IALOAD); + case DOUBLE: + return opcode + (Opcodes.DALOAD - Opcodes.IALOAD); + case ARRAY: + case OBJECT: + case INTERNAL: + return opcode + (Opcodes.AALOAD - Opcodes.IALOAD); + case METHOD: + case VOID: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); + } + } else { + switch (sort) { + case VOID: + if (opcode != Opcodes.IRETURN) { + throw new UnsupportedOperationException(); + } + return Opcodes.RETURN; + case BOOLEAN: case BYTE: - return "byte"; + case CHAR: case SHORT: - return "short"; case INT: - return "int"; + return opcode; case FLOAT: - return "float"; + return opcode + (Opcodes.FRETURN - Opcodes.IRETURN); case LONG: - return "long"; + return opcode + (Opcodes.LRETURN - Opcodes.IRETURN); case DOUBLE: - return "double"; + return opcode + (Opcodes.DRETURN - Opcodes.IRETURN); case ARRAY: - StringBuffer b = new StringBuffer(getElementType().getClassName()); - for (int i = getDimensions(); i > 0; --i) { - b.append("[]"); - } - return b.toString(); case OBJECT: - return new String(buf, off, len).replace('/', '.'); + case INTERNAL: + if (opcode != Opcodes.ILOAD && opcode != Opcodes.ISTORE && opcode != Opcodes.IRETURN) { + throw new UnsupportedOperationException(); + } + return opcode + (Opcodes.ARETURN - Opcodes.IRETURN); + case METHOD: + throw new UnsupportedOperationException(); default: - return null; - } - } - - /** - * Returns the internal name of the class corresponding to this object or - * array type. The internal name of a class is its fully qualified name (as - * returned by Class.getName(), where '.' are replaced by '/'. This method - * should only be used for an object or array type. - * - * @return the internal name of the class corresponding to this object type. - */ - public String getInternalName() { - return new String(buf, off, len); - } - - /** - * Returns the argument types of methods of this type. This method should - * only be used for method types. - * - * @return the argument types of methods of this type. - */ - public Type[] getArgumentTypes() { - return getArgumentTypes(getDescriptor()); - } - - /** - * Returns the return type of methods of this type. This method should only - * be used for method types. - * - * @return the return type of methods of this type. - */ - public Type getReturnType() { - return getReturnType(getDescriptor()); - } - - /** - * Returns the size of the arguments and of the return value of methods of - * this type. This method should only be used for method types. - * - * @return the size of the arguments (plus one for the implicit this - * argument), argSize, and the size of the return value, retSize, - * packed into a single int i = (argSize << 2) | retSize - * (argSize is therefore equal to i >> 2, and retSize to - * i & 0x03). - */ - public int getArgumentsAndReturnSizes() { - return getArgumentsAndReturnSizes(getDescriptor()); - } - - // ------------------------------------------------------------------------ - // Conversion to type descriptors - // ------------------------------------------------------------------------ - - /** - * Returns the descriptor corresponding to this Java type. - * - * @return the descriptor corresponding to this Java type. - */ - public String getDescriptor() { - StringBuffer buf = new StringBuffer(); - getDescriptor(buf); - return buf.toString(); - } - - /** - * Returns the descriptor corresponding to the given argument and return - * types. - * - * @param returnType - * the return type of the method. - * @param argumentTypes - * the argument types of the method. - * @return the descriptor corresponding to the given argument and return - * types. - */ - public static String getMethodDescriptor(final Type returnType, - final Type... argumentTypes) { - StringBuffer buf = new StringBuffer(); - buf.append('('); - for (int i = 0; i < argumentTypes.length; ++i) { - argumentTypes[i].getDescriptor(buf); - } - buf.append(')'); - returnType.getDescriptor(buf); - return buf.toString(); - } - - /** - * Appends the descriptor corresponding to this Java type to the given - * string buffer. - * - * @param buf - * the string buffer to which the descriptor must be appended. - */ - private void getDescriptor(final StringBuffer buf) { - if (this.buf == null) { - // descriptor is in byte 3 of 'off' for primitive types (buf == - // null) - buf.append((char) ((off & 0xFF000000) >>> 24)); - } else if (sort == OBJECT) { - buf.append('L'); - buf.append(this.buf, off, len); - buf.append(';'); - } else { // sort == ARRAY || sort == METHOD - buf.append(this.buf, off, len); - } - } - - // ------------------------------------------------------------------------ - // Direct conversion from classes to type descriptors, - // without intermediate Type objects - // ------------------------------------------------------------------------ - - /** - * Returns the internal name of the given class. The internal name of a - * class is its fully qualified name, as returned by Class.getName(), where - * '.' are replaced by '/'. - * - * @param c - * an object or array class. - * @return the internal name of the given class. - */ - public static String getInternalName(final Class c) { - return c.getName().replace('.', '/'); - } - - /** - * Returns the descriptor corresponding to the given Java type. - * - * @param c - * an object class, a primitive class or an array class. - * @return the descriptor corresponding to the given class. - */ - public static String getDescriptor(final Class c) { - StringBuffer buf = new StringBuffer(); - getDescriptor(buf, c); - return buf.toString(); - } - - /** - * Returns the descriptor corresponding to the given constructor. - * - * @param c - * a {@link Constructor Constructor} object. - * @return the descriptor of the given constructor. - */ - public static String getConstructorDescriptor(final Constructor c) { - Class[] parameters = c.getParameterTypes(); - StringBuffer buf = new StringBuffer(); - buf.append('('); - for (int i = 0; i < parameters.length; ++i) { - getDescriptor(buf, parameters[i]); - } - return buf.append(")V").toString(); - } - - /** - * Returns the descriptor corresponding to the given method. - * - * @param m - * a {@link Method Method} object. - * @return the descriptor of the given method. - */ - public static String getMethodDescriptor(final Method m) { - Class[] parameters = m.getParameterTypes(); - StringBuffer buf = new StringBuffer(); - buf.append('('); - for (int i = 0; i < parameters.length; ++i) { - getDescriptor(buf, parameters[i]); - } - buf.append(')'); - getDescriptor(buf, m.getReturnType()); - return buf.toString(); - } - - /** - * Appends the descriptor of the given class to the given string buffer. - * - * @param buf - * the string buffer to which the descriptor must be appended. - * @param c - * the class whose descriptor must be computed. - */ - private static void getDescriptor(final StringBuffer buf, final Class c) { - Class d = c; - while (true) { - if (d.isPrimitive()) { - char car; - if (d == Integer.TYPE) { - car = 'I'; - } else if (d == Void.TYPE) { - car = 'V'; - } else if (d == Boolean.TYPE) { - car = 'Z'; - } else if (d == Byte.TYPE) { - car = 'B'; - } else if (d == Character.TYPE) { - car = 'C'; - } else if (d == Short.TYPE) { - car = 'S'; - } else if (d == Double.TYPE) { - car = 'D'; - } else if (d == Float.TYPE) { - car = 'F'; - } else /* if (d == Long.TYPE) */{ - car = 'J'; - } - buf.append(car); - return; - } else if (d.isArray()) { - buf.append('['); - d = d.getComponentType(); - } else { - buf.append('L'); - String name = d.getName(); - int len = name.length(); - for (int i = 0; i < len; ++i) { - char car = name.charAt(i); - buf.append(car == '.' ? '/' : car); - } - buf.append(';'); - return; - } - } - } - - // ------------------------------------------------------------------------ - // Corresponding size and opcodes - // ------------------------------------------------------------------------ - - /** - * Returns the size of values of this type. This method must not be used for - * method types. - * - * @return the size of values of this type, i.e., 2 for long and - * double, 0 for void and 1 otherwise. - */ - public int getSize() { - // the size is in byte 0 of 'off' for primitive types (buf == null) - return buf == null ? (off & 0xFF) : 1; - } - - /** - * Returns a JVM instruction opcode adapted to this Java type. This method - * must not be used for method types. - * - * @param opcode - * a JVM instruction opcode. This opcode must be one of ILOAD, - * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, - * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. - * @return an opcode that is similar to the given opcode, but adapted to - * this Java type. For example, if this type is float and - * opcode is IRETURN, this method returns FRETURN. - */ - public int getOpcode(final int opcode) { - if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { - // the offset for IALOAD or IASTORE is in byte 1 of 'off' for - // primitive types (buf == null) - return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4); - } else { - // the offset for other instructions is in byte 2 of 'off' for - // primitive types (buf == null) - return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4); - } - } - - // ------------------------------------------------------------------------ - // Equals, hashCode and toString - // ------------------------------------------------------------------------ - - /** - * Tests if the given object is equal to this type. - * - * @param o - * the object to be compared to this type. - * @return true if the given object is equal to this type. - */ - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Type)) { - return false; - } - Type t = (Type) o; - if (sort != t.sort) { - return false; - } - if (sort >= ARRAY) { - if (len != t.len) { - return false; - } - for (int i = off, j = t.off, end = i + len; i < end; i++, j++) { - if (buf[i] != t.buf[j]) { - return false; - } - } - } - return true; - } - - /** - * Returns a hash code value for this type. - * - * @return a hash code value for this type. - */ - @Override - public int hashCode() { - int hc = 13 * sort; - if (sort >= ARRAY) { - for (int i = off, end = i + len; i < end; i++) { - hc = 17 * (hc + buf[i]); - } - } - return hc; - } - - /** - * Returns a string representation of this type. - * - * @return the descriptor of this type. - */ - @Override - public String toString() { - return getDescriptor(); - } + throw new AssertionError(); + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Equals, hashCode and toString + // ----------------------------------------------------------------------------------------------- + + /** + * Tests if the given object is equal to this type. + * + * @param object the object to be compared to this type. + * @return true if the given object is equal to this type. + */ + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof Type)) { + return false; + } + Type other = (Type) object; + if ((sort == INTERNAL ? OBJECT : sort) != (other.sort == INTERNAL ? OBJECT : other.sort)) { + return false; + } + int begin = valueBegin; + int end = valueEnd; + int otherBegin = other.valueBegin; + int otherEnd = other.valueEnd; + // Compare the values. + if (end - begin != otherEnd - otherBegin) { + return false; + } + for (int i = begin, j = otherBegin; i < end; i++, j++) { + if (valueBuffer.charAt(i) != other.valueBuffer.charAt(j)) { + return false; + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hashCode = 13 * (sort == INTERNAL ? OBJECT : sort); + if (sort >= ARRAY) { + for (int i = valueBegin, end = valueEnd; i < end; i++) { + hashCode = 17 * (hashCode + valueBuffer.charAt(i)); + } + } + return hashCode; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } } diff --git a/src/jvm/clojure/asm/TypePath.java b/src/jvm/clojure/asm/TypePath.java new file mode 100644 index 0000000000..aaffa43b9e --- /dev/null +++ b/src/jvm/clojure/asm/TypePath.java @@ -0,0 +1,201 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package clojure.asm; + +/** + * The path to a type argument, wildcard bound, array element type, or static inner type within an + * enclosing type. + * + * @author Eric Bruneton + */ +public class TypePath { + + /** A type path step that steps into the element type of an array type. See {@link #getStep}. */ + public static final int ARRAY_ELEMENT = 0; + + /** A type path step that steps into the nested type of a class type. See {@link #getStep}. */ + public static final int INNER_TYPE = 1; + + /** A type path step that steps into the bound of a wildcard type. See {@link #getStep}. */ + public static final int WILDCARD_BOUND = 2; + + /** A type path step that steps into a type argument of a generic type. See {@link #getStep}. */ + public static final int TYPE_ARGUMENT = 3; + + /** + * The byte array where the 'type_path' structure - as defined in the Java Virtual Machine + * Specification (JVMS) - corresponding to this TypePath is stored. The first byte of the + * structure in this array is given by {@link #typePathOffset}. + * + * @see JVMS + * 4.7.20.2 + */ + private final byte[] typePathContainer; + + /** The offset of the first byte of the type_path JVMS structure in {@link #typePathContainer}. */ + private final int typePathOffset; + + /** + * Constructs a new TypePath. + * + * @param typePathContainer a byte array containing a type_path JVMS structure. + * @param typePathOffset the offset of the first byte of the type_path structure in + * typePathContainer. + */ + TypePath(final byte[] typePathContainer, final int typePathOffset) { + this.typePathContainer = typePathContainer; + this.typePathOffset = typePathOffset; + } + + /** + * Returns the length of this path, i.e. its number of steps. + * + * @return the length of this path. + */ + public int getLength() { + // path_length is stored in the first byte of a type_path. + return typePathContainer[typePathOffset]; + } + + /** + * Returns the value of the given step of this path. + * + * @param index an index between 0 and {@link #getLength()}, exclusive. + * @return one of {@link #ARRAY_ELEMENT}, {@link #INNER_TYPE}, {@link #WILDCARD_BOUND}, or {@link + * #TYPE_ARGUMENT}. + */ + public int getStep(final int index) { + // Returns the type_path_kind of the path element of the given index. + return typePathContainer[typePathOffset + 2 * index + 1]; + } + + /** + * Returns the index of the type argument that the given step is stepping into. This method should + * only be used for steps whose value is {@link #TYPE_ARGUMENT}. + * + * @param index an index between 0 and {@link #getLength()}, exclusive. + * @return the index of the type argument that the given step is stepping into. + */ + public int getStepArgument(final int index) { + // Returns the type_argument_index of the path element of the given index. + return typePathContainer[typePathOffset + 2 * index + 2]; + } + + /** + * Converts a type path in string form, in the format used by {@link #toString()}, into a TypePath + * object. + * + * @param typePath a type path in string form, in the format used by {@link #toString()}. May be + * null or empty. + * @return the corresponding TypePath object, or null if the path is empty. + */ + public static TypePath fromString(final String typePath) { + if (typePath == null || typePath.length() == 0) { + return null; + } + int typePathLength = typePath.length(); + ByteVector output = new ByteVector(typePathLength); + output.putByte(0); + int typePathIndex = 0; + while (typePathIndex < typePathLength) { + char c = typePath.charAt(typePathIndex++); + if (c == '[') { + output.put11(ARRAY_ELEMENT, 0); + } else if (c == '.') { + output.put11(INNER_TYPE, 0); + } else if (c == '*') { + output.put11(WILDCARD_BOUND, 0); + } else if (c >= '0' && c <= '9') { + int typeArg = c - '0'; + while (typePathIndex < typePathLength) { + c = typePath.charAt(typePathIndex++); + if (c >= '0' && c <= '9') { + typeArg = typeArg * 10 + c - '0'; + } else if (c == ';') { + break; + } else { + throw new IllegalArgumentException(); + } + } + output.put11(TYPE_ARGUMENT, typeArg); + } else { + throw new IllegalArgumentException(); + } + } + output.data[0] = (byte) (output.length / 2); + return new TypePath(output.data, 0); + } + + /** + * Returns a string representation of this type path. {@link #ARRAY_ELEMENT} steps are represented + * with '[', {@link #INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND} steps with '*' and {@link + * #TYPE_ARGUMENT} steps with their type argument index in decimal form followed by ';'. + */ + @Override + public String toString() { + int length = getLength(); + StringBuilder result = new StringBuilder(length * 2); + for (int i = 0; i < length; ++i) { + switch (getStep(i)) { + case ARRAY_ELEMENT: + result.append('['); + break; + case INNER_TYPE: + result.append('.'); + break; + case WILDCARD_BOUND: + result.append('*'); + break; + case TYPE_ARGUMENT: + result.append(getStepArgument(i)).append(';'); + break; + default: + throw new AssertionError(); + } + } + return result.toString(); + } + + /** + * Puts the type_path JVMS structure corresponding to the given TypePath into the given + * ByteVector. + * + * @param typePath a TypePath instance, or null for empty paths. + * @param output where the type path must be put. + */ + static void put(final TypePath typePath, final ByteVector output) { + if (typePath == null) { + output.putByte(0); + } else { + int length = typePath.typePathContainer[typePath.typePathOffset] * 2 + 1; + output.putByteArray(typePath.typePathContainer, typePath.typePathOffset, length); + } + } +} diff --git a/src/jvm/clojure/asm/TypeReference.java b/src/jvm/clojure/asm/TypeReference.java new file mode 100644 index 0000000000..8de8fb1972 --- /dev/null +++ b/src/jvm/clojure/asm/TypeReference.java @@ -0,0 +1,436 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package clojure.asm; + +/** + * A reference to a type appearing in a class, field or method declaration, or on an instruction. + * Such a reference designates the part of the class where the referenced type is appearing (e.g. an + * 'extends', 'implements' or 'throws' clause, a 'new' instruction, a 'catch' clause, a type cast, a + * local variable declaration, etc). + * + * @author Eric Bruneton + */ +public class TypeReference { + + /** + * The sort of type references that target a type parameter of a generic class. See {@link + * #getSort}. + */ + public static final int CLASS_TYPE_PARAMETER = 0x00; + + /** + * The sort of type references that target a type parameter of a generic method. See {@link + * #getSort}. + */ + public static final int METHOD_TYPE_PARAMETER = 0x01; + + /** + * The sort of type references that target the super class of a class or one of the interfaces it + * implements. See {@link #getSort}. + */ + public static final int CLASS_EXTENDS = 0x10; + + /** + * The sort of type references that target a bound of a type parameter of a generic class. See + * {@link #getSort}. + */ + public static final int CLASS_TYPE_PARAMETER_BOUND = 0x11; + + /** + * The sort of type references that target a bound of a type parameter of a generic method. See + * {@link #getSort}. + */ + public static final int METHOD_TYPE_PARAMETER_BOUND = 0x12; + + /** The sort of type references that target the type of a field. See {@link #getSort}. */ + public static final int FIELD = 0x13; + + /** The sort of type references that target the return type of a method. See {@link #getSort}. */ + public static final int METHOD_RETURN = 0x14; + + /** + * The sort of type references that target the receiver type of a method. See {@link #getSort}. + */ + public static final int METHOD_RECEIVER = 0x15; + + /** + * The sort of type references that target the type of a formal parameter of a method. See {@link + * #getSort}. + */ + public static final int METHOD_FORMAL_PARAMETER = 0x16; + + /** + * The sort of type references that target the type of an exception declared in the throws clause + * of a method. See {@link #getSort}. + */ + public static final int THROWS = 0x17; + + /** + * The sort of type references that target the type of a local variable in a method. See {@link + * #getSort}. + */ + public static final int LOCAL_VARIABLE = 0x40; + + /** + * The sort of type references that target the type of a resource variable in a method. See {@link + * #getSort}. + */ + public static final int RESOURCE_VARIABLE = 0x41; + + /** + * The sort of type references that target the type of the exception of a 'catch' clause in a + * method. See {@link #getSort}. + */ + public static final int EXCEPTION_PARAMETER = 0x42; + + /** + * The sort of type references that target the type declared in an 'instanceof' instruction. See + * {@link #getSort}. + */ + public static final int INSTANCEOF = 0x43; + + /** + * The sort of type references that target the type of the object created by a 'new' instruction. + * See {@link #getSort}. + */ + public static final int NEW = 0x44; + + /** + * The sort of type references that target the receiver type of a constructor reference. See + * {@link #getSort}. + */ + public static final int CONSTRUCTOR_REFERENCE = 0x45; + + /** + * The sort of type references that target the receiver type of a method reference. See {@link + * #getSort}. + */ + public static final int METHOD_REFERENCE = 0x46; + + /** + * The sort of type references that target the type declared in an explicit or implicit cast + * instruction. See {@link #getSort}. + */ + public static final int CAST = 0x47; + + /** + * The sort of type references that target a type parameter of a generic constructor in a + * constructor call. See {@link #getSort}. + */ + public static final int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48; + + /** + * The sort of type references that target a type parameter of a generic method in a method call. + * See {@link #getSort}. + */ + public static final int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49; + + /** + * The sort of type references that target a type parameter of a generic constructor in a + * constructor reference. See {@link #getSort}. + */ + public static final int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A; + + /** + * The sort of type references that target a type parameter of a generic method in a method + * reference. See {@link #getSort}. + */ + public static final int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B; + + /** + * The target_type and target_info structures - as defined in the Java Virtual Machine + * Specification (JVMS) - corresponding to this type reference. target_type uses one byte, and all + * the target_info union fields use up to 3 bytes (except localvar_target, handled with the + * specific method {@link MethodVisitor#visitLocalVariableAnnotation}). Thus, both structures can + * be stored in an int. + * + *

This int field stores target_type (called the TypeReference 'sort' in the public API of this + * class) in its most significant byte, followed by the target_info fields. Depending on + * target_type, 1, 2 or even 3 least significant bytes of this field are unused. target_info + * fields which reference bytecode offsets are set to 0 (these offsets are ignored in ClassReader, + * and recomputed in MethodWriter). + * + * @see JVMS + * 4.7.20 + * @see JVMS + * 4.7.20.1 + */ + private final int targetTypeAndInfo; + + /** + * Constructs a new TypeReference. + * + * @param typeRef the int encoded value of the type reference, as received in a visit method + * related to type annotations, such as {@link ClassVisitor#visitTypeAnnotation}. + */ + public TypeReference(final int typeRef) { + this.targetTypeAndInfo = typeRef; + } + + /** + * Returns a type reference of the given sort. + * + * @param sort one of {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link + * #LOCAL_VARIABLE}, {@link #RESOURCE_VARIABLE}, {@link #INSTANCEOF}, {@link #NEW}, {@link + * #CONSTRUCTOR_REFERENCE}, or {@link #METHOD_REFERENCE}. + * @return a type reference of the given sort. + */ + public static TypeReference newTypeReference(final int sort) { + return new TypeReference(sort << 24); + } + + /** + * Returns a reference to a type parameter of a generic class or method. + * + * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}. + * @param paramIndex the type parameter index. + * @return a reference to the given generic class or method type parameter. + */ + public static TypeReference newTypeParameterReference(final int sort, final int paramIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to a type parameter bound of a generic class or method. + * + * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}. + * @param paramIndex the type parameter index. + * @param boundIndex the type bound index within the above type parameters. + * @return a reference to the given generic class or method type parameter bound. + */ + public static TypeReference newTypeParameterBoundReference( + final int sort, final int paramIndex, final int boundIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16) | (boundIndex << 8)); + } + + /** + * Returns a reference to the super class or to an interface of the 'implements' clause of a + * class. + * + * @param itfIndex the index of an interface in the 'implements' clause of a class, or -1 to + * reference the super class of the class. + * @return a reference to the given super type of a class. + */ + public static TypeReference newSuperTypeReference(final int itfIndex) { + return new TypeReference((CLASS_EXTENDS << 24) | ((itfIndex & 0xFFFF) << 8)); + } + + /** + * Returns a reference to the type of a formal parameter of a method. + * + * @param paramIndex the formal parameter index. + * @return a reference to the type of the given method formal parameter. + */ + public static TypeReference newFormalParameterReference(final int paramIndex) { + return new TypeReference((METHOD_FORMAL_PARAMETER << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to the type of an exception, in a 'throws' clause of a method. + * + * @param exceptionIndex the index of an exception in a 'throws' clause of a method. + * @return a reference to the type of the given exception. + */ + public static TypeReference newExceptionReference(final int exceptionIndex) { + return new TypeReference((THROWS << 24) | (exceptionIndex << 8)); + } + + /** + * Returns a reference to the type of the exception declared in a 'catch' clause of a method. + * + * @param tryCatchBlockIndex the index of a try catch block (using the order in which they are + * visited with visitTryCatchBlock). + * @return a reference to the type of the given exception. + */ + public static TypeReference newTryCatchReference(final int tryCatchBlockIndex) { + return new TypeReference((EXCEPTION_PARAMETER << 24) | (tryCatchBlockIndex << 8)); + } + + /** + * Returns a reference to the type of a type argument in a constructor or method call or + * reference. + * + * @param sort one of {@link #CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link + * #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link + * #METHOD_REFERENCE_TYPE_ARGUMENT}. + * @param argIndex the type argument index. + * @return a reference to the type of the given type argument. + */ + public static TypeReference newTypeArgumentReference(final int sort, final int argIndex) { + return new TypeReference((sort << 24) | argIndex); + } + + /** + * Returns the sort of this type reference. + * + * @return one of {@link #CLASS_TYPE_PARAMETER}, {@link #METHOD_TYPE_PARAMETER}, {@link + * #CLASS_EXTENDS}, {@link #CLASS_TYPE_PARAMETER_BOUND}, {@link #METHOD_TYPE_PARAMETER_BOUND}, + * {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link + * #METHOD_FORMAL_PARAMETER}, {@link #THROWS}, {@link #LOCAL_VARIABLE}, {@link + * #RESOURCE_VARIABLE}, {@link #EXCEPTION_PARAMETER}, {@link #INSTANCEOF}, {@link #NEW}, + * {@link #CONSTRUCTOR_REFERENCE}, {@link #METHOD_REFERENCE}, {@link #CAST}, {@link + * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}. + */ + public int getSort() { + return targetTypeAndInfo >>> 24; + } + + /** + * Returns the index of the type parameter referenced by this type reference. This method must + * only be used for type references whose sort is {@link #CLASS_TYPE_PARAMETER}, {@link + * #METHOD_TYPE_PARAMETER}, {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link + * #METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter index. + */ + public int getTypeParameterIndex() { + return (targetTypeAndInfo & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the type parameter bound, within the type parameter {@link + * #getTypeParameterIndex}, referenced by this type reference. This method must only be used for + * type references whose sort is {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link + * #METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter bound index. + */ + public int getTypeParameterBoundIndex() { + return (targetTypeAndInfo & 0x0000FF00) >> 8; + } + + /** + * Returns the index of the "super type" of a class that is referenced by this type reference. + * This method must only be used for type references whose sort is {@link #CLASS_EXTENDS}. + * + * @return the index of an interface in the 'implements' clause of a class, or -1 if this type + * reference references the type of the super class. + */ + public int getSuperTypeIndex() { + return (short) ((targetTypeAndInfo & 0x00FFFF00) >> 8); + } + + /** + * Returns the index of the formal parameter whose type is referenced by this type reference. This + * method must only be used for type references whose sort is {@link #METHOD_FORMAL_PARAMETER}. + * + * @return a formal parameter index. + */ + public int getFormalParameterIndex() { + return (targetTypeAndInfo & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the exception, in a 'throws' clause of a method, whose type is referenced + * by this type reference. This method must only be used for type references whose sort is {@link + * #THROWS}. + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getExceptionIndex() { + return (targetTypeAndInfo & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the try catch block (using the order in which they are visited with + * visitTryCatchBlock), whose 'catch' type is referenced by this type reference. This method must + * only be used for type references whose sort is {@link #EXCEPTION_PARAMETER} . + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getTryCatchBlockIndex() { + return (targetTypeAndInfo & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the type argument referenced by this type reference. This method must only + * be used for type references whose sort is {@link #CAST}, {@link + * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}. + * + * @return a type parameter index. + */ + public int getTypeArgumentIndex() { + return targetTypeAndInfo & 0xFF; + } + + /** + * Returns the int encoded value of this type reference, suitable for use in visit methods related + * to type annotations, like visitTypeAnnotation. + * + * @return the int encoded value of this type reference. + */ + public int getValue() { + return targetTypeAndInfo; + } + + /** + * Puts the given target_type and target_info JVMS structures into the given ByteVector. + * + * @param targetTypeAndInfo a target_type and a target_info structures encoded as in {@link + * #targetTypeAndInfo}. LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported. + * @param output where the type reference must be put. + */ + static void putTarget(final int targetTypeAndInfo, final ByteVector output) { + switch (targetTypeAndInfo >>> 24) { + case CLASS_TYPE_PARAMETER: + case METHOD_TYPE_PARAMETER: + case METHOD_FORMAL_PARAMETER: + output.putShort(targetTypeAndInfo >>> 16); + break; + case FIELD: + case METHOD_RETURN: + case METHOD_RECEIVER: + output.putByte(targetTypeAndInfo >>> 24); + break; + case CAST: + case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case METHOD_INVOCATION_TYPE_ARGUMENT: + case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case METHOD_REFERENCE_TYPE_ARGUMENT: + output.putInt(targetTypeAndInfo); + break; + case CLASS_EXTENDS: + case CLASS_TYPE_PARAMETER_BOUND: + case METHOD_TYPE_PARAMETER_BOUND: + case THROWS: + case EXCEPTION_PARAMETER: + case INSTANCEOF: + case NEW: + case CONSTRUCTOR_REFERENCE: + case METHOD_REFERENCE: + output.put12(targetTypeAndInfo >>> 24, (targetTypeAndInfo & 0xFFFF00) >> 8); + break; + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/src/jvm/clojure/asm/commons/AdviceAdapter.java b/src/jvm/clojure/asm/commons/AdviceAdapter.java deleted file mode 100644 index 8ec38d2281..0000000000 --- a/src/jvm/clojure/asm/commons/AdviceAdapter.java +++ /dev/null @@ -1,625 +0,0 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ -package clojure.asm.commons; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import clojure.asm.Handle; -import clojure.asm.Label; -import clojure.asm.MethodVisitor; -import clojure.asm.Opcodes; -import clojure.asm.Type; - -/** - * A {@link clojure.asm.MethodVisitor} to insert before, after and around - * advices in methods and constructors. - *

- * The behavior for constructors is like this: - *

    - * - *
  1. as long as the INVOKESPECIAL for the object initialization has not been - * reached, every bytecode instruction is dispatched in the ctor code visitor
  2. - * - *
  3. when this one is reached, it is only added in the ctor code visitor and a - * JP invoke is added
  4. - * - *
  5. after that, only the other code visitor receives the instructions
  6. - * - *
- * - * @author Eugene Kuleshov - * @author Eric Bruneton - */ -public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { - - private static final Object THIS = new Object(); - - private static final Object OTHER = new Object(); - - protected int methodAccess; - - protected String methodDesc; - - private boolean constructor; - - private boolean superInitialized; - - private List stackFrame; - - private Map> branches; - - /** - * Creates a new {@link AdviceAdapter}. - * - * @param api - * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. - * @param mv - * the method visitor to which this adapter delegates calls. - * @param access - * the method's access flags (see {@link Opcodes}). - * @param name - * the method's name. - * @param desc - * the method's descriptor (see {@link Type Type}). - */ - protected AdviceAdapter(final int api, final MethodVisitor mv, - final int access, final String name, final String desc) { - super(api, mv, access, name, desc); - methodAccess = access; - methodDesc = desc; - constructor = "".equals(name); - } - - @Override - public void visitCode() { - mv.visitCode(); - if (constructor) { - stackFrame = new ArrayList(); - branches = new HashMap>(); - } else { - superInitialized = true; - onMethodEnter(); - } - } - - @Override - public void visitLabel(final Label label) { - mv.visitLabel(label); - if (constructor && branches != null) { - List frame = branches.get(label); - if (frame != null) { - stackFrame = frame; - branches.remove(label); - } - } - } - - @Override - public void visitInsn(final int opcode) { - if (constructor) { - int s; - switch (opcode) { - case RETURN: // empty stack - onMethodExit(opcode); - break; - case IRETURN: // 1 before n/a after - case FRETURN: // 1 before n/a after - case ARETURN: // 1 before n/a after - case ATHROW: // 1 before n/a after - popValue(); - onMethodExit(opcode); - break; - case LRETURN: // 2 before n/a after - case DRETURN: // 2 before n/a after - popValue(); - popValue(); - onMethodExit(opcode); - break; - case NOP: - case LALOAD: // remove 2 add 2 - case DALOAD: // remove 2 add 2 - case LNEG: - case DNEG: - case FNEG: - case INEG: - case L2D: - case D2L: - case F2I: - case I2B: - case I2C: - case I2S: - case I2F: - case ARRAYLENGTH: - break; - case ACONST_NULL: - case ICONST_M1: - case ICONST_0: - case ICONST_1: - case ICONST_2: - case ICONST_3: - case ICONST_4: - case ICONST_5: - case FCONST_0: - case FCONST_1: - case FCONST_2: - case F2L: // 1 before 2 after - case F2D: - case I2L: - case I2D: - pushValue(OTHER); - break; - case LCONST_0: - case LCONST_1: - case DCONST_0: - case DCONST_1: - pushValue(OTHER); - pushValue(OTHER); - break; - case IALOAD: // remove 2 add 1 - case FALOAD: // remove 2 add 1 - case AALOAD: // remove 2 add 1 - case BALOAD: // remove 2 add 1 - case CALOAD: // remove 2 add 1 - case SALOAD: // remove 2 add 1 - case POP: - case IADD: - case FADD: - case ISUB: - case LSHL: // 3 before 2 after - case LSHR: // 3 before 2 after - case LUSHR: // 3 before 2 after - case L2I: // 2 before 1 after - case L2F: // 2 before 1 after - case D2I: // 2 before 1 after - case D2F: // 2 before 1 after - case FSUB: - case FMUL: - case FDIV: - case FREM: - case FCMPL: // 2 before 1 after - case FCMPG: // 2 before 1 after - case IMUL: - case IDIV: - case IREM: - case ISHL: - case ISHR: - case IUSHR: - case IAND: - case IOR: - case IXOR: - case MONITORENTER: - case MONITOREXIT: - popValue(); - break; - case POP2: - case LSUB: - case LMUL: - case LDIV: - case LREM: - case LADD: - case LAND: - case LOR: - case LXOR: - case DADD: - case DMUL: - case DSUB: - case DDIV: - case DREM: - popValue(); - popValue(); - break; - case IASTORE: - case FASTORE: - case AASTORE: - case BASTORE: - case CASTORE: - case SASTORE: - case LCMP: // 4 before 1 after - case DCMPL: - case DCMPG: - popValue(); - popValue(); - popValue(); - break; - case LASTORE: - case DASTORE: - popValue(); - popValue(); - popValue(); - popValue(); - break; - case DUP: - pushValue(peekValue()); - break; - case DUP_X1: - s = stackFrame.size(); - stackFrame.add(s - 2, stackFrame.get(s - 1)); - break; - case DUP_X2: - s = stackFrame.size(); - stackFrame.add(s - 3, stackFrame.get(s - 1)); - break; - case DUP2: - s = stackFrame.size(); - stackFrame.add(s - 2, stackFrame.get(s - 1)); - stackFrame.add(s - 2, stackFrame.get(s - 1)); - break; - case DUP2_X1: - s = stackFrame.size(); - stackFrame.add(s - 3, stackFrame.get(s - 1)); - stackFrame.add(s - 3, stackFrame.get(s - 1)); - break; - case DUP2_X2: - s = stackFrame.size(); - stackFrame.add(s - 4, stackFrame.get(s - 1)); - stackFrame.add(s - 4, stackFrame.get(s - 1)); - break; - case SWAP: - s = stackFrame.size(); - stackFrame.add(s - 2, stackFrame.get(s - 1)); - stackFrame.remove(s); - break; - } - } else { - switch (opcode) { - case RETURN: - case IRETURN: - case FRETURN: - case ARETURN: - case LRETURN: - case DRETURN: - case ATHROW: - onMethodExit(opcode); - break; - } - } - mv.visitInsn(opcode); - } - - @Override - public void visitVarInsn(final int opcode, final int var) { - super.visitVarInsn(opcode, var); - if (constructor) { - switch (opcode) { - case ILOAD: - case FLOAD: - pushValue(OTHER); - break; - case LLOAD: - case DLOAD: - pushValue(OTHER); - pushValue(OTHER); - break; - case ALOAD: - pushValue(var == 0 ? THIS : OTHER); - break; - case ASTORE: - case ISTORE: - case FSTORE: - popValue(); - break; - case LSTORE: - case DSTORE: - popValue(); - popValue(); - break; - } - } - } - - @Override - public void visitFieldInsn(final int opcode, final String owner, - final String name, final String desc) { - mv.visitFieldInsn(opcode, owner, name, desc); - if (constructor) { - char c = desc.charAt(0); - boolean longOrDouble = c == 'J' || c == 'D'; - switch (opcode) { - case GETSTATIC: - pushValue(OTHER); - if (longOrDouble) { - pushValue(OTHER); - } - break; - case PUTSTATIC: - popValue(); - if (longOrDouble) { - popValue(); - } - break; - case PUTFIELD: - popValue(); - if (longOrDouble) { - popValue(); - popValue(); - } - break; - // case GETFIELD: - default: - if (longOrDouble) { - pushValue(OTHER); - } - } - } - } - - @Override - public void visitIntInsn(final int opcode, final int operand) { - mv.visitIntInsn(opcode, operand); - if (constructor && opcode != NEWARRAY) { - pushValue(OTHER); - } - } - - @Override - public void visitLdcInsn(final Object cst) { - mv.visitLdcInsn(cst); - if (constructor) { - pushValue(OTHER); - if (cst instanceof Double || cst instanceof Long) { - pushValue(OTHER); - } - } - } - - @Override - public void visitMultiANewArrayInsn(final String desc, final int dims) { - mv.visitMultiANewArrayInsn(desc, dims); - if (constructor) { - for (int i = 0; i < dims; i++) { - popValue(); - } - pushValue(OTHER); - } - } - - @Override - public void visitTypeInsn(final int opcode, final String type) { - mv.visitTypeInsn(opcode, type); - // ANEWARRAY, CHECKCAST or INSTANCEOF don't change stack - if (constructor && opcode == NEW) { - pushValue(OTHER); - } - } - - @Override - public void visitMethodInsn(final int opcode, final String owner, - final String name, final String desc) { - mv.visitMethodInsn(opcode, owner, name, desc); - if (constructor) { - Type[] types = Type.getArgumentTypes(desc); - for (int i = 0; i < types.length; i++) { - popValue(); - if (types[i].getSize() == 2) { - popValue(); - } - } - switch (opcode) { - // case INVOKESTATIC: - // break; - case INVOKEINTERFACE: - case INVOKEVIRTUAL: - popValue(); // objectref - break; - case INVOKESPECIAL: - Object type = popValue(); // objectref - if (type == THIS && !superInitialized) { - onMethodEnter(); - superInitialized = true; - // once super has been initialized it is no longer - // necessary to keep track of stack state - constructor = false; - } - break; - } - - Type returnType = Type.getReturnType(desc); - if (returnType != Type.VOID_TYPE) { - pushValue(OTHER); - if (returnType.getSize() == 2) { - pushValue(OTHER); - } - } - } - } - - @Override - public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, - Object... bsmArgs) { - mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); - if (constructor) { - Type[] types = Type.getArgumentTypes(desc); - for (int i = 0; i < types.length; i++) { - popValue(); - if (types[i].getSize() == 2) { - popValue(); - } - } - - Type returnType = Type.getReturnType(desc); - if (returnType != Type.VOID_TYPE) { - pushValue(OTHER); - if (returnType.getSize() == 2) { - pushValue(OTHER); - } - } - } - } - - @Override - public void visitJumpInsn(final int opcode, final Label label) { - mv.visitJumpInsn(opcode, label); - if (constructor) { - switch (opcode) { - case IFEQ: - case IFNE: - case IFLT: - case IFGE: - case IFGT: - case IFLE: - case IFNULL: - case IFNONNULL: - popValue(); - break; - case IF_ICMPEQ: - case IF_ICMPNE: - case IF_ICMPLT: - case IF_ICMPGE: - case IF_ICMPGT: - case IF_ICMPLE: - case IF_ACMPEQ: - case IF_ACMPNE: - popValue(); - popValue(); - break; - case JSR: - pushValue(OTHER); - break; - } - addBranch(label); - } - } - - @Override - public void visitLookupSwitchInsn(final Label dflt, final int[] keys, - final Label[] labels) { - mv.visitLookupSwitchInsn(dflt, keys, labels); - if (constructor) { - popValue(); - addBranches(dflt, labels); - } - } - - @Override - public void visitTableSwitchInsn(final int min, final int max, - final Label dflt, final Label... labels) { - mv.visitTableSwitchInsn(min, max, dflt, labels); - if (constructor) { - popValue(); - addBranches(dflt, labels); - } - } - - @Override - public void visitTryCatchBlock(Label start, Label end, Label handler, - String type) { - super.visitTryCatchBlock(start, end, handler, type); - if (constructor && !branches.containsKey(handler)) { - List stackFrame = new ArrayList(); - stackFrame.add(OTHER); - branches.put(handler, stackFrame); - } - } - - private void addBranches(final Label dflt, final Label[] labels) { - addBranch(dflt); - for (int i = 0; i < labels.length; i++) { - addBranch(labels[i]); - } - } - - private void addBranch(final Label label) { - if (branches.containsKey(label)) { - return; - } - branches.put(label, new ArrayList(stackFrame)); - } - - private Object popValue() { - return stackFrame.remove(stackFrame.size() - 1); - } - - private Object peekValue() { - return stackFrame.get(stackFrame.size() - 1); - } - - private void pushValue(final Object o) { - stackFrame.add(o); - } - - /** - * Called at the beginning of the method or after super class class call in - * the constructor.
- *
- * - * Custom code can use or change all the local variables, but should not - * change state of the stack. - */ - protected void onMethodEnter() { - } - - /** - * Called before explicit exit from the method using either return or throw. - * Top element on the stack contains the return value or exception instance. - * For example: - * - *
-     *   public void onMethodExit(int opcode) {
-     *     if(opcode==RETURN) {
-     *         visitInsn(ACONST_NULL);
-     *     } else if(opcode==ARETURN || opcode==ATHROW) {
-     *         dup();
-     *     } else {
-     *         if(opcode==LRETURN || opcode==DRETURN) {
-     *             dup2();
-     *         } else {
-     *             dup();
-     *         }
-     *         box(Type.getReturnType(this.methodDesc));
-     *     }
-     *     visitIntInsn(SIPUSH, opcode);
-     *     visitMethodInsn(INVOKESTATIC, owner, "onExit", "(Ljava/lang/Object;I)V");
-     *   }
-     *
-     *   // an actual call back method
-     *   public static void onExit(Object param, int opcode) {
-     *     ...
-     * 
- * - *
- *
- * - * Custom code can use or change all the local variables, but should not - * change state of the stack. - * - * @param opcode - * one of the RETURN, IRETURN, FRETURN, ARETURN, LRETURN, DRETURN - * or ATHROW - * - */ - protected void onMethodExit(int opcode) { - } - - // TODO onException, onMethodCall -} diff --git a/src/jvm/clojure/asm/commons/AnalyzerAdapter.java b/src/jvm/clojure/asm/commons/AnalyzerAdapter.java deleted file mode 100644 index 429d8db8b2..0000000000 --- a/src/jvm/clojure/asm/commons/AnalyzerAdapter.java +++ /dev/null @@ -1,920 +0,0 @@ -/*** - * ASM: a very small and fast Java bytecode manipulation framework - * Copyright (c) 2000-2011 INRIA, France Telecom - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ -package clojure.asm.commons; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import clojure.asm.Handle; -import clojure.asm.Label; -import clojure.asm.MethodVisitor; -import clojure.asm.Opcodes; -import clojure.asm.Type; - -/** - * A {@link MethodVisitor} that keeps track of stack map frame changes between - * {@link #visitFrame(int, int, Object[], int, Object[]) visitFrame} calls. This - * adapter must be used with the - * {@link clojure.asm.ClassReader#EXPAND_FRAMES} option. Each - * visitX instruction delegates to the next visitor in the chain, if any, - * and then simulates the effect of this instruction on the stack map frame, - * represented by {@link #locals} and {@link #stack}. The next visitor in the - * chain can get the state of the stack map frame before each instruction - * by reading the value of these fields in its visitX methods (this - * requires a reference to the AnalyzerAdapter that is before it in the chain). - * If this adapter is used with a class that does not contain stack map table - * attributes (i.e., pre Java 6 classes) then this adapter may not be able to - * compute the stack map frame for each instruction. In this case no exception - * is thrown but the {@link #locals} and {@link #stack} fields will be null for - * these instructions. - * - * @author Eric Bruneton - */ -public class AnalyzerAdapter extends MethodVisitor { - - /** - * List of the local variable slots for current execution - * frame. Primitive types are represented by {@link Opcodes#TOP}, - * {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, - * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or - * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by - * two elements, the second one being TOP). Reference types are represented - * by String objects (representing internal names), and uninitialized types - * by Label objects (this label designates the NEW instruction that created - * this uninitialized value). This field is null for unreachable - * instructions. - */ - public List locals; - - /** - * List of the operand stack slots for current execution frame. - * Primitive types are represented by {@link Opcodes#TOP}, - * {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, - * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or - * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by - * two elements, the second one being TOP). Reference types are represented - * by String objects (representing internal names), and uninitialized types - * by Label objects (this label designates the NEW instruction that created - * this uninitialized value). This field is null for unreachable - * instructions. - */ - public List stack; - - /** - * The labels that designate the next instruction to be visited. May be - * null. - */ - private List