From 4669bf98656947e421c9662f3e915ae6fdad8224 Mon Sep 17 00:00:00 2001 From: Paul M Bauer Date: Tue, 11 Oct 2011 18:29:58 -0700 Subject: [PATCH] Added regression test for catching checked exceptions. --- src/script/run_tests.clj | 1 + test/clojure/test_clojure/try_catch.clj | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/script/run_tests.clj create mode 100755 test/clojure/test_clojure/try_catch.clj diff --git a/src/script/run_tests.clj b/src/script/run_tests.clj old mode 100644 new mode 100755 index 6720abd..1aa8b99 --- a/src/script/run_tests.clj +++ b/src/script/run_tests.clj @@ -50,6 +50,7 @@ clojure.test-clojure.string clojure.test-clojure.test clojure.test-clojure.test-fixtures clojure.test-clojure.transients +clojure.test-clojure.try-catch clojure.test-clojure.vars clojure.test-clojure.vectors ]) diff --git a/test/clojure/test_clojure/try_catch.clj b/test/clojure/test_clojure/try_catch.clj new file mode 100755 index 0000000..e53eb0b --- /dev/null +++ b/test/clojure/test_clojure/try_catch.clj @@ -0,0 +1,24 @@ +; 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: Paul M Bauer + +(ns clojure.test-clojure.try-catch + (:use clojure.test)) + +(defn- get-exception [expression] + (try (eval expression) + nil + (catch java.lang.Throwable t + t))) + +(deftest catch-receives-checked-exception + (are [expression expected-exception] (= expected-exception + (type (get-exception expression))) + "Eh, I'm pretty safe" nil + '(java.io.FileReader. "CAFEBABEx0/idonotexist") java.io.FileNotFoundException)) -- 1.7.5.4 From 0e917344dd7529a7519afe0dd988818fe9b19728 Mon Sep 17 00:00:00 2001 From: Ben Smith-Mannschott Date: Thu, 13 Oct 2011 21:01:55 +0200 Subject: [PATCH] CLJ-855: add test that goes through Reflector (The existing test produces the same wrapped exception symptom, but does so by going through eval.) --- src/jvm/clojure/test/ReflectorTryCatchFixture.java | 17 +++++++++++++++++ test/clojure/test_clojure/try_catch.clj | 15 +++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/jvm/clojure/test/ReflectorTryCatchFixture.java diff --git a/src/jvm/clojure/test/ReflectorTryCatchFixture.java b/src/jvm/clojure/test/ReflectorTryCatchFixture.java new file mode 100644 index 0000000..9256559 --- /dev/null +++ b/src/jvm/clojure/test/ReflectorTryCatchFixture.java @@ -0,0 +1,17 @@ +package clojure.test; + +public class ReflectorTryCatchFixture { + + public static void fail(Long x) throws Cookies { + throw new Cookies("Long"); + } + + public static void fail(Double y) throws Cookies { + throw new Cookies("Double"); + } + + public static class Cookies extends Exception { + public Cookies(String msg) { super(msg); } + } + +} diff --git a/test/clojure/test_clojure/try_catch.clj b/test/clojure/test_clojure/try_catch.clj index e53eb0b..328495a 100755 --- a/test/clojure/test_clojure/try_catch.clj +++ b/test/clojure/test_clojure/try_catch.clj @@ -9,7 +9,9 @@ ; Author: Paul M Bauer (ns clojure.test-clojure.try-catch - (:use clojure.test)) + (:use clojure.test) + (:import [clojure.test ReflectorTryCatchFixture + ReflectorTryCatchFixture$Cookies])) (defn- get-exception [expression] (try (eval expression) @@ -17,8 +19,17 @@ (catch java.lang.Throwable t t))) -(deftest catch-receives-checked-exception +(deftest catch-receives-checked-exception-from-eval (are [expression expected-exception] (= expected-exception (type (get-exception expression))) "Eh, I'm pretty safe" nil '(java.io.FileReader. "CAFEBABEx0/idonotexist") java.io.FileNotFoundException)) + + +(defn fail [x] + (ReflectorTryCatchFixture/fail x)) + +(deftest catch-receives-checked-exception-from-reflective-call + (is (thrown-with-msg? ReflectorTryCatchFixture$Cookies #"Long" (fail 1))) + (is (thrown-with-msg? ReflectorTryCatchFixture$Cookies #"Double" (fail 1.0)))) + -- 1.7.5.4 From a8a53a0c59291ca87e3b9c9f23dea3402259066d Mon Sep 17 00:00:00 2001 From: Ben Smith-Mannschott Date: Thu, 13 Oct 2011 22:47:06 +0200 Subject: [PATCH] CLJ-855: ClojureException is a RuntimeException It's intended as the base type for RuntimeExceptions which Clojure throws to make them distinguishable from Runtime exceptions produced from outside of Clojure, e.g. when Reflector calls a Java method that then turns around and thaws a RuntimeException. --- src/jvm/clojure/lang/ClojureException.java | 21 +++++++++++++++++++++ 1 files changed, 21 insertions(+), 0 deletions(-) create mode 100644 src/jvm/clojure/lang/ClojureException.java diff --git a/src/jvm/clojure/lang/ClojureException.java b/src/jvm/clojure/lang/ClojureException.java new file mode 100644 index 0000000..4e97ee2 --- /dev/null +++ b/src/jvm/clojure/lang/ClojureException.java @@ -0,0 +1,21 @@ +package clojure.lang; + +public class ClojureException extends RuntimeException { + +public ClojureException(String msg) { + super(msg); +} +public ClojureException(String msg, Throwable cause) { + super(msg, cause); +} + +public ClojureException(Throwable cause) { + super(cause); +} + +public ClojureException() { + super(); +} + +} + -- 1.7.5.4 From 0352a7ebe85cb9eadaaddff054d7a45e607d4006 Mon Sep 17 00:00:00 2001 From: Ben Smith-Mannschott Date: Thu, 13 Oct 2011 22:48:53 +0200 Subject: [PATCH] CLJ-855: WrappedException is for wrapping checked exceptions as RuntimeExceptions By using a WrappedException you signal to code catching it that the exception itself is unimportant. It should be unwrapped and its immediate cause examined. --- src/jvm/clojure/lang/WrappedException.java | 13 +++++++++++++ 1 files changed, 13 insertions(+), 0 deletions(-) create mode 100644 src/jvm/clojure/lang/WrappedException.java diff --git a/src/jvm/clojure/lang/WrappedException.java b/src/jvm/clojure/lang/WrappedException.java new file mode 100644 index 0000000..98ed267 --- /dev/null +++ b/src/jvm/clojure/lang/WrappedException.java @@ -0,0 +1,13 @@ +package clojure.lang; + +public class WrappedException extends ClojureException { + +public WrappedException(String msg, Throwable cause) { + super(msg, cause); +} + +public WrappedException(Throwable cause) { + super(cause); +} + +} -- 1.7.5.4 From 504594fad50c6a14eea8b166e9bf9c9063c45ca6 Mon Sep 17 00:00:00 2001 From: Ben Smith-Mannschott Date: Thu, 13 Oct 2011 22:55:28 +0200 Subject: [PATCH] CLJ-855: teach Util to make ClojureException or WrappedException as appropriate When Util.runtimeException is asked to wrap something that is not already a RuntimeException, we wrap it in a WrappedException instead of in a plain RuntimeException. --- src/jvm/clojure/lang/Util.java | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jvm/clojure/lang/Util.java b/src/jvm/clojure/lang/Util.java index 1fc0439..2d2e426 100644 --- a/src/jvm/clojure/lang/Util.java +++ b/src/jvm/clojure/lang/Util.java @@ -157,16 +157,16 @@ static public void clearCache(ReferenceQueue rq, ConcurrentHashMap Date: Thu, 13 Oct 2011 22:57:05 +0200 Subject: [PATCH] =?UTF-8?q?CLJ-855:=20rename=20(try=E2=80=A6)=20special=20fo?= =?UTF-8?q?rm=20to=20(try*=E2=80=A6)=20in=20Compiler.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/jvm/clojure/lang/Compiler.java | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 55e1268..9d9a162 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -49,7 +49,7 @@ static final Symbol THE_VAR = Symbol.intern("var"); static final Symbol DOT = Symbol.intern("."); static final Symbol ASSIGN = Symbol.intern("set!"); //static final Symbol TRY_FINALLY = Symbol.intern("try-finally"); -static final Symbol TRY = Symbol.intern("try"); +static final Symbol TRY = Symbol.intern("try*"); static final Symbol CATCH = Symbol.intern("catch"); static final Symbol FINALLY = Symbol.intern("finally"); static final Symbol THROW = Symbol.intern("throw"); -- 1.7.5.4 From 5dd8b0eb1e48759b8b905f4e7298a4c7087b1160 Mon Sep 17 00:00:00 2001 From: Ben Smith-Mannschott Date: Thu, 13 Oct 2011 22:58:11 +0200 Subject: [PATCH] CLJ-855: use try* in place of try in core.clj, io.clj and evaluation.clj (a test) --- src/clj/clojure/core.clj | 21 +++++++++++---------- src/clj/clojure/java/io.clj | 4 ++-- test/clojure/test_clojure/evaluation.clj | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index b1801e9..0e7f27b 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -1495,13 +1495,14 @@ [^clojure.lang.Named x] (. x (getNamespace))) + (defmacro locking "Executes exprs in an implicit do, while holding the monitor of x. Will release the monitor of x in all circumstances." {:added "1.0"} [x & body] `(let [lockee# ~x] - (try + (try* (monitor-enter lockee#) ~@body (finally @@ -1707,7 +1708,7 @@ pop-thread-bindings wrapped in a try-finally! (push-thread-bindings bindings) - (try + (try* ... (finally (pop-thread-bindings)))" @@ -1753,7 +1754,7 @@ (seq ret))))] `(let [] (push-thread-bindings (hash-map ~@(var-ize bindings))) - (try + (try* ~@body (finally (pop-thread-bindings)))))) @@ -1766,7 +1767,7 @@ :static true} [binding-map f & args] (push-thread-bindings binding-map) - (try + (try* (apply f args) (finally (pop-thread-bindings)))) @@ -3386,7 +3387,7 @@ (cond (= (count bindings) 0) `(do ~@body) (symbol? (bindings 0)) `(let ~(subvec bindings 0 2) - (try + (try* (with-open ~(subvec bindings 2) ~@body) (finally (. ~(bindings 0) close)))) @@ -3862,7 +3863,7 @@ `(let [~@(interleave (take-nth 2 name-vals-vec) (repeat '(.. clojure.lang.Var create setDynamic)))] (. clojure.lang.Var (pushThreadBindings (hash-map ~@name-vals-vec))) - (try + (try* ~@body (finally (. clojure.lang.Var (popThreadBindings)))))) @@ -4722,7 +4723,7 @@ NIL (Object.) ;nil sentinel since LBQ doesn't support nils agt (agent (seq s)) fill (fn [s] - (try + (try* (loop [[x & xs :as s] s] (if s (if (.offer q (if (nil? x) NIL x)) @@ -5058,7 +5059,7 @@ `((fn loading# [] (. clojure.lang.Var (pushThreadBindings {clojure.lang.Compiler/LOADER (.getClassLoader (.getClass ^Object loading#))})) - (try + (try* ~@body (finally (. clojure.lang.Var (popThreadBindings))))))) @@ -6056,7 +6057,7 @@ clojure.lang.IBlockingDeref (deref [_ timeout-ms timeout-val] - (try (.get fut timeout-ms java.util.concurrent.TimeUnit/MILLISECONDS) + (try* (.get fut timeout-ms java.util.concurrent.TimeUnit/MILLISECONDS) (catch java.util.concurrent.TimeoutException e timeout-val))) clojure.lang.IPending @@ -6479,7 +6480,7 @@ (.bindRoot ^clojure.lang.Var a-var a-val))) old-vals (zipmap (keys binding-map) (map deref (keys binding-map)))] - (try + (try* (root-bind binding-map) (func) (finally diff --git a/src/clj/clojure/java/io.clj b/src/clj/clojure/java/io.clj index 20840e4..d5f3eed 100644 --- a/src/clj/clojure/java/io.clj +++ b/src/clj/clojure/java/io.clj @@ -246,12 +246,12 @@ IOFactory (assoc default-streams-impl :make-input-stream (fn [^String x opts] - (try + (try* (make-input-stream (URL. x) opts) (catch MalformedURLException e (make-input-stream (File. x) opts)))) :make-output-stream (fn [^String x opts] - (try + (try* (make-output-stream (URL. x) opts) (catch MalformedURLException err (make-output-stream (File. x) opts)))))) diff --git a/test/clojure/test_clojure/evaluation.clj b/test/clojure/test_clojure/evaluation.clj index 62609ee..70b87f2 100644 --- a/test/clojure/test_clojure/evaluation.clj +++ b/test/clojure/test_clojure/evaluation.clj @@ -158,7 +158,7 @@ 5. It is an error." ; First - (doall (for [form '(def if do let quote var fn loop recur throw try + (doall (for [form '(def if do let quote var fn loop recur throw try* monitor-enter monitor-exit)] (is (thrown? Compiler$CompilerException (eval form))))) (let [if "foo"] -- 1.7.5.4 From c1c533319efb08608d8af61dabb25b9b7153a6d3 Mon Sep 17 00:00:00 2001 From: Ben Smith-Mannschott Date: Thu, 13 Oct 2011 22:59:20 +0200 Subject: [PATCH] CLJ-855: provide try as a macro built on try* for use outside of core The try macro transparently unwraps (only!) WrappedException, passing the cause on to be handled by one of its catch clauses. This appears to solve CLJ-855: - The Clojure internals written in Java can continue to wrap and rethrow checked exceptions so as not to be forced to declare "throws Exception" everywhere. - Clojure code doesn't need to deal with the possiblity that exceptions produced by Java code it's calling via interop might -- or might not -- show up wrapped in a RuntimeException. - Core code that runs before the try macro has been defined or any code that really does want to catch a WrappedException can use the try* special form. --- src/clj/clojure/core.clj | 19 +++++++++++++++++++ 1 files changed, 19 insertions(+), 0 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 0e7f27b..3d8741c 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6505,3 +6505,22 @@ "Returns true if a value has been produced for a promise, delay, future or lazy sequence." {:added "1.3"} [^clojure.lang.IPending x] (.isRealized x)) + +(defmacro try + "(try expr* catch-clause* finally-clause?) +catch-clause -> (catch classname name expr*) +finally-clause -> (finally expr*) + +The exprs are evaluated and, if no exceptions occur, the value of the last is returned. If an exception occurs and catch clauses are provided, each is examined in turn and the first for which the thrown exception is an instance of the named class is considered a matching catch clause. If there is a matching catch clause, its exprs are evaluated in a context in which name is bound to the thrown exception, and the value of the last is the return value of the function. If there is no matching catch clause, the exception propagates out of the function. Before returning, normally or abnormally, any finally exprs will be evaluated for their side effects." + {:added "1.4"} + [ & body ] + (let [clause? #(and (seq? %) (#{'catch 'finally} (first %))) + exprs (remove clause? body) + clauses (filter clause? body)] + `(try* + (try* + ~@exprs + (catch clojure.lang.WrappedException we# + (throw (. we# getCause)))) + ~@clauses))) + -- 1.7.5.4