From forax at univ-mlv.fr Tue Jun 11 09:21:12 2013 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 11 Jun 2013 18:21:12 +0200 Subject: Hiding the lambda proxy frame In-Reply-To: <51B7249D.4050109@oracle.com> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> Message-ID: <51B74E78.6060807@univ-mlv.fr> On 06/11/2013 03:22 PM, Brian Goetz wrote: > Why wouldn't you bring this to the EG list instead? Sure, it was an implementation detail for me, hence the post on lambda-dev. So from lambda-dev: Currently when you print a stacktrace that show a call inside a lambda, users can see the the frame corresponding to the method of the generated proxy. By example: java.lang.Throwable StackTraceTest.lambda$0(StackTraceTest.java:6) StackTraceTest$$Lambda$1.run(Unknown Source) <--- ugly, isn't it StackTraceTest.main(StackTraceTest.java:8) I think this line should not be visible to user, it doesn't provide useful information, just make the stack trace longer than it should. There is an annotation LambdaForm.Hidden that you can use to mark method that should be hidden when dumping the stack trace. (Maybe the annotation should be a top-level by the way, add John in CC given he is the creator of this annotation) This part was not in the original mail: Moreover, offline, one of my friend point me to a presentation of Jamie Allen http://jaxenter.com/what-you-need-to-know-about-lambdas-by-jamie-allen-47356.html on the same subject at JAXConf 2013. I've just finished to see it. He raises a good point that the compiler translation should try to provide a better name. By example, Runnable myRunnable = () -> System.out.println("hello"); should result in a lambda that instead to be called "lambda$0", instead the compiler should try to name it something like lambda$myRunnable (or lambda$myRunnable0, ..., if there is a collision) if it's possible. I think we already discuss about something like that in the light of serialization, but not in the stack trace context. R?mi From brian.goetz at oracle.com Tue Jun 11 10:23:58 2013 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 11 Jun 2013 13:23:58 -0400 Subject: Hiding the lambda proxy frame In-Reply-To: <51B74E78.6060807@univ-mlv.fr> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> Message-ID: <51B75D2E.9040604@oracle.com> > Moreover, offline, one of my friend point me to a presentation of Jamie > Allen > http://jaxenter.com/what-you-need-to-know-about-lambdas-by-jamie-allen-47356.html Note also that those slides show that we have some errors in our line number here? > He raises a good point that the compiler translation should try to > provide a better name. > By example, > Runnable myRunnable = () -> System.out.println("hello"); > should result in a lambda that instead to be called "lambda$0", > instead the compiler should try to name it something like lambda$myRunnable > (or lambda$myRunnable0, ..., if there is a collision) if it's possible. > > I think we already discuss about something like that in the light of > serialization, > but not in the stack trace context. We did discuss this in the stack trace context at least once, but apparently we never converged. And the two problems interact; things that enhance readability sometimes come at the expense of name stability. From Robert.Field at oracle.com Tue Jun 11 10:47:19 2013 From: Robert.Field at oracle.com (Robert Field) Date: Tue, 11 Jun 2013 10:47:19 -0700 Subject: Hiding the lambda proxy frame In-Reply-To: <51B74E78.6060807@univ-mlv.fr> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> Message-ID: <51B762A7.7040909@oracle.com> Another take on naming, to some extent the "run" call is documenting the SAM call, or would be with a better name: java.lang.Throwable StackTraceTest.lambda$0(StackTraceTest.java:6) StackTraceTest$$Lambda$*Runnable*.run(Unknown Source) <--- not ugly, is it StackTraceTest.main(StackTraceTest.java:8) And that name is stable. -Robert On 6/11/13 9:21 AM, Remi Forax wrote: > On 06/11/2013 03:22 PM, Brian Goetz wrote: >> Why wouldn't you bring this to the EG list instead? > > Sure, it was an implementation detail for me, hence the post on > lambda-dev. > > So from lambda-dev: > Currently when you print a stacktrace that show a call inside a lambda, > users can see the the frame corresponding to the method of the generated > proxy. > > By example: > java.lang.Throwable > StackTraceTest.lambda$0(StackTraceTest.java:6) > StackTraceTest$$Lambda$1.run(Unknown Source) <--- ugly, > isn't it > StackTraceTest.main(StackTraceTest.java:8) > > I think this line should not be visible to user, it doesn't provide > useful information, > just make the stack trace longer than it should. > > There is an annotation LambdaForm.Hidden that you can use to mark method > that should be hidden when dumping the stack trace. > (Maybe the annotation should be a top-level by the way, add John in CC > given > he is the creator of this annotation) > > This part was not in the original mail: > Moreover, offline, one of my friend point me to a presentation of > Jamie Allen > http://jaxenter.com/what-you-need-to-know-about-lambdas-by-jamie-allen-47356.html > > on the same subject at JAXConf 2013. I've just finished to see it. > > He raises a good point that the compiler translation should try to > provide a better name. > By example, > Runnable myRunnable = () -> System.out.println("hello"); > should result in a lambda that instead to be called "lambda$0", > instead the compiler should try to name it something like > lambda$myRunnable > (or lambda$myRunnable0, ..., if there is a collision) if it's possible. > > I think we already discuss about something like that in the light of > serialization, > but not in the stack trace context. > > R?mi > > From forax at univ-mlv.fr Tue Jun 11 14:34:08 2013 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 11 Jun 2013 23:34:08 +0200 Subject: Hiding the lambda proxy frame In-Reply-To: <51B75D2E.9040604@oracle.com> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> <51B75D2E.9040604@oracle.com> Message-ID: <51B797D0.9060205@univ-mlv.fr> On 06/11/2013 07:23 PM, Brian Goetz wrote: > >> He raises a good point that the compiler translation should try to >> provide a better name. >> By example, >> Runnable myRunnable = () -> System.out.println("hello"); >> should result in a lambda that instead to be called "lambda$0", >> instead the compiler should try to name it something like >> lambda$myRunnable >> (or lambda$myRunnable0, ..., if there is a collision) if it's possible. >> >> I think we already discuss about something like that in the light of >> serialization, >> but not in the stack trace context. > > We did discuss this in the stack trace context at least once, but > apparently we never converged. And the two problems interact; things > that enhance readability sometimes come at the expense of name stability. Name stability is only needed when serializing a lambda, right ? R?mi From brian.goetz at oracle.com Tue Jun 11 14:35:16 2013 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 11 Jun 2013 17:35:16 -0400 Subject: Hiding the lambda proxy frame In-Reply-To: <51B797D0.9060205@univ-mlv.fr> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> <51B75D2E.9040604@oracle.com> <51B797D0.9060205@univ-mlv.fr> Message-ID: <51B79814.6010805@oracle.com> Right. On 6/11/2013 5:34 PM, Remi Forax wrote: > On 06/11/2013 07:23 PM, Brian Goetz wrote: > >> >>> He raises a good point that the compiler translation should try to >>> provide a better name. >>> By example, >>> Runnable myRunnable = () -> System.out.println("hello"); >>> should result in a lambda that instead to be called "lambda$0", >>> instead the compiler should try to name it something like >>> lambda$myRunnable >>> (or lambda$myRunnable0, ..., if there is a collision) if it's possible. >>> >>> I think we already discuss about something like that in the light of >>> serialization, >>> but not in the stack trace context. >> >> We did discuss this in the stack trace context at least once, but >> apparently we never converged. And the two problems interact; things >> that enhance readability sometimes come at the expense of name stability. > > Name stability is only needed when serializing a lambda, right ? > > R?mi > From forax at univ-mlv.fr Tue Jun 11 14:43:27 2013 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 11 Jun 2013 23:43:27 +0200 Subject: Hiding the lambda proxy frame In-Reply-To: <51B79814.6010805@oracle.com> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> <51B75D2E.9040604@oracle.com> <51B797D0.9060205@univ-mlv.fr> <51B79814.6010805@oracle.com> Message-ID: <51B799FF.6080307@univ-mlv.fr> On 06/11/2013 11:35 PM, Brian Goetz wrote: > Right. so for a non serializable lambda, the compiler can use the name of the local variable used for the capture conversion if available. The cost is that the compiler needs to be smarter. An additional benefit can be a better* toString(). R?mi * I know, we haven't decided to have meaningful toString > > On 6/11/2013 5:34 PM, Remi Forax wrote: >> On 06/11/2013 07:23 PM, Brian Goetz wrote: >> >>> >>>> He raises a good point that the compiler translation should try to >>>> provide a better name. >>>> By example, >>>> Runnable myRunnable = () -> System.out.println("hello"); >>>> should result in a lambda that instead to be called "lambda$0", >>>> instead the compiler should try to name it something like >>>> lambda$myRunnable >>>> (or lambda$myRunnable0, ..., if there is a collision) if it's >>>> possible. >>>> >>>> I think we already discuss about something like that in the light of >>>> serialization, >>>> but not in the stack trace context. >>> >>> We did discuss this in the stack trace context at least once, but >>> apparently we never converged. And the two problems interact; things >>> that enhance readability sometimes come at the expense of name >>> stability. >> >> Name stability is only needed when serializing a lambda, right ? >> >> R?mi >> From Daniel_Heidinga at ca.ibm.com Wed Jun 12 13:41:43 2013 From: Daniel_Heidinga at ca.ibm.com (Daniel Heidinga) Date: Wed, 12 Jun 2013 16:41:43 -0400 Subject: Hiding the lambda proxy frame In-Reply-To: <51B74E78.6060807@univ-mlv.fr> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> Message-ID: The more pressing concern from the presentation is whether the source file and line number for the lambda is correct. Ensuring we get this right makes a lot of sense. A stacktrace is a powerful tool for debugging and allowing frames to be hidden decreases the value of the stacktrace. With this behaviour change, the stacktrace and debugger will disagree on what the stack looks like - which is needlessly confusing. Personally, I hate having my debugging tools lie to me so I'd rather see accurate stacktraces. An annotation that hides stack frames is too easy to abuse, even if you have the best of intentions (like hiding implementation details). It will be over-used and lead to significant difficulties in finding where errors occurred. How hidden are LambdaForms$Hidden frames? Are they only hidden from stacktraces or do APIs like sun.reflect.Reflection.getCallerClass() also skip these hidden frames? --Dan > > On 06/11/2013 03:22 PM, Brian Goetz wrote: > > Why wouldn't you bring this to the EG list instead? > > Sure, it was an implementation detail for me, hence the post on lambda-dev. > > So from lambda-dev: > Currently when you print a stacktrace that show a call inside a lambda, > users can see the the frame corresponding to the method of the generated > proxy. > > By example: > java.lang.Throwable > StackTraceTest.lambda$0(StackTraceTest.java:6) > StackTraceTest$$Lambda$1.run(Unknown Source) <--- ugly, isn't it > StackTraceTest.main(StackTraceTest.java:8) > > I think this line should not be visible to user, it doesn't provide > useful information, > just make the stack trace longer than it should. > > There is an annotation LambdaForm.Hidden that you can use to mark method > that should be hidden when dumping the stack trace. > (Maybe the annotation should be a top-level by the way, add John in CC given > he is the creator of this annotation) > > This part was not in the original mail: > Moreover, offline, one of my friend point me to a presentation of Jamie > Allen > http://jaxenter.com/what-you-need-to-know-about-lambdas-by-jamie- > allen-47356.html > on the same subject at JAXConf 2013. I've just finished to see it. > > He raises a good point that the compiler translation should try to > provide a better name. > By example, > Runnable myRunnable = () -> System.out.println("hello"); > should result in a lambda that instead to be called "lambda$0", > instead the compiler should try to name it something like lambda $myRunnable > (or lambda$myRunnable0, ..., if there is a collision) if it's possible. > > I think we already discuss about something like that in the light of > serialization, > but not in the stack trace context. > > R?mi > > From john.r.rose at oracle.com Wed Jun 12 14:34:51 2013 From: john.r.rose at oracle.com (John Rose) Date: Wed, 12 Jun 2013 14:34:51 -0700 Subject: Hiding the lambda proxy frame In-Reply-To: References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> Message-ID: <990A7833-DDDC-4817-979A-35EB5119DB15@oracle.com> On Jun 12, 2013, at 1:41 PM, Daniel Heidinga wrote: > The more pressing concern from the presentation is whether the source file and line number for the lambda is correct. Ensuring we get this right makes a lot of sense. +1 (For debugging, I also like the idea of giving meaningful names to lambda bodies, when possible.) A stacktrace is a powerful tool for debugging and allowing frames to be hidden decreases the value of the stacktrace. With this behaviour change, the stacktrace and debugger will disagree on what the stack looks like - which is needlessly confusing. Personally, I hate having my debugging tools lie to me so I'd rather see accurate stacktraces. Yes. Here's an alternative to consider which involves fewer "lies": Introduce tail calls and use them in adapter code generated for lambdas. Then there's no confusion about the state of the JVM stack, since it is an operational change that all components would see in the same way. There would be the usual confusion with tail calls, about "how did I get here?" Tail calls are weaker than hiding. In particular, if you need to apply a checkcast to the result of a call before returning from an adapter, then that adapter should (arguably) show up on the stack, since it still has work to do. BTW, JDK 1.5 bridge methods (another kind of adapter code) have a similar issue with stack traces, and could also benefit from hiding and/or tail calling. > An annotation that hides stack frames is too easy to abuse, even if you have the best of intentions (like hiding implementation details). It will be over-used and lead to significant difficulties in finding where errors occurred. That is true. That is why @Hidden is non-public. > How hidden are LambdaForms$Hidden frames? Are they only hidden from stacktraces or do APIs like sun.reflect.Reflection.getCallerClass() also skip these hidden frames? The Hidden annotation affects the behavior of Throwable.fillInStackTrace and nothing else. ? John > --Dan > > > > > On 06/11/2013 03:22 PM, Brian Goetz wrote: > > > Why wouldn't you bring this to the EG list instead? > > > > Sure, it was an implementation detail for me, hence the post on lambda-dev. > > > > So from lambda-dev: > > Currently when you print a stacktrace that show a call inside a lambda, > > users can see the the frame corresponding to the method of the generated > > proxy. > > > > By example: > > java.lang.Throwable > > StackTraceTest.lambda$0(StackTraceTest.java:6) > > StackTraceTest$$Lambda$1.run(Unknown Source) <--- ugly, isn't it > > StackTraceTest.main(StackTraceTest.java:8) > > > > I think this line should not be visible to user, it doesn't provide > > useful information, > > just make the stack trace longer than it should. > > > > There is an annotation LambdaForm.Hidden that you can use to mark method > > that should be hidden when dumping the stack trace. > > (Maybe the annotation should be a top-level by the way, add John in CC given > > he is the creator of this annotation) > > > > This part was not in the original mail: > > Moreover, offline, one of my friend point me to a presentation of Jamie > > Allen > > http://jaxenter.com/what-you-need-to-know-about-lambdas-by-jamie- > > allen-47356.html > > on the same subject at JAXConf 2013. I've just finished to see it. > > > > He raises a good point that the compiler translation should try to > > provide a better name. > > By example, > > Runnable myRunnable = () -> System.out.println("hello"); > > should result in a lambda that instead to be called "lambda$0", > > instead the compiler should try to name it something like lambda$myRunnable > > (or lambda$myRunnable0, ..., if there is a collision) if it's possible. > > > > I think we already discuss about something like that in the light of > > serialization, > > but not in the stack trace context. > > > > R?mi > > > > > From forax at univ-mlv.fr Wed Jun 12 15:10:52 2013 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 13 Jun 2013 00:10:52 +0200 Subject: Hiding the lambda proxy frame In-Reply-To: References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> Message-ID: <51B8F1EC.9020407@univ-mlv.fr> Hi Dan, On 06/12/2013 10:41 PM, Daniel Heidinga wrote: > > The more pressing concern from the presentation is whether the source > file and line number for the lambda is correct. Ensuring we get this > right makes a lot of sense. > yes, it's a compiler issue. I've reported the bug and I'm sure it will be fixed soon. > > A stacktrace is a powerful tool for debugging and allowing frames to > be hidden decreases the value of the stacktrace. With this behaviour > change, the stacktrace and debugger will disagree on what the stack > looks like - which is needlessly confusing. Personally, I hate having > my debugging tools lie to me so I'd rather see accurate stacktraces. > First, the debuggers use JDI so will get the correct stack frame in the debugger. I propose to hide a very specific stack frame which carry no information, one reason to hide it is that having an "Unknown Source" in a stack trace troubles a lot of people, the other is as you said to not expose detail of implementation. This stack frame is the activation of a method which is generated so your debugger will not help you here, apart if you use an eclipse plugin that dumps the bytecode, there is no Java code corresponding to that specific stack frame. > > An annotation that hides stack frames is too easy to abuse, even if > you have the best of intentions (like hiding implementation details). > It will be over-used and lead to significant difficulties in finding > where errors occurred. > This annotation is only available if you generate code to mitigate that issue. > > How hidden are LambdaForms$Hidden frames? Are they only hidden from > stacktraces or do APIs like sun.reflect.Reflection.getCallerClass() > also skip these hidden frames? > Only for the stack frame which is accessible from Throwable, there no way to bypass the security using it. > > --Dan > R?mi > > > > > On 06/11/2013 03:22 PM, Brian Goetz wrote: > > > Why wouldn't you bring this to the EG list instead? > > > > Sure, it was an implementation detail for me, hence the post on > lambda-dev. > > > > So from lambda-dev: > > Currently when you print a stacktrace that show a call inside a lambda, > > users can see the the frame corresponding to the method of the generated > > proxy. > > > > By example: > > java.lang.Throwable > > StackTraceTest.lambda$0(StackTraceTest.java:6) > > StackTraceTest$$Lambda$1.run(Unknown Source) <--- ugly, isn't it > > StackTraceTest.main(StackTraceTest.java:8) > > > > I think this line should not be visible to user, it doesn't provide > > useful information, > > just make the stack trace longer than it should. > > > > There is an annotation LambdaForm.Hidden that you can use to mark method > > that should be hidden when dumping the stack trace. > > (Maybe the annotation should be a top-level by the way, add John in > CC given > > he is the creator of this annotation) > > > > This part was not in the original mail: > > Moreover, offline, one of my friend point me to a presentation of Jamie > > Allen > > http://jaxenter.com/what-you-need-to-know-about-lambdas-by-jamie- > > allen-47356.html > > on the same subject at JAXConf 2013. I've just finished to see it. > > > > He raises a good point that the compiler translation should try to > > provide a better name. > > By example, > > Runnable myRunnable = () -> System.out.println("hello"); > > should result in a lambda that instead to be called "lambda$0", > > instead the compiler should try to name it something like > lambda$myRunnable > > (or lambda$myRunnable0, ..., if there is a collision) if it's possible. > > > > I think we already discuss about something like that in the light of > > serialization, > > but not in the stack trace context. > > > > R?mi > > > > > From yuval.shavit at gmail.com Wed Jun 12 16:25:49 2013 From: yuval.shavit at gmail.com (Yuval Shavit) Date: Wed, 12 Jun 2013 19:25:49 -0400 Subject: Hiding the lambda proxy frame In-Reply-To: <51B8F1EC.9020407@univ-mlv.fr> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> <51B8F1EC.9020407@univ-mlv.fr> Message-ID: There's some precedence here, in generics. Say I have: public interface Generic { void foo(T arg); } public class Concrete implements Generic { @Override public void foo(String arg) { System.out.println(arg.toString()); } public static void main(String[] args) { Generic o = new Concrete(); o.foo(null); } } Exception in thread "main" java.lang.NullPointerException at Concrete.foo(Concrete.java:4) *at Concrete.foo(Concrete.java:1) <-------------* at Concrete.main(Concrete.java:9) The "Concrete.java:1" line for the generated raw method (I forget what this is called) seems analogous to the lambda proxy. I could see this going two ways: (1) show the proxy's frame, because there's precedent with generics; or (2) hide the proxy's frame, if the consensus is that showing the analogous frame for generics was a mistake. I would vote the latter, for what it's worth. It used to just confuse me, until I learned what it was; then it just annoyed me when I stepped into a generic method. I ignore them when looking at stack traces, so it doesn't bother me much -- but I wouldn't miss them if they were hidden. For those who want to show the frame, has seeing the frame for the raw method in a generic class ever been useful to you? On Wed, Jun 12, 2013 at 6:10 PM, Remi Forax wrote: > Hi Dan, > > > On 06/12/2013 10:41 PM, Daniel Heidinga wrote: > >> >> The more pressing concern from the presentation is whether the source >> file and line number for the lambda is correct. Ensuring we get this right >> makes a lot of sense. >> >> > yes, it's a compiler issue. > I've reported the bug and I'm sure it will be fixed soon. > > > >> A stacktrace is a powerful tool for debugging and allowing frames to be >> hidden decreases the value of the stacktrace. With this behaviour change, >> the stacktrace and debugger will disagree on what the stack looks like - >> which is needlessly confusing. Personally, I hate having my debugging >> tools lie to me so I'd rather see accurate stacktraces. >> >> > First, the debuggers use JDI so will get the correct stack frame in the > debugger. > > I propose to hide a very specific stack frame which carry no information, > one reason to hide it is that having an "Unknown Source" in a stack trace > troubles a lot of people, > the other is as you said to not expose detail of implementation. > This stack frame is the activation of a method which is generated so your > debugger will not help you here, > apart if you use an eclipse plugin that dumps the bytecode, there is no > Java code corresponding to that specific stack frame. > > > >> An annotation that hides stack frames is too easy to abuse, even if you >> have the best of intentions (like hiding implementation details). It will >> be over-used and lead to significant difficulties in finding where errors >> occurred. >> >> > This annotation is only available if you generate code to mitigate that > issue. > > > >> How hidden are LambdaForms$Hidden frames? Are they only hidden from >> stacktraces or do APIs like sun.reflect.Reflection.**getCallerClass() >> also skip these hidden frames? >> >> > Only for the stack frame which is accessible from Throwable, there no way > to bypass the security using it. > > >> --Dan >> >> > R?mi > > > >> > >> > On 06/11/2013 03:22 PM, Brian Goetz wrote: >> > > Why wouldn't you bring this to the EG list instead? >> > >> > Sure, it was an implementation detail for me, hence the post on >> lambda-dev. >> > >> > So from lambda-dev: >> > Currently when you print a stacktrace that show a call inside a lambda, >> > users can see the the frame corresponding to the method of the generated >> > proxy. >> > >> > By example: >> > java.lang.Throwable >> > StackTraceTest.lambda$0(**StackTraceTest.java:6) >> > StackTraceTest$$Lambda$1.run(**Unknown Source) <--- ugly, isn't >> it >> > StackTraceTest.main(**StackTraceTest.java:8) >> > >> > I think this line should not be visible to user, it doesn't provide >> > useful information, >> > just make the stack trace longer than it should. >> > >> > There is an annotation LambdaForm.Hidden that you can use to mark method >> > that should be hidden when dumping the stack trace. >> > (Maybe the annotation should be a top-level by the way, add John in CC >> given >> > he is the creator of this annotation) >> > >> > This part was not in the original mail: >> > Moreover, offline, one of my friend point me to a presentation of Jamie >> > Allen >> > http://jaxenter.com/what-you-**need-to-know-about-lambdas-by-**jamie- >> > allen-47356.html >> > on the same subject at JAXConf 2013. I've just finished to see it. >> > >> > He raises a good point that the compiler translation should try to >> > provide a better name. >> > By example, >> > Runnable myRunnable = () -> System.out.println("hello"); >> > should result in a lambda that instead to be called "lambda$0", >> > instead the compiler should try to name it something like >> lambda$myRunnable >> > (or lambda$myRunnable0, ..., if there is a collision) if it's possible. >> > >> > I think we already discuss about something like that in the light of >> > serialization, >> > but not in the stack trace context. >> > >> > R?mi >> > >> > >> >> > From Daniel_Heidinga at ca.ibm.com Thu Jun 13 10:31:55 2013 From: Daniel_Heidinga at ca.ibm.com (Daniel Heidinga) Date: Thu, 13 Jun 2013 13:31:55 -0400 Subject: invokespecial of default methods in unrelated interfaces Message-ID: We've put together a defender method test case using invokespecial and we'd like some confirmation of our spec interpretation. The testcase is: interface A { default void m(){ System.out.println("A"); }; } interface B { default void m(){ System.out.println("B"); }; interface C { default void m(){ System.out.println("C"); }; } class D implements A, B { void m() { C.super.m(); // Note, D does not implement C } public static void main(String[] args) { new D().m(); } } While javac won't compile this, it is legal bytecode (generated with ASM because class D is unrelated to interface C). Executing 'new D().m()' unfortunately crashes Hotspot b93. The lambda changes to the JVM spec for invokespecial indicate 'The named method is resolved (5.4.3.3)'. 5.4.3.3 states "Method resolution attempts to look up the referenced method in C and its superclasses". As C clearly has an implementation of m(), resolution should succeed and C.m() should be called. (As an aside, there is an issue with invokespecial referencing 5.4.3.3 for interface sends: "If C is an interface, method resolution throws an IncompatibleClassChangeError.") Does anyone disagree with this interpretation of the spec? --Dan From brian.goetz at oracle.com Thu Jun 13 11:14:54 2013 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 13 Jun 2013 14:14:54 -0400 Subject: invokespecial of default methods in unrelated interfaces In-Reply-To: References: Message-ID: <51BA0C1E.1000404@oracle.com> Well, I wouldn't expect C code to be invoked in any case, but a crash is probably not a good result. I believe the intent was to have invokespecial reject an invocation if the named class was not an immediate supertype of the invoker. There may also be additional restrictions that the compiler imposes, designed to prevent "level skipping" usages, that may or may not need to also be enforced by the VM. On 6/13/2013 1:31 PM, Daniel Heidinga wrote: > We've put together a defender method test case using invokespecial and > we'd like some confirmation of our spec interpretation. The testcase is: > > interface A { > default void m(){ System.out.println("A"); }; > } > interface B { > default void m(){ System.out.println("B"); }; > interface C { > default void m(){ System.out.println("C"); }; > } > class D implements A, B { > void m() { > C.super.m(); // Note, D does not implement C > } > public static void main(String[] args) { > new D().m(); > } > } > > While javac won't compile this, it is legal bytecode (generated with ASM > because class D is unrelated to interface C). Executing 'new D().m()' > unfortunately crashes Hotspot b93. > > The lambda changes to the JVM spec for invokespecial indicate 'The named > method is resolved (5.4.3.3)'. 5.4.3.3 states "Method resolution > attempts to look up the referenced method in C and its superclasses". > As C clearly has an implementation of m(), resolution should succeed > and C.m() should be called. (As an aside, there is an issue with > invokespecial referencing 5.4.3.3 for interface sends: "If C is an > interface, method resolution throws an IncompatibleClassChangeError.") > > Does anyone disagree with this interpretation of the spec? > > --Dan > From daniel.smith at oracle.com Thu Jun 13 13:06:08 2013 From: daniel.smith at oracle.com (Dan Smith) Date: Thu, 13 Jun 2013 14:06:08 -0600 Subject: invokespecial of default methods in unrelated interfaces In-Reply-To: References: Message-ID: <6D8D3332-33BF-49D1-A7E7-FE37471AE86A@oracle.com> On Jun 13, 2013, at 11:31 AM, Daniel Heidinga wrote: > We've put together a defender method test case using invokespecial and we'd like some confirmation of our spec interpretation. The testcase is: > > interface A { > default void m(){ System.out.println("A"); }; > } > interface B { > default void m(){ System.out.println("B"); }; > interface C { > default void m(){ System.out.println("C"); }; > } > class D implements A, B { > void m() { > C.super.m(); // Note, D does not implement C > } > public static void main(String[] args) { > new D().m(); > } > } > > While javac won't compile this, it is legal bytecode (generated with ASM because class D is unrelated to interface C). Executing 'new D().m()' unfortunately crashes Hotspot b93. > > The lambda changes to the JVM spec for invokespecial indicate 'The named method is resolved (5.4.3.3)'. 5.4.3.3 states "Method resolution attempts to look up the referenced method in C and its superclasses". As C clearly has an implementation of m(), resolution should succeed and C.m() should be called. (As an aside, there is an issue with invokespecial referencing 5.4.3.3 for interface sends: "If C is an interface, method resolution throws an IncompatibleClassChangeError.") > > Does anyone disagree with this interpretation of the spec? I was confused by the apparent anything-goes nature of the spec for invokespecial, too, until I found 4.9.2. "Each invokespecial instruction must name an instance initialization method (?2.9), a method in the current class, or a method in a superclass of the current class." Strictly speaking, this means that all superinterface calls are illegal. Of course, we need to change it to allow calls to methods in direct superinterfaces. That's on the radar for the next iteration of the spec. Here's a write-up I did recently for our own VM people about how invokespecial is specified, what needs to change, and what isn't entirely resolved yet. You may find it useful, and I'm happy to hear any feedback you have. > The JVMS 7 spec describes the behavior, roughly, as follows: > > 1) At link time (5.4.1), structural constraints are enforced (4.9.2): the referenced method must be one of: i) an instance initialization method, ii) a non-init method in the current class, or iii) a non-init method in a superclass of the current class. [Note: does "in" mean "declared in" or "referenced in"?] > > 2) The method reference is resolved (5.4.3.3). Various errors can occur. [Note: not sure on how this interacts with (1). I assume we have to at least resolve the class name before we can decide if the invocation is structurally well-formed or not...] > > 3) In case (i), if the resolved method is not declared by the named class, NSME. > > 4) If the resolved method is 'static', ICCE. > > 5) In cases (i) and (iii) where the resolved method is 'protected', the dynamic protected-access check is performed. [Note: this is strange because it doesn't identify the error that occurs and it doesn't show up in the list of Runtime Exceptions -- only in the Description section; also note that the HotSpot implementation of the check seems overly-aggressive: JDK-8009697.] > > 6) Selection occurs. [Note: Unlike invokevirtual, selection is static -- it need only occur once per instruction.] > - In cases (i) and (ii): the resolved method is selected. > - In case (iii) with ACC_SUPER not set: the resolved method is selected. > - In case (iii) with ACC_SUPER set: we search for a matching name/descriptor starting at the immediate super and walking the superclass chain. If there is no matching superclass method (the resolved method is in an interface), AME. [Note: this is only relevant if the named class is _not_ the immediate super.] > > 7) If the objectref is null, NPE. > > 8) If the selected method is abstract, AME. > > 9) The selected method is invoked. > > Does that sound right? Consistent with HotSpot? > > --- > > Lambda Spec, 0.6.2 (Part J) makes the following changes: > > - The instruction may use a Methodref _or_ an InterfaceMethodref (4.9.1) > > - Rather than choosing nondeterministically among methods in a superinterface hierarchy, resolution chooses the "best" one, or reports an error if there is more than one "best" one. > > There are some problems here... > > --- > > Lambda Spec Part J _should_ do the following, too: > > - Case (ii) should not be read to exclude the possibility that the "current class" is an interface > > - Another case, (iv), is permitted by the structural constraints: a non-init method referenced via a _direct_ superinterface of the current class/interface. (It's not clear to me whether case (iii) is meant to refer to the _named_ class or the _resolved method's_ class. But this, for (iv), must be a restriction on the named class.) > > - Selection for case (iv) is just like selection for cases (i) and (ii) -- the resolved method is the selected method. > > - Selection for case (iii) needs to search superinterfaces before giving up, much like invokevirtual selection. > > I think that should address the bugs. Anything I missed? > > --- > > Alternative 1: Loose overriding for invokespecial > > This was part of Keith's prototype: invokespecial was modified to identify interface methods that override the resolved method (in the language-level sense) without relying on bridges. > > This is no longer what we want. The code changes should be removed, if they haven't been already. > > --- > > Alternative 2: Indirect superinterface referencing > > This would involve relaxing case (iv) so that the named class can be _any_ superinterface of the current class/interface. javac would never take advantage of the change, but it would be more consistent with the treatment of superclass methods. > > The downside is that we would then need to do the ACC_SUPER selection process (6). And it's not clear _which_ superinterface (or superclass) should act as the starting point for the selection search. (It's possible that more than one direct superinterface is a subtype of the named interface.) > > Also worth noting that, in retrospect, we really don't like the existing ability to name any superclass; if we were starting fresh, we'd probably force the named class to be the direct superclass. > > --- > > Alternative 3: Minimize resolution errors > > For invokevirtual and invokeinterface, Karen has pointed out that the resolution errors that occur when there is no "best" interface method are unnecessarily brittle: ultimately, selection will find a class method, or a "best" interface method, or report an error. > > So we could change resolution so that it simply picks an arbitrary interface method. (That's nondeterministic, and the nondeterminism can be observed -- class loader constraints depend on the choice -- but that's the way it has always been.) > > This would leave invokespecial with the responsibility of ensuring that the resolved method really is uniquely "best" in cases where the ACC_SUPER selection process does not kick in, and report an error otherwise. But that seems do-able. One way to formulate it would be to simply always do selection, with some extra parameters about where the search starts... > > invokestatic wouldn't have to worry about ambiguity, because it already reports an error if the resolved method appears in an interface other than the named interface. I'd pay the most attention to Alternative 3. There does seem to be a lot of interest in refining resolution so that it's more permissive. (The presence of static and private methods complicates matters, though.) ?Dan From Daniel_Heidinga at ca.ibm.com Thu Jun 13 19:30:02 2013 From: Daniel_Heidinga at ca.ibm.com (Daniel Heidinga) Date: Thu, 13 Jun 2013 22:30:02 -0400 Subject: Hiding the lambda proxy frame In-Reply-To: <990A7833-DDDC-4817-979A-35EB5119DB15@oracle.com> References: <51B71E18.7020501@univ-mlv.fr> <51B7249D.4050109@oracle.com> <51B74E78.6060807@univ-mlv.fr> <990A7833-DDDC-4817-979A-35EB5119DB15@oracle.com> Message-ID: > Introduce tail calls and use them in adapter code generated for > lambdas. Then there's no confusion about the state of the JVM > stack, since it is an operational change that all components would > see in the same way. There would be the usual confusion with tail > calls, about "how did I get here?" > I like this idea but its hard to argue that tail calls are in scope for lambda. Tail calls should really be an entirely separate JSR. Addressing these generated adaptor frames (and the other similar frames) is something that should be left to that (future?) JSR. --Dan From Daniel_Heidinga at ca.ibm.com Thu Jun 20 19:34:14 2013 From: Daniel_Heidinga at ca.ibm.com (Daniel Heidinga) Date: Thu, 20 Jun 2013 22:34:14 -0400 Subject: invokespecial of default methods in unrelated interfaces In-Reply-To: <51BA0C1E.1000404@oracle.com> References: <51BA0C1E.1000404@oracle.com> Message-ID: Regarding the invokespecial to an unrelated interface, I agree this should be illegal. At a minimum, it should be required that the interface targeted by the invokespecial must be compatible with the invoker as this prevents invoking methods in unrelated interfaces. While thinking about this, I've also taken a look at how the VM can prevent "level skipping" while preserving both binary compatibility and programmer intent. The picture is not pretty. After reading both your and Dan's emails, there is a subtle difference in the terms both of you are using: * "immediate supertype of the invoker" - Immediate supertype is not defined by the JVM spec but intuitively, a superinterface ("I") is not an immediate supertype if it is also a parent of another superinterface (J, J extends I). * "direct superinterface" (Dan's term) - This is defined by the JVM spec 4.1 to be any interface declared in the classfile's interfaces[]. Using this definition, but I and J would be considered direct superfaces if they were declared in the interfaces[]. Using this definition means that redundant interface declarations are suddenly meaningful information. Starting with this simple example: interface J { default void m() {...}} interface I { default void m() {...}} interface K extends I, J { default void m() { I.super.m(); } K is a valid class that resolves the conflicting defenders by choosing to dispatch to I.m(). If J is changed to extend I, a binary compatible change, then the question becomes "is I still a direct / immediate superinteface?" and a valid target for invokespecial? If I is not a valid target because it allows K to skip J.m(), then K will throw an AbstractMethodError and violate the binary compatibility requirements. Enforcing "no level skipping" can also break programs if new defender implementations are added, which decreases our ability to evolve interfaces. interface A { default void m() {...}} interface B { } interface C { } interface D extends B, C { default void m() { C.super.m(); } Adding a new default implementation of m() to B will also result in AbstraceMethodError as D's super send would skip the new B.m(). If resolution started at the targeted class 'C', as stated in the current 335 spec, then this would continue to call A.m() matching the intent at the time the code was written. Unfortunately, this makes 'C' a "bypass interface" which allows level skipping by using dummy subinterfaces. (Note, a clever programmer can always target any particular interface's default implementation by adding a "bypass interface" that implements the required method using a super send to the actual desired interface - this is just an abuse of the standard way for users to fix defender conflicts.) This seems to require the VM to ignore level skipping. Unfortunately, there's holes here with respect to programmer intent because the targeted interface must be named: interface S { default void m() { } } class T implements S { } class U extends T implements S { void m() { S.super.m(); } } The programmer than refines T to target a interface V: interface V extends S { default void m() {...} } class T implements V { } And now class U will throw an AbstractMethodError. This is the simple case, a single inheritance path, that might argue for some kind of interface analog to the ACC_SUPER search rules. If the goal is to preserve binary compatibility - that pre-existing binaries continue to link without error whenever possible - then preventing level skipping in the VM is impossible. The following is a resolution algorithm for invokespecial that attempts to limit level skipping while preserving binary compatibility: * Perform interface resolution using the set of interfaces implemented by the invoker of the invokespecial as defined in the JSR 335 JVM spec 5.4.3.4. * Examine the set of candidate methods: ** If there is only 1 method, then resolution succeeds with that method (This assumes any super implementation meets the required contract) ** If there are more than 1 method, remove any method that is not provided by the the interface named in the invokespecial or subclass of it. *** If this results in a single method, resolution succeeds with that method. * Otherwise, redo method resolution starting at the interface named in the invokespecial and succeed or fail with the result of this resolution. This uses two-passes to limit level skipping in cases when possible, such as the first example in this email, while preserving binary compatibility when the first pass would otherwise of failed. --Dan > Well, I wouldn't expect C code to be invoked in any case, but a crash is > probably not a good result. > > I believe the intent was to have invokespecial reject an invocation if > the named class was not an immediate supertype of the invoker. There > may also be additional restrictions that the compiler imposes, designed > to prevent "level skipping" usages, that may or may not need to also be > enforced by the VM. > > On 6/13/2013 1:31 PM, Daniel Heidinga wrote: > > We've put together a defender method test case using invokespecial and > > we'd like some confirmation of our spec interpretation. The testcase is: > > > > interface A { > > default void m(){ System.out.println("A"); }; > > } > > interface B { > > default void m(){ System.out.println("B"); }; > > interface C { > > default void m(){ System.out.println("C"); }; > > } > > class D implements A, B { > > void m() { > > C.super.m(); // Note, D does not implement C > > } > > public static void main(String[] args) { > > new D().m(); > > } > > } > > > > While javac won't compile this, it is legal bytecode (generated with ASM > > because class D is unrelated to interface C). Executing 'new D().m()' > > unfortunately crashes Hotspot b93. > > > > The lambda changes to the JVM spec for invokespecial indicate 'The named > > method is resolved (5.4.3.3)'. 5.4.3.3 states "Method resolution > > attempts to look up the referenced method in C and its superclasses". > > As C clearly has an implementation of m(), resolution should succeed > > and C.m() should be called. (As an aside, there is an issue with > > invokespecial referencing 5.4.3.3 for interface sends: "If C is an > > interface, method resolution throws an IncompatibleClassChangeError.") > > > > Does anyone disagree with this interpretation of the spec? > > > > --Dan > > > From daniel.smith at oracle.com Fri Jun 21 10:12:48 2013 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 21 Jun 2013 11:12:48 -0600 Subject: invokespecial of default methods in unrelated interfaces In-Reply-To: References: <51BA0C1E.1000404@oracle.com> Message-ID: <7954F73A-06CA-4D5E-874F-E7F3F6F2B1A8@oracle.com> Thanks for taking the time to explore this in detail. See my comments below. On Jun 20, 2013, at 8:34 PM, Daniel Heidinga wrote: > Regarding the invokespecial to an unrelated interface, I agree this should be illegal. At a minimum, it should be required that the interface targeted by the invokespecial must be compatible with the invoker as this prevents invoking methods in unrelated interfaces. > > While thinking about this, I've also taken a look at how the VM can prevent "level skipping" while preserving both binary compatibility and programmer intent. The picture is not pretty. > > After reading both your and Dan's emails, there is a subtle difference in the terms both of you are using: > * "immediate supertype of the invoker" - Immediate supertype is not defined by the JVM spec but intuitively, a superinterface ("I") is not an immediate supertype if it is also a parent of another superinterface (J, J extends I). > > * "direct superinterface" (Dan's term) - This is defined by the JVM spec 4.1 to be any interface declared in the classfile's interfaces[]. Using this definition, but I and J would be considered direct superfaces if they were declared in the interfaces[]. Using this definition means that redundant interface declarations are suddenly meaningful information. FWIW, I read "immediate supertype of the invoker" as "direct superinterface". In any case, yes, there are two different ways we could define the set of valid invokespecial target interfaces. > Starting with this simple example: > interface J { default void m() {...}} > interface I { default void m() {...}} > interface K extends I, J { default void m() { I.super.m(); } > K is a valid class that resolves the conflicting defenders by choosing to dispatch to I.m(). > If J is changed to extend I, a binary compatible change, then the question becomes "is I still a direct / immediate superinteface?" and a valid target for invokespecial? > If I is not a valid target because it allows K to skip J.m(), then K will throw an AbstractMethodError and violate the binary compatibility requirements. 1) Let me point out that, inherent with multiple inheritance of behavior, there are certain refinements that have to be made to the concept of binary compatibility. I don't know exactly what they look like, but the status quo will not do. Example: interface I { default void m() { ... } } interface J {} class C implements I, J {} new C; invokevirtual C.m()V Runs successfully. Now add a default m()V method to J, or add a new superinterface to C that declares m()V (both binary compatible changes, per JLS 7), and there's an ambiguity error. The details of exactly what JVM 8 binary compatibility looks like need to be explored in depth; I'm happy to consider any suggestions you may have. 2) That said, I do not think we should be trying to enforce the full language-level concept of no level skipping. I'm happy to allow the named class to be any direct superinterface of the calling class. > Enforcing "no level skipping" can also break programs if new defender implementations are added, which decreases our ability to evolve interfaces. > interface A { default void m() {...}} > interface B { } // B extends A? > interface C { } // C extends A? > interface D extends B, C { default void m() { C.super.m(); } > Adding a new default implementation of m() to B will also result in AbstraceMethodError as D's super send would skip the new B.m(). > If resolution started at the targeted class 'C', as stated in the current 335 spec, then this would continue to call A.m() matching the intent at the time the code was written. Unfortunately, this makes 'C' a "bypass interface" which allows level skipping by using dummy subinterfaces. > > (Note, a clever programmer can always target any particular interface's default implementation by adding a "bypass interface" that implements the required method using a super send to the actual desired interface - this is just an abuse of the standard way for users to fix defender conflicts.) Again, bypassing B.m doesn't bother me. I'm happy to leave this restriction to the language and allow the call to proceed in the VM. > This seems to require the VM to ignore level skipping. Unfortunately, there's holes here with respect to programmer intent because the targeted interface must be named: > interface S { default void m() { } } > class T implements S { } > class U extends T implements S { void m() { S.super.m(); } } > The programmer than refines T to target a interface V: > interface V extends S { default void m() {...} } > class T implements V { } > And now class U will throw an AbstractMethodError. This is the simple case, a single inheritance path, that might argue for some kind of interface analog to the ACC_SUPER search rules. > > If the goal is to preserve binary compatibility - that pre-existing binaries continue to link without error whenever possible - then preventing level skipping in the VM is impossible. That's okay with me. As long as U directly implements S, it doesn't bother me to allow free access to the (accessible) methods of S. > The following is a resolution algorithm for invokespecial that attempts to limit level skipping while preserving binary compatibility: > > * Perform interface resolution using the set of interfaces implemented by the invoker of the invokespecial as defined in the JSR 335 JVM spec 5.4.3.4. > * Examine the set of candidate methods: > ** If there is only 1 method, then resolution succeeds with that method (This assumes any super implementation meets the required contract) > ** If there are more than 1 method, remove any method that is not provided by the the interface named in the invokespecial or subclass of it. > *** If this results in a single method, resolution succeeds with that method. > * Otherwise, redo method resolution starting at the interface named in the invokespecial and succeed or fail with the result of this resolution. > > This uses two-passes to limit level skipping in cases when possible, such as the first example in this email, while preserving binary compatibility when the first pass would otherwise of failed. I gather the effect you're after is to, where there is an unambiguous choice, select the "most specific method" that overrides the named method. But this is seriously weakened (and rendered fairly unpredictable) by the fact that, where there is an ambiguous choice, the named method is chosen anyway. If I've added a method in what I hope is a compatible manner, but that actually messes with resolution/selection of a method, I don't think I'll be happier with an unintuitive behavioral change than with a new error. At the level of the VM, I think we should be making hard guarantees, or not trying to do anything at all. Fuzzy "this is probably what you meant" logic doesn't belong here. ?Dan From Daniel_Heidinga at ca.ibm.com Thu Jun 27 07:24:24 2013 From: Daniel_Heidinga at ca.ibm.com (Daniel Heidinga) Date: Thu, 27 Jun 2013 10:24:24 -0400 Subject: invokespecial of default methods in unrelated interfaces In-Reply-To: <7954F73A-06CA-4D5E-874F-E7F3F6F2B1A8@oracle.com> References: <51BA0C1E.1000404@oracle.com> <7954F73A-06CA-4D5E-874F-E7F3F6F2B1A8@oracle.com> Message-ID: > FWIW, I read "immediate supertype of the invoker" as "direct > superinterface". In any case, yes, there are two different ways we > could define the set of valid invokespecial target interfaces. Thanks for clearing up the terminology. Using the VM semantics for "direct superinterface" (ie: in classfile->interfaces[]), is a good approach. > The details of exactly what JVM 8 binary compatibility looks like > need to be explored in depth; I'm happy to consider any suggestions > you may have. > > 2) That said, I do not think we should be trying to enforce the full > language-level concept of no level skipping. I'm happy to allow the > named class to be any direct superinterface of the calling class. > Wearing my implementers hat, I have no objections here - this is certainly simpler and clearer to both spec and implement. However, the primary motivator for adding default methods was to enable interface evolution without breaking existing binaries. They enable this goal for Java 8 as it's the first release that will have default methods so there is no chance of conflict. Future Java releases may refactor interface hierarchies, move or add default implementations, which may break working binaries and prevent users from upgrading. Binary compatibility brings the discussion full circle back to the issue of how to resolve default method conflicts. Up to now, the decision has been to avoid complex strategies (linearization, recording compile time targets in the classfile) and just throw AbstractMethodError. One possibility is to change the binary compatibility rules so that refactoring interfaces with default methods or adding new default methods is not binary compatible. But this contradicts the state goals of interface evolution. It becomes a "one shot" thing that can be done for Java 8 and never again as future changes won't be binary compatible. A better, but certainly not perfect, answer is to introduce a default method versioning scheme using a @defaultSince annotation. In default conflict cases (both during vtable creation and super sends), resolution will pick the method with the lowest @defaultSince version. The lowest version representing the most likely valid compile time target. Cases where the conflict methods have the same @defaultSince version will result in an AbstractMethodError. This requires a consistent versioning scheme so that libraries from different maintenance domains don't accidentally "under-ride" the original default target. For the JDK using the 1.8, 1.9, etc versions will provide a consistent experience or we may be able to borrow a versioning scheme from Jigsaw. Making this system robust may be extremely difficult. Its primary benefit is that it provides a clear answer for JDK evolution, but may struggle with other frameworks versioning. Binary compatibility is too important to give up, even if it adds complexity to the VM. This seems like a necessary tradeoff. --Dan