Bug in try/finally?

20 views
Skip to first unread message

Brian Stiles

unread,
Aug 10, 2010, 2:36:01 AM8/10/10
to Clojure
The following succeeds:

(try
(prn 1)
(finally
(prn 2)
(doto (System/out) (.print "-") (.println "-"))))

prints:

1
2
--

The following fails (note the odd duplication of "2" in the output):

(try
(prn 1)
(finally
(prn 2)
(.. (System/out) (print "-") (println "-"))))

prints:


1
2
-2
-Exception in thread "main" java.lang.NullPointerException
(NO_SOURCE_FILE:0)
at clojure.lang.Compiler.eval(Compiler.java:4658)
at clojure.core$eval__5236.invoke(core.clj:2017)
at clojure.main$eval_opt__7411.invoke(main.clj:227)
at clojure.main$initialize__7418.invoke(main.clj:246)
at clojure.main$null_opt__7446.invoke(main.clj:271)
at clojure.main$main__7466.doInvoke(main.clj:346)
at clojure.lang.RestFn.invoke(RestFn.java:426)
at clojure.lang.Var.invoke(Var.java:363)
at clojure.lang.AFn.applyToHelper(AFn.java:175)
at clojure.lang.Var.applyTo(Var.java:476)
at clojure.main.main(main.java:37)
Caused by: java.lang.NullPointerException
at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:26)
at user$eval__1.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:4642)
... 10 more

Christian Vest Hansen

unread,
Aug 10, 2010, 9:31:19 AM8/10/10
to clo...@googlegroups.com
The bug has nothing to do with try-finally.

Take a look at the macro expansion:

user=> (macroexpand '(.. (System/out) (print "-") (println "-")))
(. (. (System/out) (print "-")) (println "-"))

You are trying to call 'println on what ever 'print returns. But
'print is a void method.

> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

--
Venlig hilsen / Kind regards,
Christian Vest Hansen.

Meikel Brandmeyer

unread,
Aug 10, 2010, 9:34:52 AM8/10/10
to Clojure
Hi,

On Aug 10, 8:36 am, Brian Stiles <brian.sti...@gmail.com> wrote:

> The following succeeds:
>
> (try
>   (prn 1)
>   (finally
>    (prn 2)
>    (doto (System/out) (.print "-") (.println "-"))))
>
> prints:
>
> 1
> 2
> --
>
> The following fails (note the odd duplication of "2" in the output):
>
> (try
>   (prn 1)
>   (finally
>    (prn 2)
>    (.. (System/out) (print "-") (println "-"))))
>
> prints:
>
> 1
> 2
> -2
> -Exception in thread "main" java.lang.NullPointerException

Of course it fails, because print is a void method and hence "returns"
nil. So you are calling print on nil leading to the NPE.

Why the 2 is printed twice... good question:

user=> (try (prn 1) (finally (prn 2) (.foo nil)))
1
2
2
java.lang.NullPointerException (NO_SOURCE_FILE:0)
user=> (let [] (try (prn 1) (finally (prn 2) (.foo nil))))
1
2
2
java.lang.NullPointerException (NO_SOURCE_FILE:0)
user=> (let [a (atom 2)] (try (prn 1) (finally (prn @a) (.foo nil))))
1
2
java.lang.NullPointerException (NO_SOURCE_FILE:0)
user=> (def a (atom 2))
#'user/a
user=> (try (prn 1) (finally (prn @a) (.foo nil)))
1
2
2
java.lang.NullPointerException (NO_SOURCE_FILE:0)

Sincerely
Meikel

Adrian Cuthbertson

unread,
Aug 10, 2010, 9:41:38 AM8/10/10
to clo...@googlegroups.com
Hi Brian,

System/out
#<PrintStream java.io.PrintStream@7d59ea8e>
(System/out)
#<PrintStream java.io.PrintStream@7d59ea8e>

both return the "out" PrintStream object, so
(.. System/out (print "-"))
-nil
(.. (System/out) (print "-"))
-nil
(.. System/out (print "-"))
-nil

all invoke the print method on the java PrintStream object and
correctly return nil
as does...

(.print System/out "-")
-nil

which is the alternative way of invoking a method on a java object.
Your

(doto (System/out) (.print "-") (.println "-"))

--

works correctly as you're calling the two print methods on the
System/out object,

but your

(.. (System/out) (print "-") (println "-"))

is trying to pass the result of the 2nd (println "-") as a extra arg
to the call,
hence the bomb. So actually it's nothing to do with try/finally.

-Hth, Adrian.

Laurent PETIT

unread,
Aug 10, 2010, 9:52:41 AM8/10/10
to clo...@googlegroups.com
Weird indeed:

user=> (def a (atom 1))
#'user/a
user=> (try (prn :test) (finally (swap! a inc) (.foo nil)))
:test
java.lang.NullPointerException (NO_SOURCE_FILE:0)
user=> @a
3

I would have expected 2 as a result, in any case ?

2010/8/10 Meikel Brandmeyer <m...@kotka.de>

joegg

unread,
Aug 10, 2010, 12:23:06 PM8/10/10
to Clojure
Laurent,

Looking through the output of javap -v, it looks to me like this is
roughly what's been emitted (please forgive the mix of clojure and
java):

try {
(prn :test)
(swap! a inc)
(.foo nil)
} finally {
(swap! a inc)
(.foo nil)
}

Which explains why @a is 3 -- I'm not sure this is intended, though.

