Remove public fields without breaking binary compatibility
Sebastian Sickelmann
sebastian.sickelmann at gmx.de
Tue Sep 13 12:57:41 PDT 2011
Hi i created a small concept-demo how i think we can manage to remove
some flaws(public accessible fields) out of the jdk without breaking
binary-compatibility. I uploaded the "code of cencept" to my
github-incubator[0] and posted some description on my weblog[1]. It
would be nice to get some discussion/comments on this, even if it
destroys my dream that this can solve the above noted problem. I think
discussion on the mailing list should be fine. Comments on my weblog are
also welcome but i think discussion should went into the mailing-list.
I copy the weblog-text here in a mailing-list compatible format. Sorry
for the cross-post, i think core-libs-dev can be interessted in general
in removing such fields. And I hope some of the mlvm people can say
something about if this can be solved better on vm level.
[0]
https://github.com/picpromusic/incubator/tree/master/jdk/compatibleFieldAccess
[1] http://codingwizard.wordpress.com/2011/09/13/remove-flaws-in-java-apis/
... more links at the bottom of the mail
---WEBLOG--COPY
I think i made a small step in a concept how solutions that removes
design flaws (such as public fields) can be solved in an binary
compatible way. Public fields in public API are really nasty, and there
isn't a solution how this can be resolved without completely removing
this class (with a long deprecation phase in the meantime). It is
because the missing encapsulation for accessing this field gives you no
change to change behavoir (such as checking/permitting values and
state). It is this nasty (for public instance fields) GET_FIELD bytecode
instruction.
It is like Dalibor Topic told it[2] : " Breaking binary compatibility is
bad, bad, really, really bad"
But than in saw a video[3] Virtual Extension Method with Brain Goetz
which solves the compatibility problem introduced by extending the jdk
for lambda expressions. After that i created a small concept-prototype
which shows how it can work with simple bytecode transformations and
invoke dynamic. I think there is much room for improvement and maybe it
can be better implemented at the vm level, but actually i don't have the
knowledge to do so. And in special : [4]"If you have a golden hammer,...." .
I placed the implementation of the concept on my github incubator[5]
What is this doing?
1. The build.xml looks complicated but this is not the point here. It
compiles the needed things to show the concept here. There is much room
optimizing this, but i don't do it because it is really unnecessary.
2. The Problem is the class OLD[6]. It has a public field cause which
can be access be anyone. If you put this in an public API you can never
change/remove this field without breaking binary compatibility.
2.1. There is a class NEW[7] which introduces the incompatibility in
making the public field private. But is also introduces two methods that
allows us to access the field in a controlled way.
2.2 There is a class NEW2[8] also which is nearer on my original problem
(i tried to remove a public cause field in an exception-class in
openjdk). It can throw an exception if changing the cause in the same
manner java.lang.Throwable does it.
3. The Class GEN[9] generates three testclasses (TestOld,TestNew and
TestNew2) with the same testsequence with is like this:
OLD o = new OLD();
System.out.println(o.cause);
o.cause = new RuntimeException("NEW");
System.out.println(o.cause);
TestNew does it with NEW and TestNew2 does this with NEW2.
I have done it with class generating techniques because it would not
compile and it would need a really complicated build-file to show how it
works, so it was easier to generate some bytecode.
4. The Class Main[10] does an overall test with the three testclasses
TestOld, TestNew and TestNew2.
[java] <<OLD>>
[java] java.lang.RuntimeException: INIT_OLD
[java] java.lang.RuntimeException: NEW
[java] <<NEW>>
[java] Exception in thread "main" java.lang.IllegalAccessError:
tried to access field NEW.cause from class TestNew
[java] at TestNew.testIt(TestNew.txt:3)
[java] at Main.main(Main.java:7)
The access of the cause field
System.out.println(o.cause);
crashes.
The Test does not even reach the testcase TestNew2
But if you start the Main class with the jvmarg
-javaagent:transformer.jar the output is
[java] MOCKINJECT BCI
[java] <<OLD>>
[java] java.lang.RuntimeException: INIT_OLD
[java] java.lang.RuntimeException: NEW
[java] <<NEW>>
[java] java.lang.RuntimeException: INIT_NEW
[java] java.lang.RuntimeException: NEW
[java] <<NEW2>>
[java] java.lang.RuntimeException: INIT_NEW
[java] ***java.lang.IllegalStateException: Not allowed to change
I think this is realy cool. The methods getCause() and initCause are
used instead of the field in the cases where the field is not accessable.
4. How does this work?
The classes in the transformer.jar transforms the bytecode before
loading into the vm.
It replaces the GET_FIELD / PUT_FIELD instruction with something like:
INVOKEDYNAMIC cause (LNEW;)Ljava/lang/Throwable;
[Bootstrapper.getFunction(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
(6)]
INVOKEDYNAMIC cause (LNEW;Ljava/lang/Throwable;)V
[Bootstrapper.setFunction(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
(6)]
You can look the complete bytecode of the testclases if you search for
the .txt files in the tmp dir after executing the build script.
It invokes the bootstrap methods Bootstrapper.getFunction and
Bootstrapper.setFunction in the Bootstrapper[11] class.
5. The Bootstapper class connects the invokedynamic call with the field
if accessable or with the accordant annotated access method getCause or
initCause.
So what do you think? Please throw comments on me, also if you think
binary compatible accessors for public fields is a really bad idea. I
think there is much room for optimizations here, but first lets discuss
about the idea and the concept.
-- Sebastian
[2]
http://mail.openjdk.java.net/pipermail/core-libs-dev/2011-August/007539.html
[3] http://medianetwork.oracle.com/media/show/16999
[4] http://en.wikipedia.org/wiki/Law_of_the_instrument
[5]
https://github.com/picpromusic/incubator/tree/master/jdk/compatibleFieldAccess
[6]
https://github.com/picpromusic/incubator/blob/master/jdk/compatibleFieldAccess/testsrc/OLD.java#L1
[7]
https://github.com/picpromusic/incubator/blob/master/jdk/compatibleFieldAccess/testsrc/NEW.java#L1
[8]
https://github.com/picpromusic/incubator/blob/master/jdk/compatibleFieldAccess/testsrc/NEW2.java#L1
[9]
https://github.com/picpromusic/incubator/blob/master/jdk/compatibleFieldAccess/generator/GEN.java#L1
[10]
https://github.com/picpromusic/incubator/blob/master/jdk/compatibleFieldAccess/testsrc/Main.java#L1
[11]
https://github.com/picpromusic/incubator/blob/master/jdk/compatibleFieldAccess/src/Bootstrapper.java#L1
More information about the mlvm-dev
mailing list