diff --git a/src/taoensso/nippy.clj b/src/taoensso/nippy.clj index 4846c5e..ca1705e 100644 --- a/src/taoensso/nippy.clj +++ b/src/taoensso/nippy.clj @@ -894,51 +894,45 @@ (-run! (fn [in] (-freeze-with-meta! in out)) s))))) -(defn- write-serializable [^DataOutput out x ^String class-name] +(defn- write-serializable [^DataOutput out x] (when-debug (println (str "write-serializable: " (type x)))) - (let [class-name-ba (.getBytes class-name StandardCharsets/UTF_8) - len (alength class-name-ba)] + (when (and (instance? Serializable x) (not (fn? x))) + (let [class-name (.getName (class x))] ; Reflect + (when (freeze-serializable-allowed? class-name) + (let [class-name-ba (.getBytes class-name StandardCharsets/UTF_8) + len (alength class-name-ba)] - (enc/cond - (sm-count? len) (do (write-id out id-sz-quarantined-sm) (write-bytes-sm out class-name-ba)) - (md-count? len) (do (write-id out id-sz-quarantined-md) (write-bytes-md out class-name-ba)) - ;; :else (do (write-id out id-sz-quarantined-lg) (write-bytes-md out class-name-ba)) ; Unrealistic - :else (throw (ex-info "Serializable class name too long" {:name class-name}))) + (enc/cond + (sm-count? len) (do (write-id out id-sz-quarantined-sm) (write-bytes-sm out class-name-ba)) + (md-count? len) (do (write-id out id-sz-quarantined-md) (write-bytes-md out class-name-ba)) + ;; :else (do (write-id out id-sz-quarantined-lg) (write-bytes-md out class-name-ba)) ; Unrealistic + :else (throw (ex-info "Serializable class name too long" {:name class-name}))) - ;; Legacy: write object directly to out. - ;; (.writeObject (ObjectOutputStream. out) x) + ;; Legacy: write object directly to out. + ;; (.writeObject (ObjectOutputStream. out) x) - ;; Quarantined: write object to ba, then ba to out. - ;; We'll have object length during thaw, allowing us to skip readObject. - (let [quarantined-ba (ByteArrayOutputStream.)] - (.writeObject (ObjectOutputStream. (DataOutputStream. quarantined-ba)) x) - (write-bytes out (.toByteArray quarantined-ba))))) + ;; Quarantined: write object to ba, then ba to out. + ;; We'll have object length during thaw, allowing us to skip readObject. + (let [quarantined-ba (ByteArrayOutputStream.)] + (.writeObject (ObjectOutputStream. (DataOutputStream. quarantined-ba)) x) + (write-bytes out (.toByteArray quarantined-ba))) + + true))))) (defn- write-readable [^DataOutput out x] (when-debug (println (str "write-readable: " (type x)))) - (let [edn (enc/pr-edn x) - edn-ba (.getBytes ^String edn StandardCharsets/UTF_8) - len (alength edn-ba)] - (enc/cond - (sm-count? len) (do (write-id out id-reader-sm) (write-bytes-sm out edn-ba)) - (md-count? len) (do (write-id out id-reader-md) (write-bytes-md out edn-ba)) - :else (do (write-id out id-reader-lg) (write-bytes-lg out edn-ba))))) - -(defn try-write-serializable [out x] - (when (and (instance? Serializable x) (not (fn? x))) - (try - (let [class-name (.getName (class x))] ; Reflect - (when (freeze-serializable-allowed? class-name) - (write-serializable out x class-name) - true)) - (catch Throwable _ nil)))) - -(defn try-write-readable [out x] (when (impl/seems-readable? x) - (try - (write-readable out x) - true - (catch Throwable _ nil)))) + (let [edn (enc/pr-edn x) + edn-ba (.getBytes ^String edn StandardCharsets/UTF_8) + len (alength edn-ba)] + (enc/cond + (sm-count? len) (do (write-id out id-reader-sm) (write-bytes-sm out edn-ba)) + (md-count? len) (do (write-id out id-reader-md) (write-bytes-md out edn-ba)) + :else (do (write-id out id-reader-lg) (write-bytes-lg out edn-ba))) + true))) + +(defn ^:deprecated try-write-serializable [out x] (enc/catching (write-serializable out x))) +(defn ^:deprecated try-write-readable [out x] (enc/catching (write-readable out x))) (defn- try-pr-edn [x] (try @@ -956,13 +950,6 @@ :content (try-pr-edn x)}} out)) -(defn throw-unfreezable [x] - (let [t (type x)] - (throw - (ex-info (str "Unfreezable type: " t) - {:type t - :as-str (try-pr-edn x)})))) - ;; Public `-freeze-with-meta!` with different arg order (defn freeze-to-out! "Serializes arg (any Clojure data type) to a DataOutput. @@ -1197,13 +1184,21 @@ (write-unfreezable out x))) ;; Without ff - (or - (try-write-serializable out x) - (try-write-readable out x) + (enc/cond + :let [[r1 e1] (try [(write-serializable out x)] (catch Throwable t [nil t]))], r1 r1 + :let [[r2 e2] (try [(write-readable out x)] (catch Throwable t [nil t]))], r2 r2 - (when-let [fff *final-freeze-fallback*] (fff out x) true) ; Deprecated - - (throw-unfreezable x))))) + :if-let [fff *final-freeze-fallback*] (fff out x) ; Deprecated + :else + (let [t (type x)] + (throw + (ex-info (str "Failed to freeze type: " t) + (enc/assoc-some + {:type t + :as-str (try-pr-edn x)} + {:serializable-error e1 + :readable-error e2}) + (or e1 e2)))))))) ;;;; diff --git a/test/taoensso/nippy_tests.clj b/test/taoensso/nippy_tests.clj index 4527696..319c6ac 100644 --- a/test/taoensso/nippy_tests.clj +++ b/test/taoensso/nippy_tests.clj @@ -94,12 +94,12 @@ (let [n range-uint+] (= (thaw (freeze n)) n)) (let [n (- range-uint+)] (= (thaw (freeze n)) n))])) - (is (enc/throws? :ex-info "Unfreezable type" (nippy/freeze (fn [])))) + (is (enc/throws? :ex-info "Failed to freeze type" (nippy/freeze (fn [])))) (testing "Clojure v1.10+ metadata protocol extensions" - [(is (enc/throws? :ex-info "Unfreezable type" (nippy/freeze (with-meta [] {:a :A, 'b (fn [])})))) - (is (= {:a :A} (meta (nippy/thaw (nippy/freeze (with-meta [] {:a :A, 'b/c (fn [])})))))) - (is (= nil (meta (nippy/thaw (nippy/freeze (with-meta [] { 'b/c (fn [])}))))) + [(is (enc/throws? :ex-info "Failed to freeze type" (nippy/freeze (with-meta [] {:a :A, 'b (fn [])})))) + (is (= {:a :A} (meta (nippy/thaw (nippy/freeze (with-meta [] {:a :A, 'b/c (fn [])})))))) + (is (= nil (meta (nippy/thaw (nippy/freeze (with-meta [] { 'b/c (fn [])}))))) "Don't attach empty metadata")]) (is (gen-test 1600 [gen-data] (= gen-data (thaw (freeze gen-data)))) "Generative")])