Joe

joegg

unread,
Aug 10, 2010, 1:09:57 PM8/10/10
to Clojure
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/
Compiler.java
index f5684f1..af55660 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -1775,7 +1775,7 @@ public static class TryExpr implements Expr{
gen.visitTryCatchBlock(startTry, endTry,
clause.label, clause.c.getName().replace('.', '/'));
}
if(finallyExpr != null)
- gen.visitTryCatchBlock(startTry, endTryCatch,
finallyLabel, null);
+ gen.visitTryCatchBlock(startTry, endTry,
finallyLabel, null);
for(int i = 0; i < catchExprs.count(); i++)
{
CatchClause clause = (CatchClause)
catchExprs.nth(i);


This fixes the behavior we're seeing, but, ummm... might break other
things, I suppose. The tests I've written don't cover all the
expected behavior of try/catch/finally.

Joe

Chouser

unread,
Aug 10, 2010, 2:51:21 PM8/10/10
to clo...@googlegroups.com
On Tue, Aug 10, 2010 at 1:09 PM, joegg <joeg...@gmail.com> wrote:
> diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/
> Compiler.java
> index f5684f1..af55660 100644
> --- a/src/jvm/clojure/lang/Compiler.java
> +++ b/src/jvm/clojure/lang/Compiler.java
> @@ -1775,7 +1775,7 @@ public static class TryExpr implements Expr{
>                        gen.visitTryCatchBlock(startTry, endTry,
> clause.label, clause.c.getName().replace('.', '/'));
>                        }
>                if(finallyExpr != null)
> -                       gen.visitTryCatchBlock(startTry, endTryCatch,
> finallyLabel, null);
> +                       gen.visitTryCatchBlock(startTry, endTry,
> finallyLabel, null);
>                for(int i = 0; i < catchExprs.count(); i++)
>                        {
>                        CatchClause clause = (CatchClause)
> catchExprs.nth(i);
>
>
> This fixes the behavior we're seeing, but, ummm... might break other
> things, I suppose.  The tests I've written don't cover all the
> expected behavior of try/catch/finally.

That patch seems to essentially reverse this one:

http://github.com/clojure/clojure/commit/5e9f2b293b307aa7953cd390360d24549e542b92

...which suggests to me there must be a better solution, though I
don't see yet what it would be.
--Chouser

Chouser

unread,
Aug 10, 2010, 4:09:41 PM8/10/10
to clo...@googlegroups.com
On Tue, Aug 10, 2010 at 2:51 PM, Chouser <cho...@gmail.com> wrote:
>
> That patch seems to essentially reverse this one:
>
> http://github.com/clojure/clojure/commit/5e9f2b293b307aa7953cd390360d24549e542b92
>
> ...which suggests to me there must be a better solution, though I
> don't see yet what it would be.

Ok, it looks to me like the above commit was an insufficient
solution for the problem it was trying to solve. Since the
content of the finally block is emitted after the main try
block as well as after each catch block, a single finally handler
for all them together (which is what the above commit does)
cannot avoid potentially re-running some parts of the finally
clause.

To see what javac emits in similar circumstances, I wrote
a foo.java and disassmbled it here: http://gist.github.com/517865

It looks like javac protects the try block and each catch block
individually with separate entries in the exception table, all of
them pointing to the final finally block.

The patch at the top of that gist attempts to do the same thing,
and also fully reverses the earlier "endTryCatch" commit. This
passes all tests, including new ones Joe Gallo wrote based on
reports in this thread: http://gist.github.com/517871

--Chouser
http://joyofclojure.com/

Brian Stiles

unread,
Aug 10, 2010, 9:23:30 PM8/10/10
to Clojure
You guys are awesome! Less than 24 hour turnaround.

On Aug 10, 1:09 pm, Chouser <chou...@gmail.com> wrote:
> On Tue, Aug 10, 2010 at 2:51 PM, Chouser <chou...@gmail.com> wrote:
>
> > That patch seems to essentially reverse this one:
>
> >http://github.com/clojure/clojure/commit/5e9f2b293b307aa7953cd390360d...

Chouser

unread,
Aug 11, 2010, 9:02:46 AM8/11/10
to clo...@googlegroups.com
On Tue, Aug 10, 2010 at 1:09 PM, joegg <joeg...@gmail.com> wrote:
>
> This fixes the behavior we're seeing, but, ummm... might break other
> things, I suppose.  The tests I've written don't cover all the
> expected behavior of try/catch/finally.

You wrote some nice tests for these that could be included in
clojure-test, but I don't see your name on the contributor's
list: http://clojure.org/contributing

Have you sent in your CA?

--Chouser
http://joyofclojure.com/

Chouser

unread,
Aug 11, 2010, 10:06:54 AM8/11/10
to clo...@googlegroups.com, cloju...@googlegroups.com
On Wed, Aug 11, 2010 at 8:01 AM, Stuart Halloway
<stuart....@gmail.com> wrote:
> Ticket and patch welcome!

Done and done: https://www.assembla.com/spaces/clojure/tickets/422

--Chouser
http://joyofclojure.com/

joegallo

unread,
Aug 11, 2010, 10:59:09 AM8/11/10
to Clojure
On Aug 11, 9:02 am, Chouser <chou...@gmail.com> wrote:
> Have you sent in your CA?

It's in the capable hands of the USPS.

Joe
Reply all
Reply to author
Forward
0 new messages