ClassFileTransformer does not apply to anonymous classes

Rafael Winterhalter rafael.wth at gmail.com
Tue Jan 26 08:39:33 UTC 2016


Another note on this subject: I found that applying a
reretransformation on a lambda expression's class does not provide the
original class file to the retransformer but the class file that
resulted from a previous retransformation. (If a lambda class is
retransformed several times.) This is different to "normal" classes
where the original class file is provided.

2016-01-25 20:24 GMT+01:00 Rafael Winterhalter <rafael.wth at gmail.com>:
> Hi Vladmir, hello Remi,
>
> what bothers me about instrumenting a lambda expression's target
> method is the difficulty of locating the method that contains the code
> and setting it into context. This is a lot of work to do since one
> needs to first locate any invokedynamic call sites and interpret the
> connection via the LambdaMetafactory. This is difficult to instrument
> without greater efforts and does not feel like a clean solution.
>
> Many real life instrumentations are based on the assumption of being
> able to instrument any class of a given subtype or other any other
> class property. For example, one would want to instrument any class
> that implements InputStream to discover a resource leak. If the
> interface is however functional and implemented as a lambda
> expression, a tool that manipulating all class files of this type
> stops working. At the same time, end users do not really understand
> the difference of their former anonymous class that is now expressed a
> lambda expression and perceive the beaviour as a regression.
>
> I have myself implemented a custom solution for my code generation
> library that instruments the LambdaMetafactory to apply class file
> transformers to these classes manually. This does however show two
> disadvantages:
>
> 1. javac creates the target method of a lambda expression as a private
> method. The only way to implement a lambda expression legally in byte
> code is by loading the generated implementation anonymously what
> requires non-public API. While it is possible to call
> Unsafe::defineAnonymousClass from the instrumented LambdaMetafactory,
> it is again required to use non-standardized APIs which might fail on
> differing implementations of the JVM.
>
> 2. At the same time, there is no standardized way to receive all
> ClassFileTransformers that are currently registered on the VM. this
> also requires calls into internal APIs that are not necessarily
> supported on other platforms.
>
> I fully understand the hesitation to support this from a technical
> point of view but in reality, people are already dependant on this
> feature and disallowing the instrumentation of lambda classes will
> only inspire work-arrounds that reduce the stability of software using
> this APIs that does currently work without problems for non-lambda
> classes.
>
> Best regards, Rafael
>
> PS: While implementing my solution, I found that the LambdaMetafactory
> of the Open JDK creates private, final methods for the serialization
> bits. The methods should however not be final as they are already
> private.
>
>
> 2016-01-23 13:55 GMT+01:00 Remi Forax <forax at univ-mlv.fr>:
>> I agree with Vladimir,
>>
>> You should not be able to transform/redefine a VM anonymous class because the transformer will certainly mess up with the order of the constant pool entries.
>>
>> Slightly off-topic, about ASM, when you create a ClassWriter [1], you can pass a ClassReader of an existing class, in that case ASM copy the constant pool from the class reader to the class writer so the constant pool is preserved.
>>
>> Rémi
>>
>> [1] http://asm.ow2.org/asm50/javadoc/user/org/objectweb/asm/ClassWriter.html#ClassWriter%28org.objectweb.asm.ClassReader,%20int%29
>>
>> ----- Mail original -----
>>> De: "Vladimir Ivanov" <vladimir.x.ivanov at oracle.com>
>>> À: "Rafael Winterhalter" <rafael.wth at gmail.com>
>>> Cc: "Coleen Phillimore" <coleen.phillimore at oracle.com>, core-libs-dev at openjdk.java.net, "serguei.spitsyn at oracle.com
>>> Spitsyn" <serguei.spitsyn at oracle.com>, "Daniel Daugherty" <daniel.daugherty at oracle.com>
>>> Envoyé: Vendredi 22 Janvier 2016 18:47:31
>>> Objet: Re: ClassFileTransformer does not apply to anonymous classes
>>>
>>> Rafael,
>>>
>>> First of all, I'd like to agree on the terminology. There's some
>>> confusion there. Anonymous term is ambiguous in Java. There are
>>> anonymous classes on language level and there's
>>> Unsafe.defineAnonymousClass() which produce anonymous-in-VM-sense
>>> classes. I prefer to call them VM anonymous classes to avoid confusion.
>>>
>>> I assume that whenever you use anonymous you assume "VM anonymous".
>>>
>>> > thank you for your response. While I completely understand your view
>>> > from a VM implementor's point of view, as a practicioner I would
>>> > recommend against it. Not being able to instrument lambda expressions
>>> > puts a severe limitation onto using the instrumentation API
>>> > alltogether. For example, a monitoring application that promises to
>>> > record all invocations of a method of a certain interface that only
>>> > works 95% of the time is not 5% less usefull but might no longer be
>>> > useful at all. People have build large applications based on the
>>> > assumption that all user application code can be instrumented and such
>>> > a regression would hurt them. For example, until today, over 30 people
>>> > that use my code generation framework reached out to me and reported
>>> > the reported behavior as a bug in my library and people are only
>>> > starting to migrate their applications to Java 8. The outcome is
>>> > simply not intuitive as using a lambda expression over an anonyous
>>> > inner class should not change an application's behavior.
>>> Can you elaborate on that point, please?
>>>
>>> I don't see any problems with instrumenting user code. Javac represents
>>> lambda expression body as a private static method of the enclosing
>>> class, which can be instrumented. VM anonymous classes are used only as
>>> a mechanism to glue functional interfaces and lambda expressions
>>> together at runtime.
>>>
>>> For example:
>>>     static void f(Runnable r) { r.run(); }
>>>     f(() -> {});
>>>
>>> Test::f (7 bytes)
>>>    @ 1   Test$$Lambda$1/791452441::run (4 bytes)   inline (hot)
>>>      @ 0   Test::lambda$main$0 (1 bytes)   inline (hot)
>>>
>>> Why do you need to instrument Test$$Lambda$1/... and not
>>> Test::lambda$main$0?
>>>
>>> > The currently used workaround that people use is to instrument the
>>> > LambdaMetaFactory class itself to apply the transformer manually when
>>> > it is being created. This solution is spreading as a standard approach
>>> > and I am sure that forbidding a redefinition alltogether would only
>>> > inspire to other workarrounds for being able to migrate running code
>>> > to the next Java version.
>>> It doesn't sound like a workaround, but as a solution for a different
>>> problem.
>>>
>>> If there's a need to gather profile for every lambda expression
>>> instantiation site (indy call), then redefining bootstrap method is the
>>> right way to go IMO. It allows to intercept and customize indy call site
>>> binding.
>>>
>>> What does sound as a workaround is an attempt to instrument classes
>>> produced by LambdaMetaFactory. Right now, it generates a fresh VM
>>> anonymous class on every request, but it is an implementation detail and
>>> not a requirement.
>>>
>>> For example, LambdaMetaFactory can reuse wrappers on per-functional
>>> interface basis. It would provide significant footprint savings for
>>> lambda expression-rich code bases (usually, there are much more
>>> instantiations than functional interface flavors).
>>>
>>> > Furthermore, I do not think that not being able to patch constant pool
>>> > indices in the generated code introduces any problems in practice.
>>> > Most real-world instrumentation only appends new constant pool entries
>>> > without interfering with the existant pool. Rather, I would like to
>>> > see an exception being raised if one attempts to change the original
>>> > constant pool without me being able to consider this from a technical
>>> > perspective. I think this would serve to be sufficient for most
>>> > people.
>>> If you care only about lambda expressions, then constant pool patching
>>> shouldn't be a problem (AFAIR it isn't used for lambda expressions). But
>>> in a more generic case (even for java.lang.invoke purposes), the
>>> coupling between class file and CP patches is very tight and can be
>>> easily broken with benign class file transformations. Considering ASM
>>> library, I'm not sure that even simple instrumentations preserve
>>> original constant pool structure.
>>>
>>> Probably, VM can do some structural checks on CP to forbid any
>>> modifications except appends, but there are still aliasing problems -
>>> sharing doesn't work (even for string constants), so new code shouldn't
>>> use original CP entries.
>>>
>>> I'd prefer to avoid such increase in complexity in VM implementation.
>>>
>>> > I really hope that there is a way to allow for patching anonymously
>>> > loaded classes even without exposing the constant pool patches.
>>>  From VM implementation perspective I don't see any problems with
>>> allowing class file redefinition for VM anonymous classes. What I want
>>> to do is to discourage from doing so. Again - it is very fragile and
>>> there's little we can do to fix that.
>>>
>>> > So far, thank you for looking into this. I am sure this a more complex
>>> > matter than what I am able to comprehend. I appreciate that you are
>>> > taking the time to consider my opinion!
>>>
>>> Best regards,
>>> Vladimir Ivanov
>>>



More information about the core-libs-dev mailing list