proxy of base classes with evil ctors

6 views
Skip to first unread message

Chouser

unread,
Apr 2, 2010, 3:44:12 PM4/2/10
to cloju...@googlegroups.com
I continue to learn things about Java I'd rather not know...

Apparently if a ctor of a base class calls a method that's
overridden by a derived class, the *derived* class's
implementation is called (unlike in C++ where the base class's
own impl would be called). This is apparently not recommended
but it happens, and as Lau Jenson discovered some libraries
depend on this behavior.

Here's an example of just such an evil base class:

(ns joy.proxytest)

(gen-class
:name joy.proxytest.evilbase
:prefix "evilbase-"
:post-init ctor
:methods [[tragedy [] void]]
:impl-ns joy.proxytest)

(defn evilbase-ctor [this]
(println "evilctor before tragedy")
(.tragedy this)
(println "evilctor after tragedy"))

(defn evilbase-tragedy [this]
(println "this is meant to be like pure virtual"))

The idea here is this class is meant to be extended by a derived
class that implements it's own .tragedy method, which will be
called during object construction by evilbase.

If we try doing exactly that with 'proxy', we run into a problem:

(proxy [joy.proxytest.evilbase] []
(tragedy [] (println "my own tragedy")))
; evilctor before tragedy
; this is meant to be like pure virtual
; evilctor after tragedy

What's happening here is that proxy doesn't hook up our .tragedy
implementation until after it has finished creating the object.
So when evilbase calls .tragedy, the proxy falls back on the
.tragedy impl given by the base class -- evilbase itself in this
example.

I see no way to work around this using proxy as it is. For what
it's worth, implementing the derived class using gen-class does
work:

(gen-class
:name joy.proxytest.hope
:prefix "hope-"
:extends joy.proxytest.evilbase
:impl-ns joy.proxytest)

(defn hope-tragedy [this]
(println "gen-class gives us the power"))

(joy.proxytest.hope.)
; evilctor before tragedy
; gen-class gives us the power
; evilctor after tragedy

Anyway, I was curious if there was any appetite to fix this.
I do so dislike using gen-class compared to proxy and reify that
I thought it might be worth some effort.

The only fix for proxy that I've thought of would be have
a thread-local object that would point to the proxy function-map
from just before the proxy obj was created until just after it
was done being created, or until one of the proxy methods is
called, whichever comes first. Each proxy stub would check for
the instance's own function-map as it does now, but if the map
itself was nil it would fall back to checking the thread-local
map which if found would be promptly installed and used (and the
thread-local value cleared in case this proxy method went on to
create some other proxy object).

It's a bit messy, but I think it would work. The ASM required
would be a stretch for me, but it might be worth figuring out how
to do it, compared to trying to document the behavior in some
book or other...

--Chouser
http://joyofclojure.com/

Chas Emerick

unread,
Apr 9, 2010, 9:55:07 AM4/9/10
to cloju...@googlegroups.com
This accounts for probably 20% of all my genclass usage (which doesn't
represent a large amount to begin with, but anyway...). I'm not sure
if that is an implicit endorsement one way or the other.

I would say though, that it's definitely worth solving. This is a
particularly esoteric corner of Java's object model, and regardless of
the merits or failings of the model, if proxy is going to provide for
extension of concrete classes, should meet the contract of the host
platform w.r.t. the interop features it supports. 98% of programmers,
regardless of their history with Java, would not think about this
issue when using proxy; given the subtle bugs that it can lead to, if
there's any way to solve the problem, it's worth it.

2¢ from someone who wouldn't have a clue how to solve it himself. ;-)

- Chas

> --
> You received this message because you are subscribed to the Google
> Groups "Clojure Dev" group.
> To post to this group, send email to cloju...@googlegroups.com.
> To unsubscribe from this group, send email to clojure-dev...@googlegroups.com
> .
> For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en
> .
>

Chouser

unread,
Apr 12, 2010, 9:36:44 AM4/12/10
to cloju...@googlegroups.com
On Fri, Apr 2, 2010 at 3:44 PM, Chouser <cho...@gmail.com> wrote:
> I continue to learn things about Java I'd rather not know...
>
> Apparently if a ctor of a base class calls a method that's
> overridden by a derived class, the *derived* class's
> implementation is called (unlike in C++ where the base class's
> own impl would be called).  This is apparently not recommended
> but it happens, and as Lau Jenson discovered some libraries
> depend on this behavior.
>
[snip]

>
> Anyway, I was curious if there was any appetite to fix this.
> I do so dislike using gen-class compared to proxy and reify that
> I thought it might be worth some effort.

For the record, Rich addressed this in IRC:

"I'm ok with saying - sorry, the evil class made you use gen-class"
http://clojure-log.n01se.net/date/2010-04-12.html#08:58-09:08

--Chouser
http://joyofclojure.com/

Reply all
Reply to author
Forward
0 new messages